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