Section 14.8. Directory Change Notification

   


14.8. Directory Change Notification

Applications may wish to know when the contents of a directory change. File managers, for example, may list the contents of a directory in a window and would like to keep that window up-to-date when other programs modify that directory. While the application could recheck the directory at regular intervals, Linux can send a program a signal when a directory is modified, allowing timely updates without the overhead (and delays) of polling.

The fcntl() system call is used to register for notifications of updates to a directory. Recall from Chapter 11 that this system call takes three arguments, the first is the file descriptor we are interested in, the second is the command we want fcntl() to perform, and the final one is an integer value specific to that command. For directory notifications, the first argument is a file descriptor referring to the directory of interest. This is the only case in which a directory should be opened through the normal open() system call instead of opendir(). The command to register for notifications is F_NOTIFY, and the last argument specifies what types of events cause a signal to be sent. It should be one or more of the following flags logically OR'ed together:

DN_ACCESS

A file in the directory has been read from.

DN_ATTRIB

The ownership or permissions of a file in the directory were changed.

DN_CREATE

A new file was created in the directory (this includes new hard links being made to existing files).

DN_DELETE

A file was removed from the directory.

DN_MODIFY

A file in the directory was modified (truncation is a type of modification).

DN_RENAME

A file in the directory was renamed.


To cancel event notification, call fcntl() with a command of F_NOTIFY and a final argument of zero.

Normally, directory notification is automatically canceled after a single signal has been sent. To keep directory notification in effect, the final argument to fcntl() should be OR'ed with DN_MULTISHOT, which causes signals to be sent for all appropriate events until the notification is canceled.

By default, SIGIO is sent for directory notification. If the application wants to use a different signal for this (it may want to use different signals for different directories, for example), the F_SETSIG command of fcntl() can be used, with the final argument to fcntl() specifying the signal to send for that directory. If F_SETSIG is used (even if the signal specified is SIGIO, the kernel also places the file descriptor for the directory in the si_fd member of the siginfo_t argument of the signal handler,[7] letting the application know which of the directories being monitored was updated.[8] If multiple directories are being monitored and a single signal is used for all of them, it is critical to use a real-time signal to make sure no events are lost.

[7] This is exactly the same as the method used for file leases, discussed in Chapter 13.

[8] The signal handler still must be registered with the SA_SIGINFO flag for the file descriptor to reach the signal handler properly.

Here is an example program that uses directory change notification to display messages when files are removed or added to any of the directories it is monitoring (any number of which may be specified on the command line). It registers to receive SIGRTMIN whenever a directory changes, and uses si_fd to discover which directory has been modified. To prevent any race conditions, the program uses queued signals and signal blocking. The only time a signal can be delivered is when sigsuspend() is called on line 203. This ensures that any changes to a directory that occur while that directory is being scanned force a rescan of that directory; otherwise those changes might not be noticed. Using queued signals lets any number of directory changes occur while the program is working; those signals are delivered as soon as sigsuspend() is called again, making sure nothing is forgotten.

   1: /* dirchange.c */   2:   3: #define _GNU_SOURCE   4: #include <dirent.h>   5: #include <errno.h>   6: #include <fcntl.h>   7: #include <signal.h>   8: #include <stdio.h>   9: #include <stdlib.h>  10: #include <string.h>  11: #include <unistd.h>  12:  13: /* We use a linked list to store the names of all of the files in  14:    each directory. The exists field is used for housekeeping work  15:    when we check for changes. */  16: struct fileInfo {  17:     char * name;  18:     struct fileInfo * next;  19:     int exists;  20: };  21:  22: /* This is a global array. It matches file descriptors to directory  23:    paths, stores a list of files in the directory, and gives a place  24:    for the signal handler to indicate that the directory needs to be  25:    rescanned. The last entry has a NULL path to mark the end of the  26:    array. */  27:  28: struct directoryInfo {  29:     char * path;  30:     int fd;  31:     int changed;  32:     struct fileInfo * contents;  33: } * directoryList;  34:  35: /* This will never return an empty list; all directories contain at  36:    least "." and ".." */  37: int buildDirectoryList(char * path, struct fileInfo ** listPtr) {  38:     DIR * dir;  39:     struct dirent * ent;  40:     struct fileInfo * list = NULL;  41:  42:     if (!(dir = opendir(path))) {  43:         perror("opendir");  44:         return 1;  45:     }  46:  47:     while ((ent = readdir(dir))) {  48:         if (!list) {  49:             list = malloc(sizeof(*list));  50:             list->next = NULL;  51:             *listPtr = list;  52:         } else {  53:             list->next = malloc(sizeof(*list));  54:             list = list->next;  55:         }  56:  57:         list->name = strdup(ent->d_name);  58:     }  59:  60:     if (errno) {  61:         perror("readdir");  62:         closedir(dir);  63:         return 1;  64:     }  65:  66:     closedir(dir);  67:  68:     return 0;  69: }  70:  71: /* Scans the directory path looking for changes from the previous  72:    contents, as specified by the *listPtr. The linked list is  73:    updated to reflect the new contents, and messages are printed  74:    specifying what changes have occured. */  75: int updateDirectoryList(char * path, struct fileInfo ** listPtr) {  76:     DIR * dir;  77:     struct dirent * ent;  78:     struct fileInfo * list = *listPtr;  79:     struct fileInfo * file, * prev;  80:  81:     if (!(dir = opendir(path))) {  82:         perror("opendir");  83:         return 1;  84:     }  85:  86:     for (file = list; file; file = file->next)  87:         file->exists = 0;  88:  89:     while ((ent = readdir(dir))) {  90:         file = list;  91:         while (file && strcmp(file->name, ent->d_name))  92:             file = file->next;  93:  94:         if (!file) {  95:             /* new file, add it to the list */  96:             printf("%s created in %s\n", ent->d_name, path);  97:             file = malloc(sizeof(*file));  98:             file->name = strdup(ent->d_name);  99:             file->next = list; 100:             file->exists = 1; 101:             list = file; 102:         } else { 103:             file->exists = 1; 104:         } 105:     } 106: 107:     closedir(dir); 108: 109:     file = list; 110:     prev = NULL; 111:     while (file) { 112:         if (!file->exists) { 113:             printf("%s removed from %s\n", file->name, path); 114:             free(file->name); 115: 116:             if (!prev) { 117:                 /* removing the head node */ 118:                 list = file->next; 119:                 free(file); 120:                 file = list; 121:             } else { 122:                 prev->next = file->next; 123:                 free(file); 124:                 file = prev->next; 125:             } 126:         } else { 127:             prev = file; 128:             file = file->next; 129:         } 130:     } 131: 132:     *listPtr = list; 133: 134:     return 0; 135: } 136: 137: void handler(int sig, siginfo_t * siginfo, void * context) { 138:     int i; 139: 140:     for (i = 0; directoryList[i].path; i++) { 141:         if (directoryList[i].fd == siginfo->si_fd) { 142:             directoryList[i].changed = 1; 143:             return; 144:         } 145:     } 146: } 147: 148: int main(int argc, char ** argv) { 149:     struct sigaction act; 150:     sigset_t mask, sigio; 151:     int i; 152: 153:     /* Block SIGRTMIN. We don't want to receive this anywhere but 154:        inside of the sigsuspend() system call. */ 155:     sigemptyset(&sigio); 156:     sigaddset(&sigio, SIGRTMIN); 157:     sigprocmask(SIG_BLOCK, &sigio, &mask); 158: 159:     act.sa_sigaction = handler; 160:     act.sa_flags = SA_SIGINFO; 161:     sigemptyset(&act.sa_mask); 162:     sigaction(SIGRTMIN, &act, NULL); 163: 164:     if (!argv[1]) { 165:        /* no arguments given, fix up argc/argv to look like "." was 166:             given as the only argument */ 167:        argv[1] = "."; 168:        argc++; 169:     } 170: 171:     /* each argument is a directory to watch */ 172:     directoryList = malloc(sizeof(*directoryList) * argc); 173:     directoryList[argc - 1].path = NULL; 174: 175:     for (i = 0; i < (argc - 1); i++) { 176:         directoryList[i].path = argv[i + 1]; 177:         if ((directoryList[i].fd = 178:                 open(directoryList[i].path, O_RDONLY)) < 0) { 179:             fprintf(stderr, "failed to open %s: %s\n", 180:                     directoryList[i].path, strerror(errno)); 181:             return 1; 182:         } 183: 184:         /* monitor the directory before scanning it the first time, 185:            ensuring we catch files created by someone else while 186:            we're scanning it. If someone does happen to change it, 187:            a signal will be generated (and blocked until we're 188:            ready for it) */ 189:         if (fcntl(directoryList[i].fd, F_NOTIFY, DN_DELETE | 190:                        DN_CREATE | DN_RENAME | DN_MULTISHOT)) { 191:             perror("fcntl F_NOTIFY"); 192:             return 1; 193:         } 194: 195:         fcntl(directoryList[i].fd, F_SETSIG, SIGRTMIN); 196: 197:         if (buildDirectoryList(directoryList[i].path, 198:                                &directoryList[i].contents)) 199:             return 1; 200:     } 201: 202:     while (1) { 203:         sigsuspend(&mask); 204: 205:         for (i = 0; directoryList[i].path; i++) 206:             if (directoryList[i].changed) 207:                 if (updateDirectoryList(directoryList[i].path, 208:                                    &directoryList[i].contents)) 209:                     return 1; 210:     } 211: 212:     return 0; 213: } 



       
    top
     


    Linux Application Development
    Linux Application Development (paperback) (2nd Edition)
    ISBN: 0321563220
    EAN: 2147483647
    Year: 2003
    Pages: 168

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net