An NDBM Database Example

An example of a small application employing a NDBM database is presented in the upcoming listings. The purpose of the application is to tree walk one or more directory names, calling lstat(2) on each file system object. Then the lstat(2) information is stored in the snapshot database and indexed by the device number and i-node number. The application has been named SnapShot.

Once a snapshot has been taken, it is possible to invoke the application again with different command-line options. With the -c option provided, the SnapShot program will then walk the named directories, comparing each file system's lstat(2) information to what is stored in the database. Any differences are then reported. This provides similar functionality to the Tripwire[r] file integrity software.

Directory Software

In order to perform the directory tree walk, a C++ class named Dir was created. Listing 14.1 shows the Dir.h include file, which declares the class.

Code Listing 14.1. Dir.h—The Dir Class Definition Source File
1:   // dir.h
2:  
3:   #ifndef _dir_h_
4:   #define _dir_h_
5:  
6:   #include <sys/types.h>
7:   #include <dirent.h>
8:  
9:   ////////////////////////////////////////////////////////////
10:  // A Directory class object :
11:  ////////////////////////////////////////////////////////////
12: 
13:  class Dir {
14:      DIR     *dir;
15:      char    *name;
16:      int     error;
17:  public:
18:      Dir();
19:      ~Dir();
20:      Dir &open(const char *path);
21:      Dir &rewind();
22:      Dir &close();
23:      char *read();
24:      inline int getError() {  return error; }
25:      inline char *getEntry() {  return name; }
26:  };
27: 
28:  #endif // _dir_h_
29: 
30:  // End dir.h

The class shown in Listing 14.1 implements methods to open a directory (Dir::open()),rewind it (Dir::rewind()), read entries (Dir::read()), and close it (Dir::close()). Additional inline methods Dir::getError() and Dir::getEntry() are provided. The destructor takes care of automatically closing the directory if necessary.

Listing 14.2 shows how the class is implemented.

Code Listing 14.2. Dir.cc—The Implementation of the Dir Class
1:   // dir.cc
2:  
3:   #include "Dir.h"
4:   #include <errno.h>
5:   #include <string.h>
6:  
7:   extern "C" char *strdup(const char *str);
8:  
9:   ////////////////////////////////////////////////////////////
10:  // Dir Constructor :
11:  ////////////////////////////////////////////////////////////
12: 
13:  Dir::Dir() {
14:      dir = 0;
15:      name = 0;
16:  }
17: 
18:  ////////////////////////////////////////////////////////////
19:  // Dir Destructor :
20:  ////////////////////////////////////////////////////////////
21: 
22:  Dir::~Dir() {
23:      if ( dir )
24:          close();
25:  }
26: 
27:  ////////////////////////////////////////////////////////////
28:  // Open a directory :
29:  ////////////////////////////////////////////////////////////
30: 
31:  Dir &
32:  Dir::open(const char *path) {
33: 
34:      if ( dir )
35:          throw error = EINVAL;   // Object is already open
36: 
37:      dir = ::opendir(path);      // Attempt to open directory
38:      if ( !dir )
39:          throw error = errno;    // Open failed
40: 
41:      return *this;
42:  }
43: 
44:  ////////////////////////////////////////////////////////////
45:  // Close a directory :
46:  ////////////////////////////////////////////////////////////
47: 
48:  Dir &
49:  Dir::close() {
50:      int z;
51: 
52:      if ( !dir )
53:          throw error = EINVAL;   // Nothing to close
54:      if ( name ) {
55:          delete name;
56:          name = 0;               // No name now
57:      }
58:      z = ::closedir(dir);
59:      dir = 0;                    // No dir now
60:      if ( z == -1 )
61:          throw error = errno;
62:      return *this;
63:  }
64: 
65:  ////////////////////////////////////////////////////////////
66:  // Read a directory :
67:  ////////////////////////////////////////////////////////////
68: 
69:  char *
70:  Dir::read() {
71:      dirent *p;
72: 
73:      if ( !dir )
74:          throw error = EINVAL;   // Nothing to read
75:      if ( name ) {
76:          delete name;
77:          name = 0;
78:      }
79: 
80:      p = readdir(dir);           // Read the next entry
81:      if ( !p )
82:          return name;            // End of directory
83: 
84:      return name = strdup(p->d_name);
85:  }
86: 
87:  ////////////////////////////////////////////////////////////
88:  // Rewind a directory :
89:  ////////////////////////////////////////////////////////////
90: 
91:  Dir &
92:  Dir::rewind() {
93:
94:      if ( !dir )
95:          throw error = EINVAL;   // Nothing to rewind
96:      ::rewinddir(dir);           // Rewind directory
97:      return *this;
98:  }
99: 
100: // End dir.cc

The methods in the Dir class throw errno values if errors are detected. An example of this is in lines 38 and 39 of Listing 14.2. If the opendir(3) call fails, the value in errno is thrown in line 39. The error EINVAL is thrown if the directory is not open, and an operation such as Dir::read() is attempted (lines 73 and 74, for example).

The implementation of this class should be review, since Chapter 7, "Directory Management," covered the directory functions in detail. Only the file system object name is returned by the Dir::read() method (see line 84).

The Dbm Class

The Dbm class is declared in the include file Dbm.h, which is shown in Listing 14.3. This class wraps the NDBM functions in a C++ object for convenience and simplicity. Additionally, this approach allows exceptions and destructors to be used. The object destructor ensures that the database is properly closed.

Code Listing 14.3. Dbm.h—The Dbm Class Definition
1:   // Dbm.h
2:  
3:   #ifndef _Dbm_h_
4:   #define _Dbm_h_
5:  
6:   #include <sys/types.h>
7:   #include <unistd.h>
8:   #include <ndbm.h>
9:   #include <fcntl.h>
10: 
11:  ////////////////////////////////////////////////////////////
12:  // A Class for the DBM Routines :
13:  ////////////////////////////////////////////////////////////
14: 
15:  class Dbm {
16:      int     flags;          // Open flags
17:      char    *path;          // Pathname of database
18:      DBM     *db;            // Open database
19:  protected:
20:      int     error;          // Last error
21:  public:
22:      Dbm();
23:      ~Dbm();
24:      Dbm &open(const char *path,int flags=O_RDWR,int mode=0666);
25:      Dbm &close();
26:      datum fetch(datum key);
27:      Dbm &store(datum key,datum content,int flags);
28:      Dbm &deleteKey(datum key);
29:      datum firstKey();
30:      datum nextKey();
31:      inline int getError() {  return error; }
32:      inline int getFlags() {  return flags; }
33:      inline char *getPath() {  return path; }
34:  };
35:
36:  #endif // _Dbm_h_
37:
38:  // End Dbm.h

The Dbm object manages private members flags, path, and db. The flags and path members can be examined with the inline member functions getFlags() and getPath(). The protected member error holds the last errno value thrown and can be examined with the inline function getError().

The member functions open(), close(), fetch(), store(), deleteKey(), firstKey(), and nextKey() are simply wrapper methods for the various NDBM routines you have learned about in this chapter. The method deleteKey() could not be named delete(), since this conflicts with the reserved C++ keyword delete.

Listing 14.4 shows the implementation of the Dbm class.

Code Listing 14.4. Dbm.cc—The Implementation of the Dbm Class
1:   // Dbm.cc
2:
3:   #include <string.h>
4:   #include <errno.h>
5:   #include "Dbm.h"
6:
7:   ////////////////////////////////////////////////////////////
8:   // Constructor :
9:   ////////////////////////////////////////////////////////////
10:
11:  Dbm::Dbm() {
12:      flags = 0;              // No flags
13:      path = 0;               // No path
14:      db = 0;                 // No database
15:      error = 0;              // No logged errors
16:  }
17:
18:  ////////////////////////////////////////////////////////////
19:  // Destructor :
20:  ////////////////////////////////////////////////////////////
21: 
22:  Dbm::~Dbm() {
23:      if ( db )
24:          close();            // Close database
25:  }
26:
27:  ////////////////////////////////////////////////////////////
28:  // Open/Create a Database :
29:  // NOTES:
30:  //  flags       O_RDWR, O_RDONLY, O_CREAT etc. (see open(2))
31:  //  mode        Permission bits
32:  ////////////////////////////////////////////////////////////
33: 
34:  Dbm &
35:  Dbm::open(const char *path,int flags,int mode) {
36: 
37:      if ( db )
38:          throw error = EPERM;    // Database already open
39:
40:      db = ::dbm_open(path,this->flags = flags,mode);
41:      if ( !db )
42:          throw error = EIO;      // Open failed
43:
44:      path = strdup(path);        // Save pathname
45: 
46:      return *this;
47:  }
48:
49:  ////////////////////////////////////////////////////////////
50:  // Close the open database :
51:  ////////////////////////////////////////////////////////////
52:
53:  Dbm &
54:  Dbm::close() {
55:
56:      if ( !db )
57:          throw error = EPERM;    // Database is not open
58:
59:      dbm_close(db);              // Close Database
60:      db = 0;
61:      delete path;                // Free pathname
62:      path = 0;
63:
64:      return *this;
65:  }
66:
67:  ////////////////////////////////////////////////////////////
68:  // Fetch data by key :
69:  ////////////////////////////////////////////////////////////
70:
71:  datum
72:  Dbm::fetch(datum key) {
73:      datum content;
74:
75:      if ( !db )
76:          throw error = EPERM;    // No database
77:
78:      content = ::dbm_fetch(db,key);
79:      if ( dbm_error(db) ) {
80:          dbm_clearerr(db);
81:          throw error = EIO;
82:      }
83:      if ( !content.dptr )
84:          throw error = ENOENT;   // Not found
85:
86:      return content;             // Found content
87:  }
88:
89:  ////////////////////////////////////////////////////////////
90:  // Replace or Insert new data by key :
91:  ////////////////////////////////////////////////////////////
92:
93:  Dbm &
94:  Dbm::store(datum key,datum content,int flags) {
95:
96:      if ( !db )
97:          throw error = EPERM;    // No database
98:
99:      if ( ::dbm_store(db,key,content,flags) < 0 ) {
100:         dbm_clearerr(db);
101:         throw error = EIO;      // Failed
102:     }
103:     return *this;
104: }
105:
106: ////////////////////////////////////////////////////////////
107: // Delete data by key :
108: ////////////////////////////////////////////////////////////
109:
110: Dbm &
111: Dbm::deleteKey(datum key) {
112:
113:     if ( !db )
114:         throw error = EPERM;    // No database
115:     if ( ::dbm_delete(db,key) < 0 ) {
116:         dbm_clearerr(db);
117:         throw error = EIO;      // Failed
118:     }
119:     return *this;
120: }
121:
122: ////////////////////////////////////////////////////////////
123: // Retrieve the first data key :
124: ////////////////////////////////////////////////////////////
125:
126: datum
127: Dbm::firstKey() {
128:     datum d;
129:
130:     if ( !db )
131:         throw error = EPERM;    // No database
132:
133:     d = ::dbm_firstkey(db);
134:
135:     if ( dbm_error(db) ) {
136:         dbm_clearerr(db);
137:         throw error = EIO;      // Database error
138:     }
139:
140:     return d;
141: }
142:
143: ////////////////////////////////////////////////////////////
144: // Retrieve the next data key :
145: ////////////////////////////////////////////////////////////
146:
147: datum
148: Dbm::nextKey() {
149:     datum d;
150:
151:     if ( !db )
152:         throw error = EPERM;    // No database
153:
154:     d = ::dbm_nextkey(db);
155:
156:     if ( dbm_error(db) ) {
157:         dbm_clearerr(db);
158:         throw error = EIO;      // Database error
159:     }
160:
161:     return d;
162: }
163:
164: // End Dbm.cc

The destructor Dbm::~Dbm() in Listing 14.4 calls upon Dbm::close() if it finds that private member db is not null. This allows the database to be closed automatically, when the Dbm object is destroyed. However, the user may call upon Dbm::close() himself. This allows him to re-use the object by calling the Dbm::open() method to open a different database.

The methods Dbm::fetch(), Dbm::store(), Dbm::deleteKey(), Dbm::firstKey(), and Dbm::nextKey() all use the datum data type in the same manner as the ndbm(3) routines. The InoDb class that inherits from Dbm will tailor the interfaces to the application, as you will see in listings later in this chapter.

Similar to the implementation of the Dir class, the Dbm class throws an error (EPERM) when the database is not open and an operation is attempted on it. Unlike the Dir class, the error thrown after a failed ndbm(3) call is always EIO. This was done because there are no documented errors given for ndbm(3) routines. Literature indicates that only dbm_error(3) can be trusted, and it is only an indication of error. The values of errno are not consistently returned for different UNIX platforms. The method Dbm::fetch() shows an example of this in lines 79–82.

The remainder of the implementation provides a wrapper around the ndbm(3) routines.

The InoDb Class

The Dbm class is a foundation class. The SnapShot database uses a device number and an i-node number as a key for each record. Furthermore, each record is simply the struct stat data type. A new class, inheriting from the Dbm class, could then provide convenient interfaces for the application involved. That is what was done with the InoDb class, which is presented in Listing 14.5.

Code Listing 14.5. InoDb.h—The InoDb Class Declaration
1:   // InoDb.h
2:
3:   #ifndef _InoDb_h_
4:   #define _InoDb_h_
5:
6:   #include <sys/types.h>
7:   #include <sys/stat.h>
8:   #include "Dbm.h"
9:
10:  ////////////////////////////////////////////////////////////
11:  // Specialized Database Class for an Inode Database :
12:  ////////////////////////////////////////////////////////////
13:
14:  class InoDb : public Dbm {
15:  public:
16:      struct Key {
17:          dev_t   st_dev;     // Device number
18:          ino_t   st_ino;     // Inode number
19:      } ;
20:  protected:
21:      Key         ikey;       // Internal key
22:  public:
23:      InoDb &fetchKey(Key &key,struct stat &sbuf);
24:      InoDb &insertKey(Key &key,struct stat &sbuf);
25:      InoDb &replaceKey(Key &key,struct stat &sbuf);
26:      InoDb &deleteKey(Key &key);
27:      Key *firstKey();
28:      Key *nextKey();
29:  };
30: 
31:  #endif _InoDb_h_
32: 
33:  // End InoDb.h

Line 14 of Listing 14.5 shows how the class InoDb inherits from the class Dbm. The type definition InoDb::Key is made publicly available in lines 15–19. A protected internal key member ikey is declared in line 21.

Lines 23–28 implement new methods that feature an API that is convenient for the application. In each case, the key is using the InoDb::Key type. Where data content is involved, a struct stat is referred to.

The implementation of the InoDb class is shown in Listing 14.6.

Code Listing 14.6. InoDb.cc—The Implementation of the InoDb Class
1:   // InoDb.cc
2:
3:   #include <errno.h>
4:   #include "InoDb.h"
5:
6:   ////////////////////////////////////////////////////////////
7:   // Fetch stat info by inode number :
8:   ////////////////////////////////////////////////////////////
9:
10:  InoDb &
11:  InoDb::fetchKey(Key &key,struct stat &sbuf) {
12:      datum d, f;
13:
14:      d.dptr = (char *)&key;
15:      d.dsize = sizeof key;
16:      f = fetch;
17:
18:      if ( f.dsize != sizeof (struct stat) )
19:          throw error = EINVAL;   // Corrupt database
20:      memcpy(&sbuf,f.dptr,sizeof sbuf);
21:
22:      return *this;
23:  }
24:
25:  ////////////////////////////////////////////////////////////
26:  // Add new stat info by inode number :
27:  ////////////////////////////////////////////////////////////
28:
29:  InoDb &
30:  InoDb::insertKey(Key &key,struct stat &sbuf) {
31:      datum k, c;
32:
33:      k.dptr = (char *)&key;
34:      k.dsize = sizeof key;
35:      c.dptr = (char *)&sbuf;
36:      c.dsize = sizeof sbuf;
37:      store(k,c,DBM_INSERT);
38:      return *this;
39:  }
40:
41:  ////////////////////////////////////////////////////////////
42:  // Replace stat info by inode number :
43:  ////////////////////////////////////////////////////////////
44:
45:  InoDb &
46:  InoDb::replaceKey(Key &key,struct stat &sbuf) {
47:      datum k, c;
48:
49:      k.dptr = (char *)&key;
50:      k.dsize = sizeof key;
51:      c.dptr = (char *)&sbuf;
52:      c.dsize = sizeof sbuf;
53:      store(k,c,DBM_REPLACE);
54:      return *this;
55:  }
56:
57:  ////////////////////////////////////////////////////////////
58:  // Delete stat info by inode number :
59:  ////////////////////////////////////////////////////////////
60:
61:  InoDb &
62:  InoDb::deleteKey(Key &key) {
63:      datum k;
64:
65:      k.dptr = (char *)&key;
66:      k.dsize = sizeof key;
67:      Dbm::deleteKey(k);
68:      return *this;
69:  }
70:
71:  ////////////////////////////////////////////////////////////
72:  // Retrieve the first key entry :
73:  ////////////////////////////////////////////////////////////
74:
75:  InoDb::Key *
76:  InoDb::firstKey() {
77:      datum k;
78:
79:      k = Dbm::firstKey();
80:      if ( !k.dptr )
81:          return 0;                   // Return NULL for EOF
82: 
83:      if ( k.dsize != sizeof ikey )
84:          throw error = EINVAL;       // Corrupt?
85:      memcpy(&ikey,k.dptr,sizeof ikey);
86:      return &ikey;                   // Return pointer to key
87:  }
88:
89:  ////////////////////////////////////////////////////////////
90:  // Retrieve the last key entry :
91:  ////////////////////////////////////////////////////////////
92:
93:  InoDb::Key *
94:  InoDb::nextKey() {
95:      datum k;
96:
97:      k = Dbm::nextKey();
98:      if ( !k.dptr )
99:          return 0;                   // Return NULL for EOF
100:
101:     if ( k.dsize != sizeof ikey )
102:         throw error = EINVAL;       // Corrupt?
103:     memcpy(&ikey,k.dptr,sizeof ikey);
104:     return &ikey;                   // Return pointer to key
105: }
106:
107: // End InoDb.cc

Much of the code presented in Listing 14.6 simply makes the application interface conform to the Dbm class interface. For example, examine the code for InoDb::fetchKey() (lines 10–23). The datum value d is prepared to point to the key (line 14) and establish the key size (line 15). Then the datum value f is set by the call to fetch() (which is actually a call to Dbm::fetch()).

Upon return from Dbm::fetch(), the size of the returned data is checked (line 18), and EINVAL is thrown if is not correct (line 19). Otherwise, the data pointed to by f.dptr is copied to the receiving struct stat buffer (line 20) that the application has provided as argument sbuf. The argument sbuf is provided by reference, so the value is passed back to the caller in this way.

The method InoDb::insertKey() is similar (lines 29–39), with the exception that the datum c is setup to provide the calling argument sbuf as input to the Dbm::store() call (line 37). Notice that the value DBM_INSERT is used in line 37, causing duplicate keys to be ignored.

The method InoDb::replaceKey() is identical to InoDb::insertKey(), with the exception that Dbm::store() is called using the value DBM_REPLACE in line 53.

The methods InoDb::firstKey() and InoDb::nextKey() return a null (Key *) value if they reach the end of the keys (lines 98 and 99). The returned key is copied to the protected internal key ikey in line 103. The address of ikey is returned in line 104.

The SnapShot Application

Listing 14.7 shows the SnapShot.cc application source listing. This listing shows how the Dir and InoDb objects are put to use.

Code Listing 14.7. SnapShot.cc—The SnapShot Application Program
1:   // SnapShot.cc
2:
3:   #include <stdio.h>
4:   #include <stdlib.h>
5:   #include <unistd.h>
6:   #include <errno.h>
7:   #include <string.h>
8:   #include <sys/types.h>
9:   #include <sys/stat.h>
10:  #include <pwd.h>
11:  #include <grp.h>
12:
13:  #include "Dir.h"
14:  #include "InoDb.h"
15:
16:  static int rc = 0;              // return code
17:  static int cmdopt_i = 0;        // -i
18:  static int cmdopt_c = 0;        // -c
19:  static int cmdopt_v = 0;        // -v
20:  static int cmdopt_h = 0;        // -h
21:
22:  ////////////////////////////////////////////////////////////
23:  // RETURN BASENAME OF A PATHNAME :
24:  ////////////////////////////////////////////////////////////
25:
26:  char *
27:  Basename(char *path) {
28:      char *bname = strrchr(path,'/'),
29:
30:      return !bname ? path : bname + 1;
31:  }
32:
33:  ////////////////////////////////////////////////////////////
34:  // COMPARE CURRENT VS PRIOR STAT(2) INFO :
35:  ////////////////////////////////////////////////////////////
36:
37:  char *
38:  Compare(struct stat is,struct stat was) {
39:      static char cmpmsg[512];    // Compare() message buffer
40:      static char dtbuf[64];      // Date time format buffer
41:      struct passwd *pw;          // /etc/password lookup
42:      struct group *gr;           // /etc/group lookup
43: 
44:      // DID THE FILE SIZE CHANGE?
45:      if ( is.st_size != was.st_size ) {
46:          sprintf(cmpmsg,"Size has changed (was %ld bytes)",
47:              (long)was.st_size);
48:          return cmpmsg;
49:      }
50:
51:      // DID THE FILE MODIFICATION TIME CHANGE?
52:      if ( is.st_mtime != was.st_mtime ) {
53:          strftime(dtbuf,sizeof dtbuf,"%x %X",localtime(&was.st_mtime));
54:          dtbuf[sizeof dtbuf-1] = 0;
55:          sprintf(cmpmsg,"Modification time has changed (was %s)",dtbuf);
56:          return cmpmsg;
57:      }
58:
59:      // DID THE FILE MODE CHANGE?
60:      if ( is.st_mode != was.st_mode ) {
61:          sprintf(cmpmsg,"File mode changed (was 0%03o)",was.st_mode);
62:          return cmpmsg;
63:      }
64:
65:      // DID THE OWNERSHIP OF THE FILE CHANGE?
66:      if ( is.st_uid != was.st_uid ) {
67:          if ( !(pw = getpwuid(was.st_uid)) )
68:              sprintf(cmpmsg,"File ownership has changed (was uid %d)",
69:                  was.st_uid);
70:          else
71:              sprintf(cmpmsg,"File ownership has changed (was %s)",
72:                  pw->pw_name);
73:          return cmpmsg;
74:      }
75:
76:      // DID THE GROUP CHANGE?
77:      if ( is.st_gid != was.st_gid ) {
78:          if ( !(gr = getgrgid(was.st_gid)) )
79:              sprintf(cmpmsg,"Group ownership changed (was gid %d)",
80:                  was.st_gid);
81:          else
82:              sprintf(cmpmsg,"Group ownership changed (was %s)",
83:                  gr->gr_name);
84:          return cmpmsg;
85:      }
86:
87:      // DID THE NUMBER OF LINKS TO THIS FILE CHANGE?
88:      if ( is.st_nlink != was.st_nlink ) {
89:          sprintf(cmpmsg,"Number of links changed (was %ld)",
90:              (long)was.st_nlink);
91:          return cmpmsg;
92:      }
93:
94:      return NULL;
95:  }
96:
97:  ////////////////////////////////////////////////////////////
98:  // UPDATE DATABASE OR CHECK AGAINST DATABASE :
99:  ////////////////////////////////////////////////////////////
100:
101: void
102: Process(InoDb &inodb,const char *fullpath,struct stat &sbuf) {
103:     struct stat pbuf;
104:     InoDb::Key key;
105:     char *msg;
106:
107:     if ( !strcmp(fullpath,"/proc") )
108:         return;             // Ignore pseudo directories
109:
110:     if ( lstat(fullpath,&sbuf) == -1 ) {
111:         fprintf(stderr,"%s: stat(%s)
",
112:             strerror(errno),fullpath);
113:         rc |= 4;            // Error, but non-fatal
114:         return;
115:     }
116:
117:     // READY THE DATABASE KEY:
118:     key.st_dev = sbuf.st_dev;
119:     key.st_ino = sbuf.st_ino;
120:
121:     if ( !cmdopt_c ) {
122:         // CREATE or UPDATE DB RECORD:
123:         inodb.replaceKey(key,sbuf);
124:     } else {
125:         // LOOKUP LAST SNAPSHOT :
126:         try {
127:             inodb.fetchKey(key,pbuf);
128:         } catch ( int e ) {
129:             if ( e == ENOENT ) {
130:                 fprintf(stderr,"New %s: %s
",
131:                     S_ISDIR(sbuf.st_mode)
132:                         ? "directory"
133:                         : "object",
134:                     fullpath);
135:                 return;
136:             } else {
137:                 fprintf(stderr,"%s: fetchKey(%s)
",
138:                     strerror(e),fullpath);
139:                 abort();       // Fatal DB error
140:             }
141:         }
142:
143:         // COMPARE CURRENT STAT VS STORED STAT INFO :
144:         msg = Compare(sbuf,pbuf);
145:         if ( msg ) {
146:             printf("%s: %s
",msg,fullpath);
147:             rc |= 8;
148:         }
149:     }
150: }
151:
152: ////////////////////////////////////////////////////////////
153: // WALK A DIRECTORY :
154: ////////////////////////////////////////////////////////////
155:
156: void
157: walk(InoDb &inodb,const char *dirname,int inclDir=0) {
158:     Dir dir;
159:     char *ent;
160:     long pathmax;
161:     struct stat sbuf;
162:
163:     // AVOID CERTAIN PSEUDO FILE SYSTEMS :
164:     if ( !strcmp(dirname,"/proc") )
165:         return;
166:
167:     if ( cmdopt_v )
168:         fprintf(stderr,"Examining: %s
",dirname);
169:
170:     // OPEN DIRECTORY :
171:     try {
172:         dir.open(dirname);
173:     } catch ( int e ) {
174:         fprintf(stderr,"%s: opening directory %s
",
175:             strerror(e),dirname);
176:         rc |= 2;
177:         return;                 // Non-fatal
178:     }
179:
180:     // INCLUDE TOP LEVEL DIRECTORIES :
181:     if ( inclDir )
182:         Process(inodb,dirname,sbuf);
183:
184:     // DETERMINE MAXIMUM PATHNAME LENGTH :
185:     if ( (pathmax = pathconf(dirname,_PC_PATH_MAX)) == -1L ) {
186:         fprintf(stderr,"%s: pathconf('%s',_PC_PATH_MAX)
",
187:             strerror(errno),dirname);
188:         abort();
189:     }
190:
191:     char fullpath[pathmax+1];   // Full pathname
192:     int bx;                     // Index to basename
193:
194:     strcpy(fullpath,dirname);
195:     bx = strlen(fullpath);
196:     if ( bx > 0 && fullpath[bx-1] != '/') {
197:         strcat(fullpath,"/");   // Append slash
198:         ++bx;                   // Adjust basename index
199:     }
200:
201:     // PROCESS ALL DIRECTORY ENTRIES:
202:     while ( (ent = dir.read()) ) {
203:         if ( !strcmp(ent,".") || !strcmp(ent,"..") )
204:             continue;           // Ignore these
205:         strcpy(fullpath+bx,ent);
206:
207:         Process(inodb,fullpath,sbuf);
208:
209:         // IF OBJECT IS A DIRECTORY, DESCEND INTO IT:
210:         if ( S_ISDIR(sbuf.st_mode) )
211:             walk(inodb,fullpath);
212:     }
213:
214:     // CLOSE DIRECTORY:
215:     dir.close();
216: }
217:
218: ////////////////////////////////////////////////////////////
219: // PROVIDE USAGE INSTRUCTIONS :
220: ////////////////////////////////////////////////////////////
221:
222: static void
223: usage(char *cmd) {
224:     char *bname = Basename(cmd);
225:
226:     printf("Usage:  %s [-c] [-i] [-v] [-h] [dir...]
",bname);
227:     puts("where:");
228:     puts("    -c      Check snapshot against file system");
229:     puts("    -i      (Re)Initialize the database");
230:     puts("    -v      Verbose");
231:     puts("    -h      Help (this info)");
232: }
233:
234: ////////////////////////////////////////////////////////////
235: // MAIN PROGRAM :
236: ////////////////////////////////////////////////////////////
237:
238: int
239: main(int argc,char **argv) {
240:     InoDb inodb;
241:     int optch;
242:     const char cmdopts[] = "hicv";
243:
244:     // PROCESS COMMAND LINE OPTIONS:
245:     while ( (optch = getopt(argc,argv,cmdopts)) != -1 )
246:         switch ( optch ) {
247:         case 'i':
248:             cmdopt_i = 1;   // -i (initialize database)
249:             break;
250:         case 'c':          // -c (check snapshot)
251:             cmdopt_c = 1;
252:             break;
253:         case 'v':
254:             cmdopt_v = 1;   // -v (verbose)
255:             break;
256:         case 'h':          // -h (give help)
257:             cmdopt_h = 1;
258:             break;
259:         default :
260:             rc = 1;
261:         }
262:
263:     if ( cmdopt_i && cmdopt_c ) {
264:         fputs("You cannot use -i and -c together
",stderr);
265:         exit(1);
266:     }
267:
268:     if ( cmdopt_h || rc ) {
269:         usage(argv[0]);
270:         exit(rc);
271:     }
272:
273:     // IF -i THEN DELETE DATABASE, TO RECREATE
274:     if ( cmdopt_i && unlink("snapshot.db") == -1 )
275:         if ( errno != ENOENT ) {
276:             fprintf(stderr,"%s: unlink(snapshot.db)
",
277:                 strerror(errno));
278:             exit(13);
279:         }
280:
281:     // OPEN EXISTING DATABASE (snapshot.db) :
282:     try {
283:         inodb.open("snapshot");
284:     } catch ( int e ) {
285:         // IF -c OPTION, DO NOT CREATE DB :
286:         if ( !cmdopt_c && e == EIO ) {
287:             // FILE NOT FOUND: CREATE DATABASE
288:             try {
289:                 inodb.open("snapshot",O_RDWR|O_CREAT);
290:             } catch ( int e ) {
291:                 fprintf(stderr,"%s: creating snapshot db
",
292:                     strerror(e));
293:                 exit(1);
294:             }
295:         } else {
296:             // REPORT DB OPEN ERROR :
297:             fprintf(stderr,"%s: creating snapshot db
",strerror(e));
298:             exit(1);
299:         }
300:     }
301:
302:     // WALK ALL DIRECTORIES GIVEN ON COMMAND LINE :
303:     for ( int x=optind; x<argc; ++x )
304:         walk(inodb,argv[x],1);
305:
306:     inodb.close();
307:
308:     return rc;
309: }
310:
311: // End SnapShot.cc

The main() program begins in line 239 of Listing 14.7. Command-line options are processed in lines 245–271. Line 274 checks to see if the option -i was present on the command line. If so, it deletes the database snapshot.db by calling unlink(2).

The database is opened in line 283. However, if the database does not exist, the error EIO will be thrown, and execution continues at line 286. If the -c option is not present on the command line, the database is created in line 289.

Once the database is open, the remaining command-line arguments are processed in lines 303 and 304. After the for loop exits, the database is closed in line 306.

The Tree Walk

The function walk() is implemented in lines 156–216. The argument inclDir in line 157 defaults to zero (false). However, when called from the main() program, the inclDir argument is true. This causes the directory dirname to be processed in addition to the directory members that it contains (lines 181 and 182).

Certain directories should not be included, and /proc is one of them. Consequently, a test is included in lines 164 and 165 to bypass /proc if it should be encountered.

The -v command-line option causes the directory being processed to be displayed on stderr (lines 167 and 168). This is useful when you want to see the progress of a lengthy operation.

The directory dirname is opened in line 172. Lines 185–191 determine the maximum pathname length and allocate a buffer named fullpath[]. Variable bx (lines 192 and 195) indicates where the basename of the pathname is in the buffer fullpath[].

Lines 202–212 form the directory-processing loop. For each entry encountered, the function Process() is invoked. Furthermore, if the object is a directory, walk() is called recursively on this new directory (lines 210 and 211).

Processing for walk() ends in line 215, where the directory is closed.

The Process() Function

The interesting database functionality exists in lines 101–150. Line 107 tests to see if the pathname in argument fullpath matches the directory /proc. If it matches, the return statement (line 108) is executed, which causes the /proc directory to be ignored.

Line 110 performs a lstat(2) call on the object fullpath. The function lstat(2) is used because you want to know if symbolic links have changed, not just the files to which they point.

The key is prepared in lines 118 and 119 to indicate the device and i-node entry. If option -c is not supplied, then the application is taking a snapshot of the file system, and the lstat(2) information is saved (line 123). The method used is InoDb::replaceKey(), since if this is run on an existing database, the record should be updated.

If the option -c is provided, then a lookup is attempted by device and i-node number in line 127 instead. If the exception is ENOENT, then the entry does not exist, and the object is reported as being a new object (lines 130–135). If the entry is found, then the present lstat(2) information in sbuf is compared to the prior lstat(2) information in pbuf. This is accomplished by calling upon the function Compare() in line 144. If Compare() returns a message pointer, then the difference is reported in lines 146 and 147. Otherwise, the function Process() exits quietly.

The Application Function Compare()

The function Compare() is implemented in lines 37–95. The lstat(2) information in variables is and was is compared in lines 45–92. The following comparison tests are made:

  • The sizes of the object (line 45)

  • The modification times (line 52)

  • The permissions of the object (line 60)

  • The ownership of the object (line 66)

  • The group ownership of the object (line 77)

  • The number of links to the object (line 88)

If no differences are found in these tests, a null pointer is returned in line 94.

Running the SnapShot Application

To compile the program, SnapShot and its companion executable EmptyDb perform the following:

$ make
cc -c  -Wall -fhandle-exceptions Dir.cc
cc -c  -Wall -fhandle-exceptions Dbm.cc
cc -c  -Wall -fhandle-exceptions InoDb.cc
cc -c  -Wall -fhandle-exceptions SnapShot.cc
cc -o SnapShot SnapShot.o Dir.o Dbm.o InoDb.o -lstdc++
cc -c  -Wall -fhandle-exceptions EmptyDb.cc
cc -o EmptyDb EmptyDb.o Dir.o Dbm.o InoDb.o -lstdc++
$

This should create the executables SnapShot and EmptyDb.

Note

On many UNIX systems, the NDBM routines are included in a separate library. For this reason, you may need to add the linking option -lndbm to link with the NDBM library.

Under FreeBSD, the NDBM functions are included in the standard C library /usr/lib/libc.so. Consequently, under FreeBSD you have no special linking requirements, since libc.so is searched by default.


You should now be able to provoke usage information from the executable SnapShot:

$ ./SnapShot -h
Usage:  SnapShot [-c] [-i] [-v] [-h] [dir...]
where:
    -c      Check snapshot against file system
    -i      (Re)Initialize the database
    -v      Verbose
    -h      Help (this info)
$

To create a SnapShot database (snapshot.db in the current directory), do not include the -c option. The -i option is used when you want to re-initialize an existing database. Perform this simple experiment:

$ ./SnapShot /tmp
$

If all went well, the program should quickly run through your /tmp directory, making notes in the database. To compare the /tmp directory against your database, enter the command

$ ./SnapShot -c /tmp
$

If you have a relatively quiet system, you'll not likely see any changes. Now make a change or two—perhaps this:

$ ls -ltr >/tmp/dummy.file
$ ./SnapShot -c /tmp
Modification time has changed (was 05/14/00 20:37:35): /tmp
New object: /tmp/dummy.file
$

Because you created a new file /tmp/dummy.file, it was not in the database. Hence, it is reported as a new file. However, note that the /tmp directory's modification time changed, and so it was reported. This tells you that a file was added, renamed, or deleted in that directory.

Now try something more adventuresome:

$ ./SnapShot -i /etc /var /tmp
Permission denied: opening directory /etc/isdn
Permission denied: opening directory /etc/uucp
Permission denied: opening directory /var/cron/tabs
Permission denied: opening directory /var/spool/opielocks
Permission denied: opening directory /var/games/hackdir
$

Since this was not run from a root account, there were some permission problems. These can be ignored for our purposes, as follows:

$ ./SnapShot -i /etc /var /tmp 2>/dev/null
$

Now keep that database for a while and test it later. After an hour of using a FreeBSD system with one user on it, the following changes were observed:

$ ./SnapShot -c /etc /var /tmp 2>/dev/null
Modification time has changed (was 05/14/00 16:15:38): /etc/ntp
Modification time has changed (was 05/14/00 16:15:38): /etc/ntp/drift
Size has changed (was 94415 bytes): /var/cron/log
Modification time has changed (was 05/14/00 02:02:03): /var/log
Modification time has changed (was 05/14/00 15:28:26): /var/log/lastlog
Size has changed (was 3784 bytes): /var/log/wtmp
Size has changed (was 359 bytes): /var/log/maillog.0.gz
Modification time has changed (was 05/14/00 15:28:35): /var/run/utmp
Modification time has changed (was 05/14/00 16:32:48): /var/tmp
$

This output shows what files had changed on the system (for the directories tested). The system that this ran on had the daemon xntpd(8) running to keep the clock synchronized. Consequently, directory /etc/ntp and file /etc/ntp/drift were updated.

Visiting All Keys and Deletion

To test the key visitation feature and the delete capability, the program EmptyDb.cc is provided in Listing 14.8.

Code Listing 14.8. EmptyDb.cc—Emptying the Database with InoDb::deleteKey()
1:   // EmptyDb.cc
2:
3:   #include <stdio.h>
4:   #include <stdlib.h>
5:   #include <unistd.h>
6:   #include <errno.h>
7:   #include <string.h>
8:   #include <sys/types.h>
9:   #include <sys/stat.h>
10:
11:  #include "InoDb.h"
12:
13:  ////////////////////////////////////////////////////////////
14:  // MAIN PROGRAM :
15:  //
16:  // If the first command line argument is the word "LIST"
17:  // the keys will be listed only. Otherwise the records
18:  // are deleted.
19:  //
20:  // This test program deletes all entries from the database
21:  // to demonstrate key traversal and delete operations.
22:  ////////////////////////////////////////////////////////////
23:
24:  int
25:  main(int argc,char **argv) {
26:      InoDb inodb;
27:      InoDb::Key *key;
28: 
29:      (void)argc;
30:      (void)argv;
31: 
32:      // OPEN EXISTING DATABASE (snapshot.db) :
33:      try {
34:          inodb.open("snapshot");
35:      } catch ( int e ) {
36:          fprintf(stderr,"%s: creating snapshot db",strerror(e));
37:          exit(1);
38:      }
39:
40:      // LIST THE KEYS ONLY :
41:      if ( argc == 2 && !strcasecmp(argv[1],"LIST") ) {
42:          for (key=inodb.firstKey(); key != NULL;
43:              key=inodb.nextKey() ) {
44:              printf("Key %d:%d from db.
",
45:                  key->st_dev,key->st_ino);
46:          }
47:          return 0;
48:      }
49:
50:      // DELETE ALL ENTRIES IN DB :
51:      while ( (key = inodb.firstKey()) != NULL ) {
52:          printf("Delete: Inode %d:%d from db.
",
53:              key->st_dev,key->st_ino);
54:          inodb.deleteKey(*key);  // DELETE ENTRY
55:      }
56: 
57:      // CLOSE DB :
58:      inodb.close();
59: 
60:      return 0;
61:  }
62:
63:  // End SnapShot.cc

Listing 14.8 simply opens the database in line 34 of the main() program (it must already exist). If the first argument on the command line is LIST, then the for loop in lines 42–46 exercise the database using InoDb::firstKey() and InoDb::nextKey(). The key values are reported in lines 44 and 45.

If no argument LIST is given, the delete loop in lines 51–55 is exercised instead. The InoDb::deleteKey() method is invoked in line 54.

Running the EmptyDb command in list mode is done as follows (with some output omitted):

$ ./EmptyDb LIST
Key 196608:142861 from db.
Key 196608:166656 from db.
Key 196608:198403 from db.
Key 196608:206340 from db.
Key 196608:63493 from db.
Key 196608:63509 from db.
Key 196608:63525 from db.
…
$

Running EmptyDb again to delete records is done as follows (with some output omitted):

$ ./EmptyDb
Delete: Inode 196608:142861 from db.
Delete: Inode 196608:166656 from db.
Delete: Inode 196608:198403 from db.
Delete: Inode 196608:206340 from db.
Delete: Inode 196608:63493 from db.
Delete: Inode 196608:63509 from db.
Delete: Inode 196608:63525 from db.
…
$

Using LIST on it now should yield no results:

$ ./EmptyDb LIST
$

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.188.160