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