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