1 /*  =========================================================================
2     zdir - work with file-system directories
3 
4     Copyright (c) the Contributors as noted in the AUTHORS file.
5     This file is part of CZMQ, the high-level C binding for 0MQ:
6     http://czmq.zeromq.org.
7 
8     This Source Code Form is subject to the terms of the Mozilla Public
9     License, v. 2.0. If a copy of the MPL was not distributed with this
10     file, You can obtain one at http://mozilla.org/MPL/2.0/.
11     =========================================================================*/
12 
13 /*
14 @header
15     The zdir class gives access to the file system index. It will load
16     a directory tree (a directory plus all child directories) into a
17     zdir structure and then let you navigate that structure. It exists
18     mainly to wrap non-portable OS functions to do this.
19 @discuss
20 @end
21 */
22 
23 #include "czmq_classes.h"
24 
25 //  Structure of our class
26 
27 struct _zdir_t {
28     char *path;             //  Directory name + separator
29     zlist_t *files;         //  List of files in directory
30     zlist_t *subdirs;       //  List of subdirectories
31     time_t modified;        //  Most recent file including subdirs
32     off_t cursize;          //  Total file size including subdirs
33     size_t count;           //  Total file count including subdirs
34     bool trimmed;           //  Load only top level directory
35 };
36 
37 #if (defined (WIN32))
38 static void
s_win32_populate_entry(zdir_t * self,WIN32_FIND_DATAA * entry)39 s_win32_populate_entry (zdir_t *self, WIN32_FIND_DATAA *entry)
40 {
41     if (entry->cFileName [0] == '.')
42         ; //  Skip hidden files
43     else
44     //  If we have a subdirectory, go load that
45     if (entry->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
46         if (!self->trimmed) {
47             zdir_t *subdir = zdir_new (entry->cFileName, self->path);
48             zlist_append (self->subdirs, subdir);
49         }
50     }
51     else {
52         //  Add file entry to directory list
53         zfile_t *file = zfile_new (self->path, entry->cFileName);
54         assert (file);
55         zlist_append (self->files, file);
56     }
57 }
58 
59 #else
60 static void
s_posix_populate_entry(zdir_t * self,struct dirent * entry)61 s_posix_populate_entry (zdir_t *self, struct dirent *entry)
62 {
63     //  Skip . and ..
64     if (streq (entry->d_name, ".")
65     ||  streq (entry->d_name, ".."))
66         return;
67 
68     char fullpath [1024 + 1];
69     snprintf (fullpath, 1024, "%s/%s", self->path, entry->d_name);
70     struct stat stat_buf;
71     if (stat (fullpath, &stat_buf))
72         return;
73 
74     if (entry->d_name [0] == '.')
75         ; //  Skip hidden files
76     else
77     //  If we have a subdirectory, go load that
78     if (S_ISDIR (stat_buf.st_mode)) {
79         if (!self->trimmed) {
80             zdir_t *subdir = zdir_new (entry->d_name, self->path);
81             assert (subdir);
82             zlist_append (self->subdirs, subdir);
83         }
84     }
85     else {
86         //  Add file entry to directory list
87         zfile_t *file = zfile_new (self->path, entry->d_name);
88         assert (file);
89         zlist_append (self->files, file);
90     }
91 }
92 #endif
93 
94 #ifndef WIN32
95 static pthread_mutex_t s_readdir_mutex = PTHREAD_MUTEX_INITIALIZER;
96 #endif
97 
98 //  --------------------------------------------------------------------------
99 //  Create a new directory item that loads in the full tree of the specified
100 //  path, optionally located under some parent path. If parent is "-", then
101 //  loads only the top-level directory, and does not use parent as a path.
102 
103 zdir_t *
zdir_new(const char * path,const char * parent)104 zdir_new (const char *path, const char *parent)
105 {
106     zdir_t *self = (zdir_t *) zmalloc (sizeof (zdir_t));
107     assert (self);
108 
109     if (parent) {
110         if (streq (parent, "-")) {
111             self->trimmed = true;
112             self->path = strdup (path);
113             if (!self->path) {
114                 zdir_destroy (&self);
115                 return NULL;
116             }
117         }
118         else {
119             self->path = (char *) zmalloc (strlen (path) + strlen (parent) + 2);
120             if (self->path)
121                 sprintf (self->path, "%s/%s", parent, path);
122             else {
123                 zdir_destroy (&self);
124                 return NULL;
125             }
126         }
127     }
128     else {
129         self->path = strdup (path);
130         if (!self->path) {
131             zdir_destroy (&self);
132             return NULL;
133         }
134     }
135     if (self->path)
136         self->files = zlist_new ();
137     if (self->files)
138         self->subdirs = zlist_new ();
139     if (!self->subdirs) {
140         zdir_destroy (&self);
141         return NULL;
142     }
143 
144 #if (defined (WIN32))
145     //  On Windows, replace backslashes by normal slashes
146     char *path_clean_ptr = self->path;
147     while (*path_clean_ptr) {
148         if (*path_clean_ptr == '\\')
149             *path_clean_ptr = '/';
150         path_clean_ptr++;
151     }
152     //  Remove any trailing slash
153     if (self->path [strlen (self->path) - 1] == '/')
154         self->path [strlen (self->path) - 1] = 0;
155 
156     //  Win32 wants a wildcard at the end of the path
157     char *wildcard = (char *) zmalloc (strlen (self->path) + 3);
158     if (!wildcard) {
159         zdir_destroy (&self);
160         return NULL;
161     }
162     sprintf (wildcard, "%s/*", self->path);
163     WIN32_FIND_DATAA entry;
164     HANDLE handle = FindFirstFileA (wildcard, &entry);
165     freen (wildcard);
166 
167     if (handle != INVALID_HANDLE_VALUE) {
168         //  We have read an entry, so return those values
169         s_win32_populate_entry (self, &entry);
170         while (FindNextFileA (handle, &entry))
171             s_win32_populate_entry (self, &entry);
172         FindClose (handle);
173     }
174 #else
175     //  Remove any trailing slash
176     if (self->path [strlen (self->path) - 1] == '/')
177         self->path [strlen (self->path) - 1] = 0;
178 
179     DIR *handle = opendir (self->path);
180     if (handle) {
181         // readdir_r is deprecated in glibc 2.24, but readdir is still not
182         // guaranteed to be thread safe if the same directory is accessed
183         // by different threads at the same time. Unfortunately given it was
184         // not a constraint before we cannot change it now as it would be an
185         // API breakage. Use a global lock when scanning the directory to
186         // work around it.
187         pthread_mutex_lock (&s_readdir_mutex);
188         struct dirent *entry = readdir (handle);
189         pthread_mutex_unlock (&s_readdir_mutex);
190         while (entry != NULL) {
191             // Beware of recursion. Lock only around readdir calls.
192             s_posix_populate_entry (self, entry);
193             pthread_mutex_lock (&s_readdir_mutex);
194             entry = readdir (handle);
195             pthread_mutex_unlock (&s_readdir_mutex);
196         }
197         closedir (handle);
198     }
199 #endif
200     else {
201         zdir_destroy (&self);
202         return NULL;
203     }
204     //  Update directory signatures
205     zdir_t *subdir = (zdir_t *) zlist_first (self->subdirs);
206     while (subdir) {
207         if (self->modified < subdir->modified)
208             self->modified = subdir->modified;
209         self->cursize += subdir->cursize;
210         self->count += subdir->count;
211         subdir = (zdir_t *) zlist_next (self->subdirs);
212     }
213     zfile_t *file = (zfile_t *) zlist_first (self->files);
214     while (file) {
215         if (self->modified < zfile_modified (file))
216             self->modified = zfile_modified (file);
217         self->cursize += zfile_cursize (file);
218         self->count += 1;
219         file = (zfile_t *) zlist_next (self->files);
220     }
221     return self;
222 }
223 
224 
225 //  --------------------------------------------------------------------------
226 //  Destroy a directory item
227 
228 void
zdir_destroy(zdir_t ** self_p)229 zdir_destroy (zdir_t **self_p)
230 {
231     assert (self_p);
232     if (*self_p) {
233         zdir_t *self = *self_p;
234         if (self->subdirs)
235             while (zlist_size (self->subdirs)) {
236                 zdir_t *subdir = (zdir_t *) zlist_pop (self->subdirs);
237                 zdir_destroy (&subdir);
238             }
239         if (self->files)
240             while (zlist_size (self->files)) {
241                 zfile_t *file = (zfile_t *) zlist_pop (self->files);
242                 zfile_destroy (&file);
243             }
244         zlist_destroy (&self->subdirs);
245         zlist_destroy (&self->files);
246         freen (self->path);
247         freen (self);
248         *self_p = NULL;
249     }
250 }
251 
252 
253 //  --------------------------------------------------------------------------
254 //  Return directory path
255 
256 const char *
zdir_path(zdir_t * self)257 zdir_path (zdir_t *self)
258 {
259     return self->path;
260 }
261 
262 
263 //  --------------------------------------------------------------------------
264 //  Return last modification time for directory.
265 
266 time_t
zdir_modified(zdir_t * self)267 zdir_modified (zdir_t *self)
268 {
269     assert (self);
270     return self->modified;
271 }
272 
273 
274 //  --------------------------------------------------------------------------
275 //  Return total hierarchy size, in bytes of data contained in all files
276 //  in the directory tree.
277 
278 off_t
zdir_cursize(zdir_t * self)279 zdir_cursize (zdir_t *self)
280 {
281     assert (self);
282     return self->cursize;
283 }
284 
285 
286 //  --------------------------------------------------------------------------
287 //  Return directory count
288 
289 size_t
zdir_count(zdir_t * self)290 zdir_count (zdir_t *self)
291 {
292     assert (self);
293     return self->count;
294 }
295 
296 
297 //  --------------------------------------------------------------------------
298 //  Returns a sorted array of zfile objects; returns a single block of memory,
299 //  that you destroy by calling zdir_flatten_free(). Each entry in the array
300 //  is a pointer to a zfile_t item already allocated in the zdir tree. The
301 //  array ends with a null pointer. Do not destroy the original zdir tree
302 //  until you are done with this array.
303 
304 static int  s_dir_flatten (zdir_t *self, zfile_t **files, int index);
305 static int s_dir_compare (void *item1, void *item2);
306 static int s_file_compare (void *item1, void *item2);
307 
308 zfile_t **
zdir_flatten(zdir_t * self)309 zdir_flatten (zdir_t *self)
310 {
311     size_t flat_size;
312     if (self)
313         flat_size = self->count + 1;
314     else
315         flat_size = 1;      //  Just null terminator
316 
317     zfile_t **files = (zfile_t **) zmalloc (sizeof (zfile_t *) * flat_size);
318     uint index = 0;
319     if (self)
320         index = s_dir_flatten (self, files, index);
321     return files;
322 }
323 
324 //  Flatten one directory, calls itself recursively
325 
326 static int
s_dir_flatten(zdir_t * self,zfile_t ** files,int index)327 s_dir_flatten (zdir_t *self, zfile_t **files, int index)
328 {
329     //  First flatten the normal files
330     zlist_sort (self->files, s_file_compare);
331     zfile_t *file = (zfile_t *) zlist_first (self->files);
332     while (file) {
333         files [index++] = file;
334         file = (zfile_t *) zlist_next (self->files);
335     }
336     //  Now flatten subdirectories, recursively
337     zlist_sort (self->subdirs, s_dir_compare);
338     zdir_t *subdir = (zdir_t *) zlist_first (self->subdirs);
339     while (subdir) {
340         index = s_dir_flatten (subdir, files, index);
341         subdir = (zdir_t *) zlist_next (self->subdirs);
342     }
343     return index;
344 }
345 
346 //  Compare two subdirs, true if they need swapping
347 
348 static int
s_dir_compare(void * item1,void * item2)349 s_dir_compare (void *item1, void *item2)
350 {
351     assert (item1);
352     assert (item2);
353 
354     return strcmp (zdir_path ((zdir_t *) item1),
355                    zdir_path ((zdir_t *) item2));
356 }
357 
358 //  Compare two files, true if they need swapping. We sort by ascending name.
359 
360 static int
s_file_compare(void * item1,void * item2)361 s_file_compare (void *item1, void *item2)
362 {
363     assert (item1);
364     assert (item2);
365 
366     return strcmp (zfile_filename ((zfile_t *) item1, NULL),
367                    zfile_filename ((zfile_t *) item2, NULL));
368 }
369 
370 
371 //  --------------------------------------------------------------------------
372 //  Free a provided string, and nullify the parent pointer. Safe to call on
373 //  a null pointer.
374 
375 void
zdir_flatten_free(zfile_t *** files_p)376 zdir_flatten_free (zfile_t ***files_p)
377 {
378     assert (files_p);
379     freen (*files_p);
380     *files_p = NULL;
381 }
382 
383 //  --------------------------------------------------------------------------
384 //  Returns a sorted list of zfile objects; Each entry in the list is a pointer
385 //  to a zfile_t item already allocated in the zdir tree. Do not destroy the
386 //  original zdir tree until you are done with this list.
387 
388 zlist_t *
zdir_list(zdir_t * self)389 zdir_list (zdir_t *self)
390 {
391     zfile_t **files = zdir_flatten (self);
392     zlist_t *list = zlist_new ();
393     size_t index;
394 
395     if (files)
396     {
397         for (index = 0 ; files[index] ; index++)
398         {
399             zlist_append (list, files[index]);
400         }
401     }
402 
403     zdir_flatten_free (&files);
404     return list;
405 }
406 
407 //  --------------------------------------------------------------------------
408 //  Remove directory, optionally including all files that it contains, at
409 //  all levels. If force is false, will only remove the directory if empty.
410 //  If force is true, will remove all files and all subdirectories.
411 
412 void
zdir_remove(zdir_t * self,bool force)413 zdir_remove (zdir_t *self, bool force)
414 {
415     //  If forced, remove all subdirectories and files
416     if (force) {
417         zfile_t *file = (zfile_t *) zlist_pop (self->files);
418         while (file) {
419             zfile_remove (file);
420             zfile_destroy (&file);
421             file = (zfile_t *) zlist_pop (self->files);
422         }
423         zdir_t *subdir = (zdir_t *) zlist_pop (self->subdirs);
424         while (subdir) {
425             zdir_remove (subdir, force);
426             zdir_destroy (&subdir);
427             subdir = (zdir_t *) zlist_pop (self->subdirs);
428         }
429         self->cursize = 0;
430         self->count = 0;
431     }
432     //  Remove if empty
433     if (zlist_size (self->files) == 0
434     &&  zlist_size (self->subdirs) == 0)
435         zsys_dir_delete (self->path);
436 }
437 
438 
439 //  --------------------------------------------------------------------------
440 //  Calculate differences between two versions of a directory tree.
441 //  Returns a list of zdir_patch_t patches. Either older or newer may
442 //  be null, indicating the directory is empty/absent. If alias is set,
443 //  generates virtual filename (minus path, plus alias).
444 
445 zlist_t *
zdir_diff(zdir_t * older,zdir_t * newer,const char * alias)446 zdir_diff (zdir_t *older, zdir_t *newer, const char *alias)
447 {
448     zlist_t *patches = zlist_new ();
449     if (!patches)
450         return NULL;
451 
452     zfile_t **old_files = zdir_flatten (older);
453     zfile_t **new_files = zdir_flatten (newer);
454 
455     int old_index = 0;
456     int new_index = 0;
457 
458     //  Note that both lists are sorted, so detecting differences
459     //  is rather trivial
460     while (old_files [old_index] || new_files [new_index]) {
461         zfile_t *old_file = old_files [old_index];
462         zfile_t *new_file = new_files [new_index];
463 
464         int cmp;
465         if (!old_file)
466             cmp = 1;        //  Old file was deleted at end of list
467         else
468         if (!new_file)
469             cmp = -1;       //  New file was added at end of list
470         else
471             cmp = strcmp (zfile_filename (old_file, NULL), zfile_filename (new_file, NULL));
472 
473         if (cmp > 0) {
474             //  New file was created
475             if (zfile_is_stable (new_file)) {
476                 int rc = zlist_append (patches, zdir_patch_new (newer->path, new_file, patch_create, alias));
477                 if (rc != 0) {
478                     zlist_destroy (&patches);
479                     break;
480                 }
481             }
482             old_index--;
483         }
484         else
485         if (cmp < 0) {
486             //  Old file was deleted
487             if (zfile_is_stable (old_file)) {
488                 int rc = zlist_append (patches, zdir_patch_new (older->path, old_file, patch_delete, alias));
489                 if (rc != 0) {
490                     zlist_destroy (&patches);
491                     break;
492                 }
493             }
494             new_index--;
495         }
496         else
497         if (cmp == 0 && zfile_is_stable (new_file)) {
498             if (zfile_is_stable (old_file)) {
499                 //  Old file was modified or replaced
500                 //  Since we don't check file contents, treat as created
501                 //  Could better do SHA check on file here
502                 if (zfile_modified (new_file) != zfile_modified (old_file)
503                 ||  zfile_cursize (new_file) != zfile_cursize (old_file)) {
504                     int rc = zlist_append (patches, zdir_patch_new (newer->path, new_file, patch_create, alias));
505                     if (rc != 0) {
506                         zlist_destroy (&patches);
507                         break;
508                     }
509                 }
510             }
511             else {
512                 //  File was created over some period of time
513                 int rc = zlist_append (patches, zdir_patch_new (newer->path, new_file, patch_create, alias));
514                 if (rc != 0) {
515                     zlist_destroy (&patches);
516                     break;
517                 }
518             }
519         }
520         old_index++;
521         new_index++;
522     }
523     freen (old_files);
524     freen (new_files);
525 
526     return patches;
527 }
528 
529 
530 //  --------------------------------------------------------------------------
531 //  Return full contents of directory as a patch list. If alias is set,
532 //  generates virtual filename (minus path, plus alias).
533 
534 zlist_t *
zdir_resync(zdir_t * self,const char * alias)535 zdir_resync (zdir_t *self, const char *alias)
536 {
537     zlist_t *patches = zlist_new ();
538     if (!patches)
539         return NULL;
540 
541     zfile_t **files = zdir_flatten (self);
542     uint index;
543     for (index = 0;; index++) {
544         zfile_t *file = files [index];
545         if (!file)
546             break;
547         if (zlist_append (patches, zdir_patch_new (
548             self->path, file, patch_create, alias))) {
549             zlist_destroy (&patches);
550             break;
551         }
552     }
553     freen (files);
554     return patches;
555 }
556 
557 
558 //  --------------------------------------------------------------------------
559 //  Load directory cache; returns a hash table containing the SHA-1 digests
560 //  of every file in the tree. The cache is saved between runs in .cache.
561 //  The caller must destroy the hash table when done with it.
562 
563 zhash_t *
zdir_cache(zdir_t * self)564 zdir_cache (zdir_t *self)
565 {
566     assert (self);
567 
568     //  Load any previous cache from disk
569     zhash_t *cache = zhash_new ();
570     if (!cache)
571         return NULL;
572     zhash_autofree (cache);
573     char *cache_file = (char *) zmalloc (strlen (self->path) + strlen ("/.cache") + 1);
574     if (!cache_file) {
575         zhash_destroy (&cache);
576         return NULL;
577     }
578     sprintf (cache_file, "%s/.cache", self->path);
579     zhash_load (cache, cache_file);
580 
581     //  Recalculate digest for any new files
582     zfile_t **files = zdir_flatten (self);
583     uint index;
584     for (index = 0;; index++) {
585         zfile_t *file = files [index];
586         if (!file)
587             break;
588         const char *filename = zfile_filename (file, self->path);
589         if (zhash_lookup (cache, zfile_filename (file, self->path)) == NULL) {
590             int rc = zhash_insert (cache, filename, (void *) zfile_digest (file));
591             if (rc != 0) {
592                 zhash_destroy (&cache);
593                 break;
594             }
595         }
596     }
597     freen (files);
598 
599     //  Save cache to disk for future reference
600     if (cache)
601         zhash_save (cache, cache_file);
602     freen (cache_file);
603     return cache;
604 }
605 
606 
607 //  --------------------------------------------------------------------------
608 //  Print contents of directory to open stream
609 
610 void
zdir_fprint(zdir_t * self,FILE * stream,int indent)611 zdir_fprint (zdir_t *self, FILE *stream, int indent)
612 {
613     assert (self);
614 
615     zfile_t **files = zdir_flatten (self);
616     uint index;
617     for (index = 0;; index++) {
618         zfile_t *file = files [index];
619         if (!file)
620             break;
621         fprintf (stream, "%s\n", zfile_filename (file, NULL));
622     }
623     zdir_flatten_free (&files);
624 }
625 
626 
627 //  --------------------------------------------------------------------------
628 //  Print contents of directory to stdout
629 
630 void
zdir_print(zdir_t * self,int indent)631 zdir_print (zdir_t *self, int indent)
632 {
633     zdir_fprint (self, stdout, indent);
634 }
635 
636 //  --------------------------------------------------------------------------
637 //  Watch a directory for changes
638 
639 typedef struct _zdir_watch_t {
640     zsock_t *pipe;            // actor command channel
641     zloop_t *loop;            // event reactor
642     int read_timer_id;        // the zloop timer id to signal directory updating
643 
644     bool verbose;             // extra logging to be printed
645     zhash_t *subs;            // path -> zdir_watch_sub_t instance hashtable for each active subscription
646 } zdir_watch_t;
647 
648 typedef struct _zdir_watch_sub_t {
649     zdir_t *dir;
650 } zdir_watch_sub_t;
651 
652 static int
s_on_read_timer(zloop_t * loop,int timer_id,void * arg)653 s_on_read_timer (zloop_t *loop, int timer_id, void *arg)
654 {
655     zdir_watch_t *watch = (zdir_watch_t *) arg;
656 
657     void *data;
658     for (data = zhash_first (watch->subs); data != NULL; data = zhash_next (watch->subs))
659     {
660         zdir_watch_sub_t *sub = (zdir_watch_sub_t *) data;
661 
662         zdir_t *new_dir = zdir_new (zdir_path (sub->dir), NULL);
663         if (!new_dir) {
664             if (watch->verbose)
665                 zsys_error ("zdir_watch: Unable to create new zdir for path %s", zdir_path (sub->dir));
666             continue;
667         }
668 
669         // Determine if anything has changed.
670         zlist_t *diff = zdir_diff (sub->dir, new_dir, "");
671 
672         // Do memory management before error handling...
673         zdir_destroy (&sub->dir);
674         sub->dir = new_dir;
675 
676         if (!diff) {
677             if (watch->verbose)
678                 zsys_error ("zdir_watch: Unable to create diff for path %s", zdir_path (sub->dir));
679             continue;
680         }
681 
682         if (zlist_size (diff) > 0) {
683             if (watch->verbose) {
684                 zdir_patch_t *patch = (zdir_patch_t *) zlist_first (diff);
685 
686                 zsys_info ("zdir_watch: Found %d changes in %s:", zlist_size (diff), zdir_path (sub->dir));
687                 while (patch)
688                 {
689                     zsys_info ("zdir_watch:   %s %s", zfile_filename (zdir_patch_file (patch), NULL), zdir_patch_op (patch) == ZDIR_PATCH_CREATE? "created": "deleted");
690                     patch = (zdir_patch_t *) zlist_next (diff);
691                 }
692             }
693 
694             if (zsock_send (watch->pipe, "sp", zdir_path (sub->dir), diff) != 0) {
695                 if (watch->verbose)
696                     zsys_error ("zdir_watch: Unable to send patch list for path %s", zdir_path (sub->dir));
697                 zlist_destroy (&diff);
698             }
699 
700             // Successfully sent `diff` list - now owned by receiver
701         }
702         else {
703             zlist_destroy (&diff);
704         }
705     }
706 
707     return 0;
708 }
709 
710 
711 static void
s_zdir_watch_destroy(zdir_watch_t ** watch_p)712 s_zdir_watch_destroy (zdir_watch_t **watch_p)
713 {
714     assert (watch_p);
715     if (*watch_p) {
716         zdir_watch_t *watch = *watch_p;
717 
718         zloop_destroy (&watch->loop);
719         zhash_destroy (&watch->subs);
720 
721         freen (watch);
722         *watch_p = NULL;
723     }
724 }
725 
726 static void
s_sub_free(void * data)727 s_sub_free (void *data)
728 {
729     zdir_watch_sub_t *sub = (zdir_watch_sub_t *) data;
730     zdir_destroy (&sub->dir);
731 
732     freen (sub);
733 }
734 
735 static void
s_zdir_watch_subscribe(zdir_watch_t * watch,const char * path)736 s_zdir_watch_subscribe (zdir_watch_t *watch, const char *path)
737 {
738     if (watch->verbose)
739         zsys_info ("zdir_watch: Subscribing to directory path: %s", path);
740 
741     zdir_watch_sub_t *sub = (zdir_watch_sub_t *) zmalloc (sizeof (zdir_watch_sub_t));
742     sub->dir = zdir_new (path, NULL);
743     if (!sub->dir) {
744         if (watch->verbose)
745             zsys_error ("zdir_watch: Unable to create zdir for path: %s", path);
746         zsock_signal (watch->pipe, 1);
747         return;
748     }
749 
750     int rc = zhash_insert (watch->subs, path, sub);
751     if (rc) {
752         if (watch->verbose)
753             zsys_error ("zdir_watch: Unable to insert path '%s' into subscription list", path);
754         zsock_signal (watch->pipe, 1);
755         return;
756     }
757 
758     void *item = zhash_freefn (watch->subs, path, s_sub_free);
759     if (item != sub) {
760         if (watch->verbose)
761             zsys_error ("zdir_watch: Unable to set free fn for path %s", path);
762         zsock_signal (watch->pipe, 1);
763         return;
764     }
765 
766     if (watch->verbose)
767         zsys_info ("zdir_watch: Successfully subscribed to %s", path);
768     zsock_signal (watch->pipe, 0);
769 }
770 
771 static void
s_zdir_watch_unsubscribe(zdir_watch_t * watch,const char * path)772 s_zdir_watch_unsubscribe (zdir_watch_t *watch, const char *path)
773 {
774     if (watch->verbose)
775         zsys_info ("zdir_watch: Unsubscribing from directory path: %s", path);
776 
777     zhash_delete (watch->subs, path);
778     if (watch->verbose)
779         zsys_info ("zdir_watch: Successfully unsubscribed from %s", path);
780     zsock_signal (watch->pipe, 0);
781 }
782 
783 static int
s_zdir_watch_timeout(zdir_watch_t * watch,int timeout)784 s_zdir_watch_timeout (zdir_watch_t *watch, int timeout)
785 {
786     if (watch->verbose)
787         zsys_info ("zdir_watch: Setting directory poll timeout to %d", timeout);
788 
789     if (watch->read_timer_id != -1) {
790         zloop_timer_end (watch->loop, watch->read_timer_id);
791         watch->read_timer_id = -1;
792     }
793 
794     watch->read_timer_id = zloop_timer (watch->loop, timeout, 0, s_on_read_timer, watch);
795 
796     if (watch->verbose)
797         zsys_info ("zdir_watch: Successfully set directory poll timeout to %d", timeout);
798     return 0;
799 }
800 
801 static zdir_watch_t *
s_zdir_watch_new(zsock_t * pipe)802 s_zdir_watch_new (zsock_t *pipe)
803 {
804     zdir_watch_t *watch = (zdir_watch_t *) zmalloc (sizeof (zdir_watch_t));
805     if (!watch)
806         return NULL;
807     watch->pipe = pipe;
808     watch->read_timer_id = -1;
809     watch->verbose = false;
810     return watch;
811 }
812 
813 static int
s_on_command(zloop_t * loop,zsock_t * reader,void * arg)814 s_on_command (zloop_t *loop, zsock_t *reader, void *arg)
815 {
816     zdir_watch_t *watch = (zdir_watch_t *) arg;
817 
818     zmsg_t *msg = zmsg_recv (watch->pipe);
819     assert (msg);
820     char *command = zmsg_popstr (msg);
821     assert (command);
822 
823     if (watch->verbose)
824         zsys_info ("zdir_watch: Command received: %s", command);
825 
826     if (streq (command, "$TERM")) {
827         zstr_free (&command);
828         zmsg_destroy (&msg);
829         return -1;
830     }
831     else
832     if (streq (command, "VERBOSE")) {
833         watch->verbose = true;
834         zsock_signal (watch->pipe, 0);
835     }
836     else
837     if (streq (command, "SUBSCRIBE")) {
838         char *path = zmsg_popstr (msg);
839         if (path) {
840             s_zdir_watch_subscribe (watch, path);
841             freen (path);
842         }
843         else {
844             if (watch->verbose)
845                 zsys_error ("zdir_watch: Unable to extract path from SUBSCRIBE message");
846             zsock_signal (watch->pipe, 1);
847         }
848     }
849     else
850     if (streq (command, "UNSUBSCRIBE")) {
851         char *path = zmsg_popstr (msg);
852         if (path) {
853             assert (path);
854             s_zdir_watch_unsubscribe (watch, path);
855             freen (path);
856         }
857         else {
858             if (watch->verbose)
859                 zsys_error ("zdir_watch: Unable to extract path from UNSUBSCRIBE message");
860             zsock_signal (watch->pipe, 1);
861         }
862     }
863     else
864     if (streq (command, "TIMEOUT")) {
865         char *timeout_string = zmsg_popstr (msg);
866         if (timeout_string) {
867             int timeout = atoi (timeout_string);
868             zsock_signal (watch->pipe, s_zdir_watch_timeout (watch, timeout));
869             freen (timeout_string);
870         }
871         else {
872             if (watch->verbose)
873                 zsys_error ("zdir_watch: Unable to extract time from TIMEOUT message");
874             zsock_signal (watch->pipe, 1);
875         }
876     }
877     else {
878         if (watch->verbose)
879             zsys_warning ("zdir_watch: Unknown command '%s'", command);
880         zsock_signal (watch->pipe, 1);
881     }
882 
883     freen (command);
884     zmsg_destroy (&msg);
885     return 0;
886 }
887 
888 //  --------------------------------------------------------------------------
889 //  Create a new zdir_watch actor instance
890 
891 void
zdir_watch(zsock_t * pipe,void * unused)892 zdir_watch (zsock_t *pipe, void *unused)
893 {
894     zdir_watch_t *watch = s_zdir_watch_new (pipe);
895     assert (watch);
896 
897     watch->loop = zloop_new ();
898     assert (watch->loop);
899 
900     watch->subs = zhash_new ();
901     assert (watch->subs);
902 
903     zloop_reader (watch->loop, pipe, s_on_command, watch);
904     zloop_reader_set_tolerant (watch->loop, pipe); // command pipe needs to be tolerant, otherwise we'd have a hard time shutting down
905 
906     s_zdir_watch_timeout (watch, 250); // default poll time of 250ms
907 
908     //  Signal initialization
909     zsock_signal (pipe, 0);
910 
911     // Dispatch above handlers
912     zloop_start (watch->loop);
913     if (watch->verbose)
914         zsys_info ("zdir_watch: Complete");
915 
916     // signal destruction
917     zsock_signal (watch->pipe, 0);
918 
919     // Done - cleanup and exit
920     s_zdir_watch_destroy (&watch);
921 }
922 
923 
924 //  --------------------------------------------------------------------------
925 //  Self test of this class
926 
927 void
zdir_test(bool verbose)928 zdir_test (bool verbose)
929 {
930     printf (" * zdir: ");
931 
932     //  @selftest
933 
934     const char *SELFTEST_DIR_RW = "src/selftest-rw";
935 
936     const char *testbasedir  = "zdir-test-dir";
937     const char *testfile1 = "initial_file";
938     const char *testfile2 = "test_abc";
939     char *basedirpath = NULL;   // subdir in a test, under SELFTEST_DIR_RW
940     char *filepath1 = NULL;      // pathname to testfile in a test, in dirpath
941     char *filepath2 = NULL;      // pathname to testfile in a test, in dirpath
942 
943     basedirpath = zsys_sprintf ("%s/%s", SELFTEST_DIR_RW, testbasedir);
944     assert (basedirpath);
945     filepath1 = zsys_sprintf ("%s/%s", basedirpath, testfile1);
946     assert (filepath1);
947     filepath2 = zsys_sprintf ("%s/%s", basedirpath, testfile2);
948     assert (filepath2);
949 
950 /*
951     char *relfilepath2 = NULL;      // pathname to testfile in a test, in dirpath
952     relfilepath2 = zsys_sprintf ("%s/%s", testbasedir, testfile2);
953     assert (relfilepath2);
954 */
955 
956     // Make sure old aborted tests do not hinder us
957     zdir_t *dir = zdir_new (basedirpath, NULL);
958     if (dir) {
959         zdir_remove (dir, true);
960         zdir_destroy (&dir);
961     }
962     zsys_file_delete (filepath1);
963     zsys_file_delete (filepath2);
964     zsys_dir_delete  (basedirpath);
965 
966     dir = zdir_new ("does-not-exist", NULL);
967     if (dir) {
968         zdir_remove (dir, true);
969         zdir_destroy (&dir);
970     }
971 
972     // need to create a file in the test directory we're watching
973     // in order to ensure the directory exists
974     zfile_t *initfile = zfile_new (basedirpath, testfile1);
975     assert (initfile);
976     zfile_output (initfile);
977     fprintf (zfile_handle (initfile), "initial file\n");
978     zfile_close (initfile);
979     zfile_destroy (&initfile);
980 
981     zdir_t *older = zdir_new (basedirpath, NULL);
982     assert (older);
983     if (verbose) {
984         printf ("\n");
985         zdir_dump (older, 0);
986     }
987     zdir_t *newer = zdir_new (SELFTEST_DIR_RW, NULL);
988     assert (newer);
989     zlist_t *patches = zdir_diff (older, newer, "/");
990     assert (patches);
991     while (zlist_size (patches)) {
992         zdir_patch_t *patch = (zdir_patch_t *) zlist_pop (patches);
993         zdir_patch_destroy (&patch);
994     }
995     zlist_destroy (&patches);
996     zdir_destroy (&older);
997     zdir_destroy (&newer);
998 
999     zdir_t *nosuch = zdir_new ("does-not-exist", NULL);
1000     assert (nosuch == NULL);
1001 
1002     // zdir_watch test:
1003     zactor_t *watch = zactor_new (zdir_watch, NULL);
1004     assert (watch);
1005 
1006     int synced;
1007     if (verbose) {
1008         zsock_send (watch, "s", "VERBOSE");
1009         synced = zsock_wait(watch);
1010         assert ( synced == 0);
1011     }
1012 
1013     // wait for initial file to become 'stable'
1014 #ifdef CZMQ_BUILD_DRAFT_API
1015     zclock_sleep ((int)zsys_file_stable_age_msec() + 50);
1016 #else
1017     zclock_sleep (5050);
1018 #endif
1019 
1020     zsock_send (watch, "si", "TIMEOUT", 100);
1021     synced = zsock_wait(watch);
1022     assert (synced == 0);
1023 
1024     zsock_send (watch, "ss", "SUBSCRIBE", basedirpath);
1025     synced = zsock_wait(watch);
1026     assert(synced == 0);
1027 
1028     zsock_send (watch, "ss", "UNSUBSCRIBE", basedirpath);
1029     synced = zsock_wait(watch);
1030     assert(synced == 0);
1031 
1032     zsock_send (watch, "ss", "SUBSCRIBE", basedirpath);
1033     synced = zsock_wait(watch);
1034     assert(synced == 0);
1035 
1036     zfile_t *newfile = zfile_new (basedirpath, testfile2);
1037     zfile_output (newfile);
1038     fprintf (zfile_handle (newfile), "test file\n");
1039     zfile_close (newfile);
1040 
1041     zpoller_t *watch_poll = zpoller_new (watch, NULL);
1042 
1043     // poll for a certain timeout before giving up and failing the test
1044     void* polled = NULL;
1045 #ifdef CZMQ_BUILD_DRAFT_API
1046     polled = zpoller_wait(watch_poll, (int)zsys_file_stable_age_msec() + 150);
1047 #else
1048     polled = zpoller_wait(watch_poll, 5150);
1049 #endif
1050     assert (polled == watch);
1051 
1052     // wait for notification of the file being added
1053     char *path;
1054     int rc = zsock_recv (watch, "sp", &path, &patches);
1055     assert (rc == 0);
1056 
1057     assert (streq (path, basedirpath));
1058     freen (path);
1059 
1060     if (verbose)
1061         zsys_debug("zdir_test() : added : zlist_size (patches)=%d",
1062             zlist_size (patches) );
1063     assert (zlist_size (patches) == 1);
1064 
1065     zdir_patch_t *patch = (zdir_patch_t *) zlist_pop (patches);
1066     if (verbose)
1067         zsys_debug("zdir_test() : added : zdir_patch_path (patch)='%s'",
1068             zdir_patch_path (patch) );
1069     assert (streq (zdir_patch_path (patch), basedirpath));
1070 
1071     zfile_t *patch_file = zdir_patch_file (patch);
1072     if (verbose)
1073         zsys_debug("zdir_test() : added : zfile_filename (patch_file, \"\")='%s'",
1074             zfile_filename (patch_file, "") );
1075     assert (streq (zfile_filename (patch_file, ""), filepath2));
1076 
1077     zdir_patch_destroy (&patch);
1078     zlist_destroy (&patches);
1079 
1080     // remove the file
1081     zfile_remove (newfile);
1082     zfile_destroy (&newfile);
1083 
1084     // poll for a certain timeout before giving up and failing the test.
1085 #ifdef CZMQ_BUILD_DRAFT_API
1086     polled = zpoller_wait(watch_poll, (int)zsys_file_stable_age_msec() + 150);
1087 #else
1088     polled = zpoller_wait(watch_poll, 5150);
1089 #endif
1090     assert (polled == watch);
1091 
1092     // wait for notification of the file being removed
1093     rc = zsock_recv (watch, "sp", &path, &patches);
1094     assert (rc == 0);
1095 
1096     assert (streq (path, basedirpath));
1097     freen (path);
1098 
1099     if (verbose)
1100         zsys_debug("zdir_test() : removed : zlist_size (patches)=%d",
1101             zlist_size (patches) );
1102     assert (zlist_size (patches) == 1);
1103 
1104     patch = (zdir_patch_t *) zlist_pop (patches);
1105     if (verbose)
1106         zsys_debug("zdir_test() : removed : zdir_patch_path (patch)='%s'",
1107             zdir_patch_path (patch) );
1108     assert (streq (zdir_patch_path (patch), basedirpath));
1109 
1110     patch_file = zdir_patch_file (patch);
1111     if (verbose)
1112         zsys_debug("zdir_test() : removed : zfile_filename (patch_file, \"\")='%s'",
1113             zfile_filename (patch_file, "") );
1114     assert (streq (zfile_filename (patch_file, ""), filepath2));
1115 
1116     zdir_patch_destroy (&patch);
1117     zlist_destroy (&patches);
1118 
1119     zpoller_destroy (&watch_poll);
1120     zactor_destroy (&watch);
1121 
1122     // clean up by removing the test directory.
1123     dir = zdir_new (basedirpath, NULL);
1124     assert (dir);
1125     zdir_remove (dir, true);
1126     zdir_destroy (&dir);
1127 
1128     zstr_free (&basedirpath);
1129     zstr_free (&filepath1);
1130     zstr_free (&filepath2);
1131 
1132 #if defined (__WINDOWS__)
1133     zsys_shutdown();
1134 #endif
1135     //  @end
1136 
1137     printf ("OK\n");
1138 }
1139