<NME> zdir.c
<BEF> /*  =========================================================================
    zdir - work with file-system directories

    Copyright (c) the Contributors as noted in the AUTHORS file.
    This file is part of CZMQ, the high-level C binding for 0MQ:
    http://czmq.zeromq.org.

    This Source Code Form is subject to the terms of the Mozilla Public
    License, v. 2.0. If a copy of the MPL was not distributed with this
    file, You can obtain one at http://mozilla.org/MPL/2.0/.
    =========================================================================*/

/*
@header
    The zdir class gives access to the file system index. It will load
    a directory tree (a directory plus all child directories) into a
    zdir structure and then let you navigate that structure. It exists
    mainly to wrap non-portable OS functions to do this.
@discuss
@end
*/

#include "czmq_classes.h"

//  Structure of our class

struct _zdir_t {
    char *path;             //  Directory name + separator
    zlist_t *files;         //  List of files in directory
    zlist_t *subdirs;       //  List of subdirectories
    time_t modified;        //  Most recent file including subdirs
    off_t cursize;          //  Total file size including subdirs
    size_t count;           //  Total file count including subdirs
    bool trimmed;           //  Load only top level directory
};

#if (defined (WIN32))
static void
s_win32_populate_entry (zdir_t *self, WIN32_FIND_DATAA *entry)
{
    if (entry->cFileName [0] == '.')
        ; //  Skip hidden files
    else
    //  If we have a subdirectory, go load that
    if (entry->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
        if (!self->trimmed) {
            zdir_t *subdir = zdir_new (entry->cFileName, self->path);
            zlist_append (self->subdirs, subdir);
        }
    }
    else {
        //  Add file entry to directory list
        zfile_t *file = zfile_new (self->path, entry->cFileName);
        assert (file);
        zlist_append (self->files, file);
    }
}

#else
static void
s_posix_populate_entry (zdir_t *self, struct dirent *entry)
{
    else {
        //  Add file entry to directory list
        zfile_t *file = zfile_new (self->path, entry->cFileName);
        zlist_append (self->files, file);
    }
}
    struct stat stat_buf;
    if (stat (fullpath, &stat_buf))
        return;

    if (entry->d_name [0] == '.')
        ; //  Skip hidden files
    else
    //  If we have a subdirectory, go load that
    if (S_ISDIR (stat_buf.st_mode)) {
        if (!self->trimmed) {
            zdir_t *subdir = zdir_new (entry->d_name, self->path);
            assert (subdir);
            zlist_append (self->subdirs, subdir);
        }
    }
    else {
        //  Add file entry to directory list
        zfile_t *file = zfile_new (self->path, entry->d_name);
        assert (file);
        zlist_append (self->files, file);
    if (stat_buf.st_mode & S_IFDIR) {
        if (!self->trimmed) {
            zdir_t *subdir = zdir_new (entry->d_name, self->path);
            zlist_append (self->subdirs, subdir);
        }
    }
    else {
        //  Add file entry to directory list
        zfile_t *file = zfile_new (self->path, entry->d_name);
        zlist_append (self->files, file);
    }
}
zdir_new (const char *path, const char *parent)
{
    zdir_t *self = (zdir_t *) zmalloc (sizeof (zdir_t));
    assert (self);

    if (parent) {
        if (streq (parent, "-")) {
            self->trimmed = true;
            self->path = strdup (path);
            if (!self->path) {
                zdir_destroy (&self);
                return NULL;
            }
        }
        else {
            self->path = (char *) zmalloc (strlen (path) + strlen (parent) + 2);
            if (self->path)
                sprintf (self->path, "%s/%s", parent, path);
            else {
                zdir_destroy (&self);
                return NULL;
            }
        }
    }
    else {
        self->path = strdup (path);
        if (!self->path) {
            zdir_destroy (&self);
            return NULL;
        }
    }
    if (self->path)
        self->files = zlist_new ();
    if (self->files)
        self->subdirs = zlist_new ();
    if (!self->subdirs) {
        zdir_destroy (&self);
        return NULL;
    }

#if (defined (WIN32))
    //  On Windows, replace backslashes by normal slashes
    char *path_clean_ptr = self->path;
    while (*path_clean_ptr) {
        if (*path_clean_ptr == '\\')
            *path_clean_ptr = '/';
        path_clean_ptr++;
    }
    //  Remove any trailing slash
    if (self->path [strlen (self->path) - 1] == '/')
        self->path [strlen (self->path) - 1] = 0;

    //  Win32 wants a wildcard at the end of the path
    char *wildcard = (char *) zmalloc (strlen (self->path) + 3);
    if (!wildcard) {
        zdir_destroy (&self);
        return NULL;
    }
    sprintf (wildcard, "%s/*", self->path);
    WIN32_FIND_DATAA entry;
    HANDLE handle = FindFirstFileA (wildcard, &entry);
    freen (wildcard);

    if (handle != INVALID_HANDLE_VALUE) {
        //  We have read an entry, so return those values
        s_win32_populate_entry (self, &entry);
        while (FindNextFileA (handle, &entry))
            s_win32_populate_entry (self, &entry);
        FindClose (handle);
    }
#else
    //  Remove any trailing slash
    {
        size_t l = strlen (self->path);

        while (l > 0) {
            if ( (l == 1) && self->path [0] == '/')
                // The whole path is root, skip its trimming
                break;

            if (self->path [l - 1] == '/') {
                self->path [l - 1] = '\0';
            } else {
                // Last char is not slash
                break;
            }
            l--;
        }
        assert (l > 0); // path should not be empty
    }

    DIR *handle = opendir (self->path);
    if (handle) {
        // readdir_r is deprecated in glibc 2.24, but readdir is still not
        // guaranteed to be thread safe if the same directory is accessed
        // by different threads at the same time. Unfortunately given it was
        // not a constraint before we cannot change it now as it would be an
        // API breakage. Use a global lock when scanning the directory to
        // work around it.
        pthread_mutex_lock (&s_readdir_mutex);
        struct dirent *entry = readdir (handle);
        pthread_mutex_unlock (&s_readdir_mutex);
        while (entry != NULL) {
            // Beware of recursion. Lock only around readdir calls.
            s_posix_populate_entry (self, entry);
            pthread_mutex_lock (&s_readdir_mutex);
            entry = readdir (handle);
            pthread_mutex_unlock (&s_readdir_mutex);
        }
        closedir (handle);
    }
#endif
    else {
        zdir_destroy (&self);
        return NULL;
    }
    //  Update directory signatures
    zdir_t *subdir = (zdir_t *) zlist_first (self->subdirs);
    while (subdir) {
        if (self->modified < subdir->modified)
            self->modified = subdir->modified;
        self->cursize += subdir->cursize;
        self->count += subdir->count;
        subdir = (zdir_t *) zlist_next (self->subdirs);
    }
    zfile_t *file = (zfile_t *) zlist_first (self->files);
    while (file) {
        if (self->modified < zfile_modified (file))
            self->modified = zfile_modified (file);
        self->cursize += zfile_cursize (file);
        self->count += 1;
        file = (zfile_t *) zlist_next (self->files);
    }
    return self;
}


//  --------------------------------------------------------------------------
//  Destroy a directory item

void
zdir_destroy (zdir_t **self_p)
{
    assert (self_p);
    if (*self_p) {
        zdir_t *self = *self_p;
        if (self->subdirs)
            while (zlist_size (self->subdirs)) {
                zdir_t *subdir = (zdir_t *) zlist_pop (self->subdirs);
                zdir_destroy (&subdir);
            }
        if (self->files)
            while (zlist_size (self->files)) {
                zfile_t *file = (zfile_t *) zlist_pop (self->files);
                zfile_destroy (&file);
            }
        zlist_destroy (&self->subdirs);
        zlist_destroy (&self->files);
        freen (self->path);
        freen (self);
        *self_p = NULL;
    }
}


//  --------------------------------------------------------------------------
//  Return directory path

const char *
zdir_path (zdir_t *self)
{
    return self->path;
}


//  --------------------------------------------------------------------------
//  Return last modification time for directory.

time_t
zdir_modified (zdir_t *self)
{
    assert (self);
    return self->modified;
}


//  --------------------------------------------------------------------------
//  Return total hierarchy size, in bytes of data contained in all files
//  in the directory tree.

off_t
zdir_cursize (zdir_t *self)
{
    assert (self);
    return self->cursize;
}


//  --------------------------------------------------------------------------
//  Return directory count

size_t
zdir_count (zdir_t *self)
{
    assert (self);
    return self->count;
}


//  --------------------------------------------------------------------------
//  Returns a sorted array of zfile objects; returns a single block of memory,
//  that you destroy by calling zdir_flatten_free(). Each entry in the array
//  is a pointer to a zfile_t item already allocated in the zdir tree. The
//  array ends with a null pointer. Do not destroy the original zdir tree
//  until you are done with this array.

static int  s_dir_flatten (zdir_t *self, zfile_t **files, int index);
static int s_dir_compare (void *item1, void *item2);
static int s_file_compare (void *item1, void *item2);

zfile_t **
zdir_flatten (zdir_t *self)
{
    size_t flat_size;
    if (self)
        flat_size = self->count + 1;
    else
        flat_size = 1;      //  Just null terminator

    zfile_t **files = (zfile_t **) zmalloc (sizeof (zfile_t *) * flat_size);
    uint index = 0;
    if (self)
        index = s_dir_flatten (self, files, index);
    
    //sort flattened list for proper patches computation
    zlist_t *sorted = zlist_new ();
    for (size_t i = 0; i < self->count; i++)
        zlist_append (sorted, files[i]);
    zlist_sort (sorted, s_file_compare);
    for (size_t i = 0; i < self->count; i++)
        files[i] = (zfile_t *) zlist_pop (sorted);
    zlist_destroy (&sorted);
    
    return files;
}

//  Flatten one directory, calls itself recursively

static int
s_dir_flatten (zdir_t *self, zfile_t **files, int index)
{
    //  First flatten the normal files
    zlist_sort (self->files, s_file_compare);
    zfile_t *file = (zfile_t *) zlist_first (self->files);
    while (file) {
        files [index++] = file;
        file = (zfile_t *) zlist_next (self->files);
    }
    //  Now flatten subdirectories, recursively
    zlist_sort (self->subdirs, s_dir_compare);
    zdir_t *subdir = (zdir_t *) zlist_first (self->subdirs);
    while (subdir) {
        index = s_dir_flatten (subdir, files, index);
        subdir = (zdir_t *) zlist_next (self->subdirs);
    }
    return index;
}

//  Compare two subdirs, true if they need swapping

static int
s_dir_compare (void *item1, void *item2)
{
    assert (item1);
    assert (item2);

    return strcmp (zdir_path ((zdir_t *) item1),
                   zdir_path ((zdir_t *) item2));
}

//  Compare two files, true if they need swapping. We sort by ascending name.

static int
s_file_compare (void *item1, void *item2)
{
    assert (item1);
    assert (item2);

    return strcmp (zfile_filename ((zfile_t *) item1, NULL),
                   zfile_filename ((zfile_t *) item2, NULL));
}


//  --------------------------------------------------------------------------
//  Free a provided string, and nullify the parent pointer. Safe to call on
//  a null pointer.

void
zdir_flatten_free (zfile_t ***files_p)
{
    assert (files_p);
    freen (*files_p);
zdir_diff (zdir_t *older, zdir_t *newer, const char *alias)
{
    zlist_t *patches = zlist_new ();
    zfile_t **old_files = zdir_flatten (older);
    zfile_t **new_files = zdir_flatten (newer);

zdir_list (zdir_t *self)
{
    zfile_t **files = zdir_flatten (self);
    zlist_t *list = zlist_new ();
    size_t index;

    if (files)
    {
        for (index = 0 ; files[index] ; index++)
        {
            zlist_append (list, files[index]);
        }
    }

    zdir_flatten_free (&files);
    return list;
}

        if (cmp > 0) {
            //  New file was created
            if (zfile_is_stable (new_file))
                zlist_append (patches, zdir_patch_new (newer->path, new_file, patch_create, alias));
            old_index--;
        }
        else
        if (cmp < 0) {
            //  Old file was deleted
            if (zfile_is_stable (old_file))
                zlist_append (patches, zdir_patch_new (older->path, old_file, patch_delete, alias));
            new_index--;
        }
        else
    }
    //  Remove if empty
    if (zlist_size (self->files) == 0
                //  Since we don't check file contents, treat as created
                //  Could better do SHA check on file here
                if (zfile_modified (new_file) != zfile_modified (old_file)
                ||  zfile_cursize (new_file) != zfile_cursize (old_file))
                    zlist_append (patches, zdir_patch_new (newer->path, new_file, patch_create, alias));
            }
            else
                //  File was created over some period of time
                zlist_append (patches, zdir_patch_new (newer->path, new_file, patch_create, alias));
        }
        old_index++;
        new_index++;
    int new_index = 0;

    //  Note that both lists are sorted, so detecting differences
    //  is rather trivial
    while (old_files [old_index] || new_files [new_index]) {
        zfile_t *old_file = old_files [old_index];
        zfile_t *new_file = new_files [new_index];

        int cmp;
        if (!old_file)
            cmp = 1;        //  Old file was deleted at end of list
        else
        if (!new_file)
zdir_resync (zdir_t *self, const char *alias)
{
    zlist_t *patches = zlist_new ();
    zfile_t **files = zdir_flatten (self);
    uint index;
    for (index = 0;; index++) {
        zfile_t *file = files [index];
        if (!file)
            break;
        zlist_append (patches, zdir_patch_new (self->path, file, patch_create, alias));
    }
    free (files);
    return patches;
                int rc = zlist_append (patches, zdir_patch_new (older->path, old_file, patch_delete, alias));
                if (rc != 0) {
                    zlist_destroy (&patches);
                    break;
                }
            }
            new_index--;
        }
        else
        if (cmp == 0 && zfile_is_stable (new_file)) {
            if (zfile_is_stable (old_file)) {
                //  Old file was modified or replaced

    //  Load any previous cache from disk
    zhash_t *cache = zhash_new ();
    zhash_autofree (cache);
    char *cache_file = (char *) zmalloc (strlen (self->path) + strlen ("/.cache") + 1);
    sprintf (cache_file, "%s/.cache", self->path);
    zhash_load (cache, cache_file);

                if (rc != 0) {
                    zlist_destroy (&patches);
                    break;
                }
            }
        if (!file)
            break;
        char *filename = zfile_filename (file, self->path);
        if (zhash_lookup (cache, zfile_filename (file, self->path)) == NULL)
            zhash_insert (cache, filename, zfile_digest (file));
    }
    free (files);

    //  Save cache to disk for future reference
    zhash_save (cache, cache_file);
    free (cache_file);
    return cache;
}
    if (!patches)
        return NULL;

    zfile_t **files = zdir_flatten (self);
    uint index;
    for (index = 0;; index++) {
        zfile_t *file = files [index];
        if (!file)
            break;
        if (zlist_append (patches, zdir_patch_new (
            self->path, file, patch_create, alias))) {
            zlist_destroy (&patches);
            break;
        }
    }
    freen (files);
    return patches;
}


//  --------------------------------------------------------------------------
//  Load directory cache; returns a hash table containing the SHA-1 digests
//  of every file in the tree. The cache is saved between runs in .cache.
//  The caller must destroy the hash table when done with it.

zhash_t *
zdir_cache (zdir_t *self)
{
    assert (self);

    //  Load any previous cache from disk
    zhash_t *cache = zhash_new ();
    if (!cache)
        return NULL;
    zhash_autofree (cache);
    char *cache_file = (char *) zmalloc (strlen (self->path) + strlen ("/.cache") + 1);
    if (!cache_file) {
        zhash_destroy (&cache);
        return NULL;
    }
    sprintf (cache_file, "%s/.cache", self->path);
    zhash_load (cache, cache_file);

    //  Recalculate digest for any new files
    zfile_t **files = zdir_flatten (self);
    uint index;
    for (index = 0;; index++) {
        zfile_t *file = files [index];
        if (!file)
            break;
        const char *filename = zfile_filename (file, self->path);
        if (zhash_lookup (cache, zfile_filename (file, self->path)) == NULL) {
            int rc = zhash_insert (cache, filename, (void *) zfile_digest (file));
            if (rc != 0) {
                zhash_destroy (&cache);
                break;
            }
        }
    }
    freen (files);

    //  Save cache to disk for future reference
    if (cache)
        zhash_save (cache, cache_file);
    freen (cache_file);
    return cache;
}


//  --------------------------------------------------------------------------
//  Print contents of directory to open stream

void
zdir_fprint (zdir_t *self, FILE *stream, int indent)
{
    assert (self);

    zfile_t **files = zdir_flatten (self);
    uint index;
    for (index = 0;; index++) {
        zfile_t *file = files [index];
        if (!file)
            break;
        fprintf (stream, "%s\n", zfile_filename (file, NULL));
    }
    zdir_flatten_free (&files);
}


//  --------------------------------------------------------------------------
//  Print contents of directory to stdout

void
zdir_print (zdir_t *self, int indent)
{
    zdir_fprint (self, stdout, indent);
}

//  --------------------------------------------------------------------------
//  Watch a directory for changes

typedef struct _zdir_watch_t {
    zsock_t *pipe;            // actor command channel
    zloop_t *loop;            // event reactor
    int read_timer_id;        // the zloop timer id to signal directory updating

    bool verbose;             // extra logging to be printed
    zhash_t *subs;            // path -> zdir_watch_sub_t instance hashtable for each active subscription
} zdir_watch_t;

typedef struct _zdir_watch_sub_t {
    zdir_t *dir;
} zdir_watch_sub_t;

static int
s_on_read_timer (zloop_t *loop, int timer_id, void *arg)
{
    zdir_watch_t *watch = (zdir_watch_t *) arg;

    void *data;
    for (data = zhash_first (watch->subs); data != NULL; data = zhash_next (watch->subs))
    {
        zdir_watch_sub_t *sub = (zdir_watch_sub_t *) data;

        zdir_t *new_dir = zdir_new (zdir_path (sub->dir), NULL);
        if (!new_dir) {
            if (watch->verbose)
                zsys_error ("zdir_watch: Unable to create new zdir for path %s", zdir_path (sub->dir));
            continue;
        }

        // Determine if anything has changed.
        zlist_t *diff = zdir_diff (sub->dir, new_dir, "");

        // Do memory management before error handling...
        zdir_destroy (&sub->dir);
        sub->dir = new_dir;

        if (!diff) {
            if (watch->verbose)
                zsys_error ("zdir_watch: Unable to create diff for path %s", zdir_path (sub->dir));
            continue;
        }

        if (zlist_size (diff) > 0) {
            if (watch->verbose) {
                zdir_patch_t *patch = (zdir_patch_t *) zlist_first (diff);

                zsys_info ("zdir_watch: Found %d changes in %s:", zlist_size (diff), zdir_path (sub->dir));
                while (patch)
                {
                    zsys_info ("zdir_watch:   %s %s", zfile_filename (zdir_patch_file (patch), NULL), zdir_patch_op (patch) == ZDIR_PATCH_CREATE? "created": "deleted");
                    patch = (zdir_patch_t *) zlist_next (diff);
                }
            }

            if (zsock_send (watch->pipe, "sp", zdir_path (sub->dir), diff) != 0) {
                if (watch->verbose)
                    zsys_error ("zdir_watch: Unable to send patch list for path %s", zdir_path (sub->dir));
                zlist_destroy (&diff);
            }

            // Successfully sent `diff` list - now owned by receiver
        }
        else {
            zlist_destroy (&diff);
        }
    }

    return 0;
}


static void
s_zdir_watch_destroy (zdir_watch_t **watch_p)
{
    assert (watch_p);
    if (*watch_p) {
        zdir_watch_t *watch = *watch_p;

        zloop_destroy (&watch->loop);
        zhash_destroy (&watch->subs);

        freen (watch);
        *watch_p = NULL;
    }
}

static void
s_sub_free (void *data)
{
    zdir_watch_sub_t *sub = (zdir_watch_sub_t *) data;
    zdir_destroy (&sub->dir);

    freen (sub);
}

static void
s_zdir_watch_subscribe (zdir_watch_t *watch, const char *path)
{
    if (watch->verbose)
        zsys_info ("zdir_watch: Subscribing to directory path: %s", path);

    zdir_watch_sub_t *sub = (zdir_watch_sub_t *) zmalloc (sizeof (zdir_watch_sub_t));
    sub->dir = zdir_new (path, NULL);
    if (!sub->dir) {
        if (watch->verbose)
            zsys_error ("zdir_watch: Unable to create zdir for path: %s", path);
        zsock_signal (watch->pipe, 1);
        return;
    }

    int rc = zhash_insert (watch->subs, path, sub);
    if (rc) {
        if (watch->verbose)
            zsys_error ("zdir_watch: Unable to insert path '%s' into subscription list", path);
        zsock_signal (watch->pipe, 1);
        return;
    }

    void *item = zhash_freefn (watch->subs, path, s_sub_free);
    if (item != sub) {
        if (watch->verbose)
            zsys_error ("zdir_watch: Unable to set free fn for path %s", path);
        zsock_signal (watch->pipe, 1);
        return;
    }

    if (watch->verbose)
        zsys_info ("zdir_watch: Successfully subscribed to %s", path);
    zsock_signal (watch->pipe, 0);
}

static void
s_zdir_watch_unsubscribe (zdir_watch_t *watch, const char *path)
{
    if (watch->verbose)
        zsys_info ("zdir_watch: Unsubscribing from directory path: %s", path);

    zhash_delete (watch->subs, path);
    if (watch->verbose)
        zsys_info ("zdir_watch: Successfully unsubscribed from %s", path);
    zsock_signal (watch->pipe, 0);
}

static int
s_zdir_watch_timeout (zdir_watch_t *watch, int timeout)
{
    if (watch->verbose)
        zsys_info ("zdir_watch: Setting directory poll timeout to %d", timeout);

    if (watch->read_timer_id != -1) {
        zloop_timer_end (watch->loop, watch->read_timer_id);
        watch->read_timer_id = -1;
    }

    watch->read_timer_id = zloop_timer (watch->loop, timeout, 0, s_on_read_timer, watch);

    if (watch->verbose)
        zsys_info ("zdir_watch: Successfully set directory poll timeout to %d", timeout);
    return 0;
}

static zdir_watch_t *
s_zdir_watch_new (zsock_t *pipe)
{
    zdir_watch_t *watch = (zdir_watch_t *) zmalloc (sizeof (zdir_watch_t));
    if (!watch)
        return NULL;
    watch->pipe = pipe;
    watch->read_timer_id = -1;
    watch->verbose = false;
    return watch;
}

static int
s_on_command (zloop_t *loop, zsock_t *reader, void *arg)
{
    zdir_watch_t *watch = (zdir_watch_t *) arg;

    zmsg_t *msg = zmsg_recv (watch->pipe);
    assert (msg);
    char *command = zmsg_popstr (msg);
    assert (command);

    if (watch->verbose)
        zsys_info ("zdir_watch: Command received: %s", command);

    if (streq (command, "$TERM")) {
        zstr_free (&command);
        zmsg_destroy (&msg);
        return -1;
    }
    else
    if (streq (command, "VERBOSE")) {
        watch->verbose = true;
        zsock_signal (watch->pipe, 0);
    }
    else
    if (streq (command, "SUBSCRIBE")) {
        char *path = zmsg_popstr (msg);
        if (path) {
            s_zdir_watch_subscribe (watch, path);
            freen (path);
        }
        else {
            if (watch->verbose)
                zsys_error ("zdir_watch: Unable to extract path from SUBSCRIBE message");
            zsock_signal (watch->pipe, 1);
        }
    }
    else
    if (streq (command, "UNSUBSCRIBE")) {
        char *path = zmsg_popstr (msg);
        if (path) {
            assert (path);
            s_zdir_watch_unsubscribe (watch, path);
            freen (path);
        }
        else {
            if (watch->verbose)
                zsys_error ("zdir_watch: Unable to extract path from UNSUBSCRIBE message");
            zsock_signal (watch->pipe, 1);
        }
    }
    else
    if (streq (command, "TIMEOUT")) {
        char *timeout_string = zmsg_popstr (msg);
        if (timeout_string) {
            int timeout = atoi (timeout_string);
            zsock_signal (watch->pipe, s_zdir_watch_timeout (watch, timeout));
            freen (timeout_string);
        }
        else {
            if (watch->verbose)
                zsys_error ("zdir_watch: Unable to extract time from TIMEOUT message");
            zsock_signal (watch->pipe, 1);
        }
    }
    else {
        if (watch->verbose)
            zsys_warning ("zdir_watch: Unknown command '%s'", command);
        zsock_signal (watch->pipe, 1);
    }

    freen (command);
    zmsg_destroy (&msg);
    return 0;
}

//  --------------------------------------------------------------------------
//  Create a new zdir_watch actor instance

void
zdir_watch (zsock_t *pipe, void *unused)
{
    zdir_watch_t *watch = s_zdir_watch_new (pipe);
    assert (watch);

    watch->loop = zloop_new ();
    assert (watch->loop);

    watch->subs = zhash_new ();
    assert (watch->subs);

    zloop_reader (watch->loop, pipe, s_on_command, watch);
    zloop_reader_set_tolerant (watch->loop, pipe); // command pipe needs to be tolerant, otherwise we'd have a hard time shutting down

    s_zdir_watch_timeout (watch, 250); // default poll time of 250ms

    //  Signal initialization
    zsock_signal (pipe, 0);

    // Dispatch above handlers
    zloop_start (watch->loop);
    if (watch->verbose)
        zsys_info ("zdir_watch: Complete");

    // signal destruction
    zsock_signal (watch->pipe, 0);

    // Done - cleanup and exit
    s_zdir_watch_destroy (&watch);
}


//  --------------------------------------------------------------------------
//  Self test of this class

void
zdir_test (bool verbose)
{
    printf (" * zdir: ");

    //  @selftest

    const char *SELFTEST_DIR_RW = "src/selftest-rw";

    const char *testbasedir  = "zdir-test-dir";
    const char *testfile1 = "initial_file";
    const char *testfile2 = "test_abc";
    char *basedirpath = NULL;   // subdir in a test, under SELFTEST_DIR_RW
    char *filepath1 = NULL;      // pathname to testfile in a test, in dirpath
    char *filepath2 = NULL;      // pathname to testfile in a test, in dirpath

    basedirpath = zsys_sprintf ("%s/%s", SELFTEST_DIR_RW, testbasedir);
    assert (basedirpath);
    filepath1 = zsys_sprintf ("%s/%s", basedirpath, testfile1);
    assert (filepath1);
    filepath2 = zsys_sprintf ("%s/%s", basedirpath, testfile2);
    assert (filepath2);

/*
    char *relfilepath2 = NULL;      // pathname to testfile in a test, in dirpath
    relfilepath2 = zsys_sprintf ("%s/%s", testbasedir, testfile2);
    assert (relfilepath2);
*/

    // Make sure old aborted tests do not hinder us
    zdir_t *dir = zdir_new (basedirpath, NULL);
    if (dir) {
        zdir_remove (dir, true);
        zdir_destroy (&dir);
    }
    zsys_file_delete (filepath1);
    zsys_file_delete (filepath2);
    zsys_dir_delete  (basedirpath);

    dir = zdir_new ("does-not-exist", NULL);
    if (dir) {
        zdir_remove (dir, true);
        zdir_destroy (&dir);
    }

    // need to create a file in the test directory we're watching
    // in order to ensure the directory exists
    zfile_t *initfile = zfile_new (basedirpath, testfile1);
    assert (initfile);
    zfile_output (initfile);
    fprintf (zfile_handle (initfile), "initial file\n");
    zfile_close (initfile);
    zfile_destroy (&initfile);

    zdir_t *older = zdir_new (basedirpath, NULL);
    assert (older);
    if (verbose) {
        printf ("\n");
        zdir_dump (older, 0);
    }
    zdir_t *newer = zdir_new (SELFTEST_DIR_RW, NULL);
    assert (newer);
    zlist_t *patches = zdir_diff (older, newer, "/");
    assert (patches);
    while (zlist_size (patches)) {
        zdir_patch_t *patch = (zdir_patch_t *) zlist_pop (patches);
        zdir_patch_destroy (&patch);
    }
    zlist_destroy (&patches);
    zdir_destroy (&older);
    zdir_destroy (&newer);

    zdir_t *nosuch = zdir_new ("does-not-exist", NULL);
    assert (nosuch == NULL);

    // zdir_watch test:
    zactor_t *watch = zactor_new (zdir_watch, NULL);
    assert (watch);

    int synced;
    if (verbose) {
        zsock_send (watch, "s", "VERBOSE");
        synced = zsock_wait(watch);
        assert ( synced == 0);
    }

    // wait for initial file to become 'stable'
#ifdef CZMQ_BUILD_DRAFT_API
    zclock_sleep ((int)zsys_file_stable_age_msec() + 50);
#else
    zclock_sleep (5050);
#endif

    zsock_send (watch, "si", "TIMEOUT", 100);
    synced = zsock_wait(watch);
    assert (synced == 0);

    zsock_send (watch, "ss", "SUBSCRIBE", basedirpath);
    synced = zsock_wait(watch);
    assert(synced == 0);

    zsock_send (watch, "ss", "UNSUBSCRIBE", basedirpath);
    synced = zsock_wait(watch);
    assert(synced == 0);

    zsock_send (watch, "ss", "SUBSCRIBE", basedirpath);
    synced = zsock_wait(watch);
    assert(synced == 0);

    zfile_t *newfile = zfile_new (basedirpath, testfile2);
    zfile_output (newfile);
    fprintf (zfile_handle (newfile), "test file\n");
    zfile_close (newfile);

    zpoller_t *watch_poll = zpoller_new (watch, NULL);

    // poll for a certain timeout before giving up and failing the test
    void* polled = NULL;
#ifdef CZMQ_BUILD_DRAFT_API
    polled = zpoller_wait(watch_poll, (int)zsys_file_stable_age_msec() + 150);
#else
    polled = zpoller_wait(watch_poll, 5150);
#endif
    assert (polled == watch);

    // wait for notification of the file being added
    char *path;
    int rc = zsock_recv (watch, "sp", &path, &patches);
    assert (rc == 0);

    assert (streq (path, basedirpath));
    freen (path);

    if (verbose)
        zsys_debug("zdir_test() : added : zlist_size (patches)=%d",
            zlist_size (patches) );
    assert (zlist_size (patches) == 1);

    zdir_patch_t *patch = (zdir_patch_t *) zlist_pop (patches);
    if (verbose)
        zsys_debug("zdir_test() : added : zdir_patch_path (patch)='%s'",
            zdir_patch_path (patch) );
    assert (streq (zdir_patch_path (patch), basedirpath));

    zfile_t *patch_file = zdir_patch_file (patch);
    if (verbose)
        zsys_debug("zdir_test() : added : zfile_filename (patch_file, \"\")='%s'",
            zfile_filename (patch_file, "") );
    assert (streq (zfile_filename (patch_file, ""), filepath2));

    zdir_patch_destroy (&patch);
    zlist_destroy (&patches);

    // remove the file
    zfile_remove (newfile);
    zfile_destroy (&newfile);

    // poll for a certain timeout before giving up and failing the test.
#ifdef CZMQ_BUILD_DRAFT_API
    polled = zpoller_wait(watch_poll, (int)zsys_file_stable_age_msec() + 150);
#else
    polled = zpoller_wait(watch_poll, 5150);
#endif
    assert (polled == watch);

    // wait for notification of the file being removed
    rc = zsock_recv (watch, "sp", &path, &patches);
    assert (rc == 0);

    assert (streq (path, basedirpath));
    freen (path);

    if (verbose)
        zsys_debug("zdir_test() : removed : zlist_size (patches)=%d",
            zlist_size (patches) );
    assert (zlist_size (patches) == 1);

    patch = (zdir_patch_t *) zlist_pop (patches);
    if (verbose)
        zsys_debug("zdir_test() : removed : zdir_patch_path (patch)='%s'",
            zdir_patch_path (patch) );
    assert (streq (zdir_patch_path (patch), basedirpath));

    patch_file = zdir_patch_file (patch);
    if (verbose)
        zsys_debug("zdir_test() : removed : zfile_filename (patch_file, \"\")='%s'",
            zfile_filename (patch_file, "") );
    assert (streq (zfile_filename (patch_file, ""), filepath2));

    zdir_patch_destroy (&patch);
    zlist_destroy (&patches);

    zpoller_destroy (&watch_poll);
    zactor_destroy (&watch);

    // clean up by removing the test directory.
    dir = zdir_new (basedirpath, NULL);
    assert (dir);
    zdir_remove (dir, true);
    zdir_destroy (&dir);

    zstr_free (&basedirpath);
    zstr_free (&filepath1);
    zstr_free (&filepath2);

#if defined (__WINDOWS__)
    zsys_shutdown();
#endif
    //  @end

    printf ("OK\n");
}

<MSG> zdir: check for errors

<DFF> @@ -63,6 +63,7 @@ s_win32_populate_entry (zdir_t *self, WIN32_FIND_DATAA *entry)
     else {
         //  Add file entry to directory list
         zfile_t *file = zfile_new (self->path, entry->cFileName);
+        assert (file);
         zlist_append (self->files, file);
     }
 }
@@ -89,12 +90,14 @@ s_posix_populate_entry (zdir_t *self, struct dirent *entry)
     if (stat_buf.st_mode & S_IFDIR) {
         if (!self->trimmed) {
             zdir_t *subdir = zdir_new (entry->d_name, self->path);
+            assert (subdir);
             zlist_append (self->subdirs, subdir);
         }
     }
     else {
         //  Add file entry to directory list
         zfile_t *file = zfile_new (self->path, entry->d_name);
+        assert (file);
         zlist_append (self->files, file);
     }
 }
@@ -403,6 +406,9 @@ zlist_t *
 zdir_diff (zdir_t *older, zdir_t *newer, const char *alias)
 {
     zlist_t *patches = zlist_new ();
+    if (!patches)
+        return NULL;
+
     zfile_t **old_files = zdir_flatten (older);
     zfile_t **new_files = zdir_flatten (newer);
 
@@ -426,15 +432,25 @@ zdir_diff (zdir_t *older, zdir_t *newer, const char *alias)
 
         if (cmp > 0) {
             //  New file was created
-            if (zfile_is_stable (new_file))
-                zlist_append (patches, zdir_patch_new (newer->path, new_file, patch_create, alias));
+            if (zfile_is_stable (new_file)) {
+                int rc = zlist_append (patches, zdir_patch_new (newer->path, new_file, patch_create, alias));
+                if (rc != 0) {
+                    zlist_destroy (&patches);
+                    break;
+                }
+            }
             old_index--;
         }
         else
         if (cmp < 0) {
             //  Old file was deleted
-            if (zfile_is_stable (old_file))
-                zlist_append (patches, zdir_patch_new (older->path, old_file, patch_delete, alias));
+            if (zfile_is_stable (old_file)) {
+                int rc = zlist_append (patches, zdir_patch_new (older->path, old_file, patch_delete, alias));
+                if (rc != 0) {
+                    zlist_destroy (&patches);
+                    break;
+                }
+            }
             new_index--;
         }
         else
@@ -444,12 +460,22 @@ zdir_diff (zdir_t *older, zdir_t *newer, const char *alias)
                 //  Since we don't check file contents, treat as created
                 //  Could better do SHA check on file here
                 if (zfile_modified (new_file) != zfile_modified (old_file)
-                ||  zfile_cursize (new_file) != zfile_cursize (old_file))
-                    zlist_append (patches, zdir_patch_new (newer->path, new_file, patch_create, alias));
+                ||  zfile_cursize (new_file) != zfile_cursize (old_file)) {
+                    int rc = zlist_append (patches, zdir_patch_new (newer->path, new_file, patch_create, alias));
+                    if (rc != 0) {
+                        zlist_destroy (&patches);
+                        break;
+                    }
+                }
             }
-            else
+            else {
                 //  File was created over some period of time
-                zlist_append (patches, zdir_patch_new (newer->path, new_file, patch_create, alias));
+                int rc = zlist_append (patches, zdir_patch_new (newer->path, new_file, patch_create, alias));
+                if (rc != 0) {
+                    zlist_destroy (&patches);
+                    break;
+                }
+            }
         }
         old_index++;
         new_index++;
@@ -469,13 +495,19 @@ zlist_t *
 zdir_resync (zdir_t *self, const char *alias)
 {
     zlist_t *patches = zlist_new ();
+    if (!patches)
+        return NULL;
     zfile_t **files = zdir_flatten (self);
     uint index;
     for (index = 0;; index++) {
         zfile_t *file = files [index];
         if (!file)
             break;
-        zlist_append (patches, zdir_patch_new (self->path, file, patch_create, alias));
+        int rc = zlist_append (patches, zdir_patch_new (self->path, file, patch_create, alias));
+        if (rc != 0) {
+            zlist_destroy (&patches);
+            break;
+        }
     }
     free (files);
     return patches;
@@ -494,8 +526,14 @@ zdir_cache (zdir_t *self)
 
     //  Load any previous cache from disk
     zhash_t *cache = zhash_new ();
+    if (!cache)
+        return NULL;
     zhash_autofree (cache);
     char *cache_file = (char *) zmalloc (strlen (self->path) + strlen ("/.cache") + 1);
+    if (!cache_file) {
+        zhash_destroy (&cache);
+        return NULL;
+    }
     sprintf (cache_file, "%s/.cache", self->path);
     zhash_load (cache, cache_file);
 
@@ -507,13 +545,19 @@ zdir_cache (zdir_t *self)
         if (!file)
             break;
         char *filename = zfile_filename (file, self->path);
-        if (zhash_lookup (cache, zfile_filename (file, self->path)) == NULL)
-            zhash_insert (cache, filename, zfile_digest (file));
+        if (zhash_lookup (cache, zfile_filename (file, self->path)) == NULL) {
+            int rc = zhash_insert (cache, filename, zfile_digest (file));
+            if (rc != 0) {
+                zhash_destroy (&cache);
+                break;
+            }
+        }
     }
     free (files);
 
     //  Save cache to disk for future reference
-    zhash_save (cache, cache_file);
+    if (cache)
+        zhash_save (cache, cache_file);
     free (cache_file);
     return cache;
 }

{'commit': 'diff --git a/src/zdir.c b/src/zdir.c\nindex a1070d41..f8e434c1 100644\n--- a/src/zdir.c\n+++ b/src/zdir.c\n@@ -63,6 +63,7 @@ s_win32_populate_entry (zdir_t *self, WIN32_FIND_DATAA *entry)\n     else {\n         //  Add file entry to directory list\n         zfile_t *file = zfile_new (self->path, entry->cFileName);\n+        assert (file);\n         zlist_append (self->files, file);\n     }\n }\n@@ -89,12 +90,14 @@ s_posix_populate_entry (zdir_t *self, struct dirent *entry)\n     if (stat_buf.st_mode & S_IFDIR) {\n         if (!self->trimmed) {\n             zdir_t *subdir = zdir_new (entry->d_name, self->path);\n+            assert (subdir);\n             zlist_append (self->subdirs, subdir);\n         }\n     }\n     else {\n         //  Add file entry to directory list\n         zfile_t *file = zfile_new (self->path, entry->d_name);\n+        assert (file);\n         zlist_append (self->files, file);\n     }\n }\n@@ -403,6 +406,9 @@ zlist_t *\n zdir_diff (zdir_t *older, zdir_t *newer, const char *alias)\n {\n     zlist_t *patches = zlist_new ();\n+    if (!patches)\n+        return NULL;\n+\n     zfile_t **old_files = zdir_flatten (older);\n     zfile_t **new_files = zdir_flatten (newer);\n \n@@ -426,15 +432,25 @@ zdir_diff (zdir_t *older, zdir_t *newer, const char *alias)\n \n         if (cmp > 0) {\n             //  New file was created\n-            if (zfile_is_stable (new_file))\n-                zlist_append (patches, zdir_patch_new (newer->path, new_file, patch_create, alias));\n+            if (zfile_is_stable (new_file)) {\n+                int rc = zlist_append (patches, zdir_patch_new (newer->path, new_file, patch_create, alias));\n+                if (rc != 0) {\n+                    zlist_destroy (&patches);\n+                    break;\n+                }\n+            }\n             old_index--;\n         }\n         else\n         if (cmp < 0) {\n             //  Old file was deleted\n-            if (zfile_is_stable (old_file))\n-                zlist_append (patches, zdir_patch_new (older->path, old_file, patch_delete, alias));\n+            if (zfile_is_stable (old_file)) {\n+                int rc = zlist_append (patches, zdir_patch_new (older->path, old_file, patch_delete, alias));\n+                if (rc != 0) {\n+                    zlist_destroy (&patches);\n+                    break;\n+                }\n+            }\n             new_index--;\n         }\n         else\n@@ -444,12 +460,22 @@ zdir_diff (zdir_t *older, zdir_t *newer, const char *alias)\n                 //  Since we don\'t check file contents, treat as created\n                 //  Could better do SHA check on file here\n                 if (zfile_modified (new_file) != zfile_modified (old_file)\n-                ||  zfile_cursize (new_file) != zfile_cursize (old_file))\n-                    zlist_append (patches, zdir_patch_new (newer->path, new_file, patch_create, alias));\n+                ||  zfile_cursize (new_file) != zfile_cursize (old_file)) {\n+                    int rc = zlist_append (patches, zdir_patch_new (newer->path, new_file, patch_create, alias));\n+                    if (rc != 0) {\n+                        zlist_destroy (&patches);\n+                        break;\n+                    }\n+                }\n             }\n-            else\n+            else {\n                 //  File was created over some period of time\n-                zlist_append (patches, zdir_patch_new (newer->path, new_file, patch_create, alias));\n+                int rc = zlist_append (patches, zdir_patch_new (newer->path, new_file, patch_create, alias));\n+                if (rc != 0) {\n+                    zlist_destroy (&patches);\n+                    break;\n+                }\n+            }\n         }\n         old_index++;\n         new_index++;\n@@ -469,13 +495,19 @@ zlist_t *\n zdir_resync (zdir_t *self, const char *alias)\n {\n     zlist_t *patches = zlist_new ();\n+    if (!patches)\n+        return NULL;\n     zfile_t **files = zdir_flatten (self);\n     uint index;\n     for (index = 0;; index++) {\n         zfile_t *file = files [index];\n         if (!file)\n             break;\n-        zlist_append (patches, zdir_patch_new (self->path, file, patch_create, alias));\n+        int rc = zlist_append (patches, zdir_patch_new (self->path, file, patch_create, alias));\n+        if (rc != 0) {\n+            zlist_destroy (&patches);\n+            break;\n+        }\n     }\n     free (files);\n     return patches;\n@@ -494,8 +526,14 @@ zdir_cache (zdir_t *self)\n \n     //  Load any previous cache from disk\n     zhash_t *cache = zhash_new ();\n+    if (!cache)\n+        return NULL;\n     zhash_autofree (cache);\n     char *cache_file = (char *) zmalloc (strlen (self->path) + strlen ("/.cache") + 1);\n+    if (!cache_file) {\n+        zhash_destroy (&cache);\n+        return NULL;\n+    }\n     sprintf (cache_file, "%s/.cache", self->path);\n     zhash_load (cache, cache_file);\n \n@@ -507,13 +545,19 @@ zdir_cache (zdir_t *self)\n         if (!file)\n             break;\n         char *filename = zfile_filename (file, self->path);\n-        if (zhash_lookup (cache, zfile_filename (file, self->path)) == NULL)\n-            zhash_insert (cache, filename, zfile_digest (file));\n+        if (zhash_lookup (cache, zfile_filename (file, self->path)) == NULL) {\n+            int rc = zhash_insert (cache, filename, zfile_digest (file));\n+            if (rc != 0) {\n+                zhash_destroy (&cache);\n+                break;\n+            }\n+        }\n     }\n     free (files);\n \n     //  Save cache to disk for future reference\n-    zhash_save (cache, cache_file);\n+    if (cache)\n+        zhash_save (cache, cache_file);\n     free (cache_file);\n     return cache;\n }\n', 'commit_subject': 'zdir: check for errors', 'repo_name': 'zeromq/czmq', 'committer': {'name': 'Goswin von Brederlow', 'email': '571dbbcb8f732699f5c5afd31551329d6133029d@q-leap.de', 'time_sec': '1411146735', 'tz_offset': '120', 'date': {'seconds': '1411146735'}}, 'commit_tree': '2ceca989cb362c02e95c20bdcdcf45d007f67e5f', 'commit_message': 'zdir: check for errors\n', 'language_name': 'C++', 'license': 'mpl-2.0', 'hunks': '@@ -63,6 +63,7 @@ s_win32_populate_entry (zdir_t *self, WIN32_FIND_DATAA *entry)\n     else {\n         //  Add file entry to directory list\n         zfile_t *file = zfile_new (self->path, entry->cFileName);\n+        assert (file);\n         zlist_append (self->files, file);\n     }\n }\n@@ -89,12 +90,14 @@ s_posix_populate_entry (zdir_t *self, struct dirent *entry)\n     if (stat_buf.st_mode & S_IFDIR) {\n         if (!self->trimmed) {\n             zdir_t *subdir = zdir_new (entry->d_name, self->path);\n+            assert (subdir);\n             zlist_append (self->subdirs, subdir);\n         }\n     }\n     else {\n         //  Add file entry to directory list\n         zfile_t *file = zfile_new (self->path, entry->d_name);\n+        assert (file);\n         zlist_append (self->files, file);\n     }\n }\n@@ -403,6 +406,9 @@ zlist_t *\n zdir_diff (zdir_t *older, zdir_t *newer, const char *alias)\n {\n     zlist_t *patches = zlist_new ();\n+    if (!patches)\n+        return NULL;\n+\n     zfile_t **old_files = zdir_flatten (older);\n     zfile_t **new_files = zdir_flatten (newer);\n \n@@ -426,15 +432,25 @@ zdir_diff (zdir_t *older, zdir_t *newer, const char *alias)\n \n         if (cmp > 0) {\n             //  New file was created\n-            if (zfile_is_stable (new_file))\n-                zlist_append (patches, zdir_patch_new (newer->path, new_file, patch_create, alias));\n+            if (zfile_is_stable (new_file)) {\n+                int rc = zlist_append (patches, zdir_patch_new (newer->path, new_file, patch_create, alias));\n+                if (rc != 0) {\n+                    zlist_destroy (&patches);\n+                    break;\n+                }\n+            }\n             old_index--;\n         }\n         else\n         if (cmp < 0) {\n             //  Old file was deleted\n-            if (zfile_is_stable (old_file))\n-                zlist_append (patches, zdir_patch_new (older->path, old_file, patch_delete, alias));\n+            if (zfile_is_stable (old_file)) {\n+                int rc = zlist_append (patches, zdir_patch_new (older->path, old_file, patch_delete, alias));\n+                if (rc != 0) {\n+                    zlist_destroy (&patches);\n+                    break;\n+                }\n+            }\n             new_index--;\n         }\n         else\n@@ -444,12 +460,22 @@ zdir_diff (zdir_t *older, zdir_t *newer, const char *alias)\n                 //  Since we don\'t check file contents, treat as created\n                 //  Could better do SHA check on file here\n                 if (zfile_modified (new_file) != zfile_modified (old_file)\n-                ||  zfile_cursize (new_file) != zfile_cursize (old_file))\n-                    zlist_append (patches, zdir_patch_new (newer->path, new_file, patch_create, alias));\n+                ||  zfile_cursize (new_file) != zfile_cursize (old_file)) {\n+                    int rc = zlist_append (patches, zdir_patch_new (newer->path, new_file, patch_create, alias));\n+                    if (rc != 0) {\n+                        zlist_destroy (&patches);\n+                        break;\n+                    }\n+                }\n             }\n-            else\n+            else {\n                 //  File was created over some period of time\n-                zlist_append (patches, zdir_patch_new (newer->path, new_file, patch_create, alias));\n+                int rc = zlist_append (patches, zdir_patch_new (newer->path, new_file, patch_create, alias));\n+                if (rc != 0) {\n+                    zlist_destroy (&patches);\n+                    break;\n+                }\n+            }\n         }\n         old_index++;\n         new_index++;\n@@ -469,13 +495,19 @@ zlist_t *\n zdir_resync (zdir_t *self, const char *alias)\n {\n     zlist_t *patches = zlist_new ();\n+    if (!patches)\n+        return NULL;\n     zfile_t **files = zdir_flatten (self);\n     uint index;\n     for (index = 0;; index++) {\n         zfile_t *file = files [index];\n         if (!file)\n             break;\n-        zlist_append (patches, zdir_patch_new (self->path, file, patch_create, alias));\n+        int rc = zlist_append (patches, zdir_patch_new (self->path, file, patch_create, alias));\n+        if (rc != 0) {\n+            zlist_destroy (&patches);\n+            break;\n+        }\n     }\n     free (files);\n     return patches;\n@@ -494,8 +526,14 @@ zdir_cache (zdir_t *self)\n \n     //  Load any previous cache from disk\n     zhash_t *cache = zhash_new ();\n+    if (!cache)\n+        return NULL;\n     zhash_autofree (cache);\n     char *cache_file = (char *) zmalloc (strlen (self->path) + strlen ("/.cache") + 1);\n+    if (!cache_file) {\n+        zhash_destroy (&cache);\n+        return NULL;\n+    }\n     sprintf (cache_file, "%s/.cache", self->path);\n     zhash_load (cache, cache_file);\n \n@@ -507,13 +545,19 @@ zdir_cache (zdir_t *self)\n         if (!file)\n             break;\n         char *filename = zfile_filename (file, self->path);\n-        if (zhash_lookup (cache, zfile_filename (file, self->path)) == NULL)\n-            zhash_insert (cache, filename, zfile_digest (file));\n+        if (zhash_lookup (cache, zfile_filename (file, self->path)) == NULL) {\n+            int rc = zhash_insert (cache, filename, zfile_digest (file));\n+            if (rc != 0) {\n+                zhash_destroy (&cache);\n+                break;\n+            }\n+        }\n     }\n     free (files);\n \n     //  Save cache to disk for future reference\n-    zhash_save (cache, cache_file);\n+    if (cache)\n+        zhash_save (cache, cache_file);\n     free (cache_file);\n     return cache;\n }\n', 'addition_count': 56, 'deletion_count': 12, 'src_file': 'a/src/zdir.c', 'tgt_file': 'b/src/zdir.c', 'file_extension': '.c', 'before_file': '/*  =========================================================================\n    zdir - work with file-system directories\n\n    Copyright (c) the Contributors as noted in the AUTHORS file.\n    This file is part of CZMQ, the high-level C binding for 0MQ:\n    http://czmq.zeromq.org.\n\n    This Source Code Form is subject to the terms of the Mozilla Public\n    License, v. 2.0. If a copy of the MPL was not distributed with this\n    file, You can obtain one at http://mozilla.org/MPL/2.0/.\n    =========================================================================*/\n\n/*\n@header\n    The zdir class gives access to the file system index. It will load\n    a directory tree (a directory plus all child directories) into a\n    zdir structure and then let you navigate that structure. It exists\n    mainly to wrap non-portable OS functions to do this.\n@discuss\n@end\n*/\n\n#include "czmq_classes.h"\n\n//  Structure of our class\n\nstruct _zdir_t {\n    char *path;             //  Directory name + separator\n    zlist_t *files;         //  List of files in directory\n    zlist_t *subdirs;       //  List of subdirectories\n    time_t modified;        //  Most recent file including subdirs\n    off_t cursize;          //  Total file size including subdirs\n    size_t count;           //  Total file count including subdirs\n    bool trimmed;           //  Load only top level directory\n};\n\n#if (defined (WIN32))\nstatic void\ns_win32_populate_entry (zdir_t *self, WIN32_FIND_DATAA *entry)\n{\n    if (entry->cFileName [0] == \'.\')\n        ; //  Skip hidden files\n    else\n    //  If we have a subdirectory, go load that\n    if (entry->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {\n        if (!self->trimmed) {\n            zdir_t *subdir = zdir_new (entry->cFileName, self->path);\n            zlist_append (self->subdirs, subdir);\n        }\n    }\n    else {\n        //  Add file entry to directory list\n        zfile_t *file = zfile_new (self->path, entry->cFileName);\n        assert (file);\n        zlist_append (self->files, file);\n    }\n}\n\n#else\nstatic void\ns_posix_populate_entry (zdir_t *self, struct dirent *entry)\n{\n    else {\n        //  Add file entry to directory list\n        zfile_t *file = zfile_new (self->path, entry->cFileName);\n        zlist_append (self->files, file);\n    }\n}\n    struct stat stat_buf;\n    if (stat (fullpath, &stat_buf))\n        return;\n\n    if (entry->d_name [0] == \'.\')\n        ; //  Skip hidden files\n    else\n    //  If we have a subdirectory, go load that\n    if (S_ISDIR (stat_buf.st_mode)) {\n        if (!self->trimmed) {\n            zdir_t *subdir = zdir_new (entry->d_name, self->path);\n            assert (subdir);\n            zlist_append (self->subdirs, subdir);\n        }\n    }\n    else {\n        //  Add file entry to directory list\n        zfile_t *file = zfile_new (self->path, entry->d_name);\n        assert (file);\n        zlist_append (self->files, file);\n    if (stat_buf.st_mode & S_IFDIR) {\n        if (!self->trimmed) {\n            zdir_t *subdir = zdir_new (entry->d_name, self->path);\n            zlist_append (self->subdirs, subdir);\n        }\n    }\n    else {\n        //  Add file entry to directory list\n        zfile_t *file = zfile_new (self->path, entry->d_name);\n        zlist_append (self->files, file);\n    }\n}\nzdir_new (const char *path, const char *parent)\n{\n    zdir_t *self = (zdir_t *) zmalloc (sizeof (zdir_t));\n    assert (self);\n\n    if (parent) {\n        if (streq (parent, "-")) {\n            self->trimmed = true;\n            self->path = strdup (path);\n            if (!self->path) {\n                zdir_destroy (&self);\n                return NULL;\n            }\n        }\n        else {\n            self->path = (char *) zmalloc (strlen (path) + strlen (parent) + 2);\n            if (self->path)\n                sprintf (self->path, "%s/%s", parent, path);\n            else {\n                zdir_destroy (&self);\n                return NULL;\n            }\n        }\n    }\n    else {\n        self->path = strdup (path);\n        if (!self->path) {\n            zdir_destroy (&self);\n            return NULL;\n        }\n    }\n    if (self->path)\n        self->files = zlist_new ();\n    if (self->files)\n        self->subdirs = zlist_new ();\n    if (!self->subdirs) {\n        zdir_destroy (&self);\n        return NULL;\n    }\n\n#if (defined (WIN32))\n    //  On Windows, replace backslashes by normal slashes\n    char *path_clean_ptr = self->path;\n    while (*path_clean_ptr) {\n        if (*path_clean_ptr == \'\\\\\')\n            *path_clean_ptr = \'/\';\n        path_clean_ptr++;\n    }\n    //  Remove any trailing slash\n    if (self->path [strlen (self->path) - 1] == \'/\')\n        self->path [strlen (self->path) - 1] = 0;\n\n    //  Win32 wants a wildcard at the end of the path\n    char *wildcard = (char *) zmalloc (strlen (self->path) + 3);\n    if (!wildcard) {\n        zdir_destroy (&self);\n        return NULL;\n    }\n    sprintf (wildcard, "%s/*", self->path);\n    WIN32_FIND_DATAA entry;\n    HANDLE handle = FindFirstFileA (wildcard, &entry);\n    freen (wildcard);\n\n    if (handle != INVALID_HANDLE_VALUE) {\n        //  We have read an entry, so return those values\n        s_win32_populate_entry (self, &entry);\n        while (FindNextFileA (handle, &entry))\n            s_win32_populate_entry (self, &entry);\n        FindClose (handle);\n    }\n#else\n    //  Remove any trailing slash\n    {\n        size_t l = strlen (self->path);\n\n        while (l > 0) {\n            if ( (l == 1) && self->path [0] == \'/\')\n                // The whole path is root, skip its trimming\n                break;\n\n            if (self->path [l - 1] == \'/\') {\n                self->path [l - 1] = \'\\0\';\n            } else {\n                // Last char is not slash\n                break;\n            }\n            l--;\n        }\n        assert (l > 0); // path should not be empty\n    }\n\n    DIR *handle = opendir (self->path);\n    if (handle) {\n        // readdir_r is deprecated in glibc 2.24, but readdir is still not\n        // guaranteed to be thread safe if the same directory is accessed\n        // by different threads at the same time. Unfortunately given it was\n        // not a constraint before we cannot change it now as it would be an\n        // API breakage. Use a global lock when scanning the directory to\n        // work around it.\n        pthread_mutex_lock (&s_readdir_mutex);\n        struct dirent *entry = readdir (handle);\n        pthread_mutex_unlock (&s_readdir_mutex);\n        while (entry != NULL) {\n            // Beware of recursion. Lock only around readdir calls.\n            s_posix_populate_entry (self, entry);\n            pthread_mutex_lock (&s_readdir_mutex);\n            entry = readdir (handle);\n            pthread_mutex_unlock (&s_readdir_mutex);\n        }\n        closedir (handle);\n    }\n#endif\n    else {\n        zdir_destroy (&self);\n        return NULL;\n    }\n    //  Update directory signatures\n    zdir_t *subdir = (zdir_t *) zlist_first (self->subdirs);\n    while (subdir) {\n        if (self->modified < subdir->modified)\n            self->modified = subdir->modified;\n        self->cursize += subdir->cursize;\n        self->count += subdir->count;\n        subdir = (zdir_t *) zlist_next (self->subdirs);\n    }\n    zfile_t *file = (zfile_t *) zlist_first (self->files);\n    while (file) {\n        if (self->modified < zfile_modified (file))\n            self->modified = zfile_modified (file);\n        self->cursize += zfile_cursize (file);\n        self->count += 1;\n        file = (zfile_t *) zlist_next (self->files);\n    }\n    return self;\n}\n\n\n//  --------------------------------------------------------------------------\n//  Destroy a directory item\n\nvoid\nzdir_destroy (zdir_t **self_p)\n{\n    assert (self_p);\n    if (*self_p) {\n        zdir_t *self = *self_p;\n        if (self->subdirs)\n            while (zlist_size (self->subdirs)) {\n                zdir_t *subdir = (zdir_t *) zlist_pop (self->subdirs);\n                zdir_destroy (&subdir);\n            }\n        if (self->files)\n            while (zlist_size (self->files)) {\n                zfile_t *file = (zfile_t *) zlist_pop (self->files);\n                zfile_destroy (&file);\n            }\n        zlist_destroy (&self->subdirs);\n        zlist_destroy (&self->files);\n        freen (self->path);\n        freen (self);\n        *self_p = NULL;\n    }\n}\n\n\n//  --------------------------------------------------------------------------\n//  Return directory path\n\nconst char *\nzdir_path (zdir_t *self)\n{\n    return self->path;\n}\n\n\n//  --------------------------------------------------------------------------\n//  Return last modification time for directory.\n\ntime_t\nzdir_modified (zdir_t *self)\n{\n    assert (self);\n    return self->modified;\n}\n\n\n//  --------------------------------------------------------------------------\n//  Return total hierarchy size, in bytes of data contained in all files\n//  in the directory tree.\n\noff_t\nzdir_cursize (zdir_t *self)\n{\n    assert (self);\n    return self->cursize;\n}\n\n\n//  --------------------------------------------------------------------------\n//  Return directory count\n\nsize_t\nzdir_count (zdir_t *self)\n{\n    assert (self);\n    return self->count;\n}\n\n\n//  --------------------------------------------------------------------------\n//  Returns a sorted array of zfile objects; returns a single block of memory,\n//  that you destroy by calling zdir_flatten_free(). Each entry in the array\n//  is a pointer to a zfile_t item already allocated in the zdir tree. The\n//  array ends with a null pointer. Do not destroy the original zdir tree\n//  until you are done with this array.\n\nstatic int  s_dir_flatten (zdir_t *self, zfile_t **files, int index);\nstatic int s_dir_compare (void *item1, void *item2);\nstatic int s_file_compare (void *item1, void *item2);\n\nzfile_t **\nzdir_flatten (zdir_t *self)\n{\n    size_t flat_size;\n    if (self)\n        flat_size = self->count + 1;\n    else\n        flat_size = 1;      //  Just null terminator\n\n    zfile_t **files = (zfile_t **) zmalloc (sizeof (zfile_t *) * flat_size);\n    uint index = 0;\n    if (self)\n        index = s_dir_flatten (self, files, index);\n    \n    //sort flattened list for proper patches computation\n    zlist_t *sorted = zlist_new ();\n    for (size_t i = 0; i < self->count; i++)\n        zlist_append (sorted, files[i]);\n    zlist_sort (sorted, s_file_compare);\n    for (size_t i = 0; i < self->count; i++)\n        files[i] = (zfile_t *) zlist_pop (sorted);\n    zlist_destroy (&sorted);\n    \n    return files;\n}\n\n//  Flatten one directory, calls itself recursively\n\nstatic int\ns_dir_flatten (zdir_t *self, zfile_t **files, int index)\n{\n    //  First flatten the normal files\n    zlist_sort (self->files, s_file_compare);\n    zfile_t *file = (zfile_t *) zlist_first (self->files);\n    while (file) {\n        files [index++] = file;\n        file = (zfile_t *) zlist_next (self->files);\n    }\n    //  Now flatten subdirectories, recursively\n    zlist_sort (self->subdirs, s_dir_compare);\n    zdir_t *subdir = (zdir_t *) zlist_first (self->subdirs);\n    while (subdir) {\n        index = s_dir_flatten (subdir, files, index);\n        subdir = (zdir_t *) zlist_next (self->subdirs);\n    }\n    return index;\n}\n\n//  Compare two subdirs, true if they need swapping\n\nstatic int\ns_dir_compare (void *item1, void *item2)\n{\n    assert (item1);\n    assert (item2);\n\n    return strcmp (zdir_path ((zdir_t *) item1),\n                   zdir_path ((zdir_t *) item2));\n}\n\n//  Compare two files, true if they need swapping. We sort by ascending name.\n\nstatic int\ns_file_compare (void *item1, void *item2)\n{\n    assert (item1);\n    assert (item2);\n\n    return strcmp (zfile_filename ((zfile_t *) item1, NULL),\n                   zfile_filename ((zfile_t *) item2, NULL));\n}\n\n\n//  --------------------------------------------------------------------------\n//  Free a provided string, and nullify the parent pointer. Safe to call on\n//  a null pointer.\n\nvoid\nzdir_flatten_free (zfile_t ***files_p)\n{\n    assert (files_p);\n    freen (*files_p);\nzdir_diff (zdir_t *older, zdir_t *newer, const char *alias)\n{\n    zlist_t *patches = zlist_new ();\n    zfile_t **old_files = zdir_flatten (older);\n    zfile_t **new_files = zdir_flatten (newer);\n\nzdir_list (zdir_t *self)\n{\n    zfile_t **files = zdir_flatten (self);\n    zlist_t *list = zlist_new ();\n    size_t index;\n\n    if (files)\n    {\n        for (index = 0 ; files[index] ; index++)\n        {\n            zlist_append (list, files[index]);\n        }\n    }\n\n    zdir_flatten_free (&files);\n    return list;\n}\n\n        if (cmp > 0) {\n            //  New file was created\n            if (zfile_is_stable (new_file))\n                zlist_append (patches, zdir_patch_new (newer->path, new_file, patch_create, alias));\n            old_index--;\n        }\n        else\n        if (cmp < 0) {\n            //  Old file was deleted\n            if (zfile_is_stable (old_file))\n                zlist_append (patches, zdir_patch_new (older->path, old_file, patch_delete, alias));\n            new_index--;\n        }\n        else\n    }\n    //  Remove if empty\n    if (zlist_size (self->files) == 0\n                //  Since we don\'t check file contents, treat as created\n                //  Could better do SHA check on file here\n                if (zfile_modified (new_file) != zfile_modified (old_file)\n                ||  zfile_cursize (new_file) != zfile_cursize (old_file))\n                    zlist_append (patches, zdir_patch_new (newer->path, new_file, patch_create, alias));\n            }\n            else\n                //  File was created over some period of time\n                zlist_append (patches, zdir_patch_new (newer->path, new_file, patch_create, alias));\n        }\n        old_index++;\n        new_index++;\n    int new_index = 0;\n\n    //  Note that both lists are sorted, so detecting differences\n    //  is rather trivial\n    while (old_files [old_index] || new_files [new_index]) {\n        zfile_t *old_file = old_files [old_index];\n        zfile_t *new_file = new_files [new_index];\n\n        int cmp;\n        if (!old_file)\n            cmp = 1;        //  Old file was deleted at end of list\n        else\n        if (!new_file)\nzdir_resync (zdir_t *self, const char *alias)\n{\n    zlist_t *patches = zlist_new ();\n    zfile_t **files = zdir_flatten (self);\n    uint index;\n    for (index = 0;; index++) {\n        zfile_t *file = files [index];\n        if (!file)\n            break;\n        zlist_append (patches, zdir_patch_new (self->path, file, patch_create, alias));\n    }\n    free (files);\n    return patches;\n                int rc = zlist_append (patches, zdir_patch_new (older->path, old_file, patch_delete, alias));\n                if (rc != 0) {\n                    zlist_destroy (&patches);\n                    break;\n                }\n            }\n            new_index--;\n        }\n        else\n        if (cmp == 0 && zfile_is_stable (new_file)) {\n            if (zfile_is_stable (old_file)) {\n                //  Old file was modified or replaced\n\n    //  Load any previous cache from disk\n    zhash_t *cache = zhash_new ();\n    zhash_autofree (cache);\n    char *cache_file = (char *) zmalloc (strlen (self->path) + strlen ("/.cache") + 1);\n    sprintf (cache_file, "%s/.cache", self->path);\n    zhash_load (cache, cache_file);\n\n                if (rc != 0) {\n                    zlist_destroy (&patches);\n                    break;\n                }\n            }\n        if (!file)\n            break;\n        char *filename = zfile_filename (file, self->path);\n        if (zhash_lookup (cache, zfile_filename (file, self->path)) == NULL)\n            zhash_insert (cache, filename, zfile_digest (file));\n    }\n    free (files);\n\n    //  Save cache to disk for future reference\n    zhash_save (cache, cache_file);\n    free (cache_file);\n    return cache;\n}\n    if (!patches)\n        return NULL;\n\n    zfile_t **files = zdir_flatten (self);\n    uint index;\n    for (index = 0;; index++) {\n        zfile_t *file = files [index];\n        if (!file)\n            break;\n        if (zlist_append (patches, zdir_patch_new (\n            self->path, file, patch_create, alias))) {\n            zlist_destroy (&patches);\n            break;\n        }\n    }\n    freen (files);\n    return patches;\n}\n\n\n//  --------------------------------------------------------------------------\n//  Load directory cache; returns a hash table containing the SHA-1 digests\n//  of every file in the tree. The cache is saved between runs in .cache.\n//  The caller must destroy the hash table when done with it.\n\nzhash_t *\nzdir_cache (zdir_t *self)\n{\n    assert (self);\n\n    //  Load any previous cache from disk\n    zhash_t *cache = zhash_new ();\n    if (!cache)\n        return NULL;\n    zhash_autofree (cache);\n    char *cache_file = (char *) zmalloc (strlen (self->path) + strlen ("/.cache") + 1);\n    if (!cache_file) {\n        zhash_destroy (&cache);\n        return NULL;\n    }\n    sprintf (cache_file, "%s/.cache", self->path);\n    zhash_load (cache, cache_file);\n\n    //  Recalculate digest for any new files\n    zfile_t **files = zdir_flatten (self);\n    uint index;\n    for (index = 0;; index++) {\n        zfile_t *file = files [index];\n        if (!file)\n            break;\n        const char *filename = zfile_filename (file, self->path);\n        if (zhash_lookup (cache, zfile_filename (file, self->path)) == NULL) {\n            int rc = zhash_insert (cache, filename, (void *) zfile_digest (file));\n            if (rc != 0) {\n                zhash_destroy (&cache);\n                break;\n            }\n        }\n    }\n    freen (files);\n\n    //  Save cache to disk for future reference\n    if (cache)\n        zhash_save (cache, cache_file);\n    freen (cache_file);\n    return cache;\n}\n\n\n//  --------------------------------------------------------------------------\n//  Print contents of directory to open stream\n\nvoid\nzdir_fprint (zdir_t *self, FILE *stream, int indent)\n{\n    assert (self);\n\n    zfile_t **files = zdir_flatten (self);\n    uint index;\n    for (index = 0;; index++) {\n        zfile_t *file = files [index];\n        if (!file)\n            break;\n        fprintf (stream, "%s\\n", zfile_filename (file, NULL));\n    }\n    zdir_flatten_free (&files);\n}\n\n\n//  --------------------------------------------------------------------------\n//  Print contents of directory to stdout\n\nvoid\nzdir_print (zdir_t *self, int indent)\n{\n    zdir_fprint (self, stdout, indent);\n}\n\n//  --------------------------------------------------------------------------\n//  Watch a directory for changes\n\ntypedef struct _zdir_watch_t {\n    zsock_t *pipe;            // actor command channel\n    zloop_t *loop;            // event reactor\n    int read_timer_id;        // the zloop timer id to signal directory updating\n\n    bool verbose;             // extra logging to be printed\n    zhash_t *subs;            // path -> zdir_watch_sub_t instance hashtable for each active subscription\n} zdir_watch_t;\n\ntypedef struct _zdir_watch_sub_t {\n    zdir_t *dir;\n} zdir_watch_sub_t;\n\nstatic int\ns_on_read_timer (zloop_t *loop, int timer_id, void *arg)\n{\n    zdir_watch_t *watch = (zdir_watch_t *) arg;\n\n    void *data;\n    for (data = zhash_first (watch->subs); data != NULL; data = zhash_next (watch->subs))\n    {\n        zdir_watch_sub_t *sub = (zdir_watch_sub_t *) data;\n\n        zdir_t *new_dir = zdir_new (zdir_path (sub->dir), NULL);\n        if (!new_dir) {\n            if (watch->verbose)\n                zsys_error ("zdir_watch: Unable to create new zdir for path %s", zdir_path (sub->dir));\n            continue;\n        }\n\n        // Determine if anything has changed.\n        zlist_t *diff = zdir_diff (sub->dir, new_dir, "");\n\n        // Do memory management before error handling...\n        zdir_destroy (&sub->dir);\n        sub->dir = new_dir;\n\n        if (!diff) {\n            if (watch->verbose)\n                zsys_error ("zdir_watch: Unable to create diff for path %s", zdir_path (sub->dir));\n            continue;\n        }\n\n        if (zlist_size (diff) > 0) {\n            if (watch->verbose) {\n                zdir_patch_t *patch = (zdir_patch_t *) zlist_first (diff);\n\n                zsys_info ("zdir_watch: Found %d changes in %s:", zlist_size (diff), zdir_path (sub->dir));\n                while (patch)\n                {\n                    zsys_info ("zdir_watch:   %s %s", zfile_filename (zdir_patch_file (patch), NULL), zdir_patch_op (patch) == ZDIR_PATCH_CREATE? "created": "deleted");\n                    patch = (zdir_patch_t *) zlist_next (diff);\n                }\n            }\n\n            if (zsock_send (watch->pipe, "sp", zdir_path (sub->dir), diff) != 0) {\n                if (watch->verbose)\n                    zsys_error ("zdir_watch: Unable to send patch list for path %s", zdir_path (sub->dir));\n                zlist_destroy (&diff);\n            }\n\n            // Successfully sent `diff` list - now owned by receiver\n        }\n        else {\n            zlist_destroy (&diff);\n        }\n    }\n\n    return 0;\n}\n\n\nstatic void\ns_zdir_watch_destroy (zdir_watch_t **watch_p)\n{\n    assert (watch_p);\n    if (*watch_p) {\n        zdir_watch_t *watch = *watch_p;\n\n        zloop_destroy (&watch->loop);\n        zhash_destroy (&watch->subs);\n\n        freen (watch);\n        *watch_p = NULL;\n    }\n}\n\nstatic void\ns_sub_free (void *data)\n{\n    zdir_watch_sub_t *sub = (zdir_watch_sub_t *) data;\n    zdir_destroy (&sub->dir);\n\n    freen (sub);\n}\n\nstatic void\ns_zdir_watch_subscribe (zdir_watch_t *watch, const char *path)\n{\n    if (watch->verbose)\n        zsys_info ("zdir_watch: Subscribing to directory path: %s", path);\n\n    zdir_watch_sub_t *sub = (zdir_watch_sub_t *) zmalloc (sizeof (zdir_watch_sub_t));\n    sub->dir = zdir_new (path, NULL);\n    if (!sub->dir) {\n        if (watch->verbose)\n            zsys_error ("zdir_watch: Unable to create zdir for path: %s", path);\n        zsock_signal (watch->pipe, 1);\n        return;\n    }\n\n    int rc = zhash_insert (watch->subs, path, sub);\n    if (rc) {\n        if (watch->verbose)\n            zsys_error ("zdir_watch: Unable to insert path \'%s\' into subscription list", path);\n        zsock_signal (watch->pipe, 1);\n        return;\n    }\n\n    void *item = zhash_freefn (watch->subs, path, s_sub_free);\n    if (item != sub) {\n        if (watch->verbose)\n            zsys_error ("zdir_watch: Unable to set free fn for path %s", path);\n        zsock_signal (watch->pipe, 1);\n        return;\n    }\n\n    if (watch->verbose)\n        zsys_info ("zdir_watch: Successfully subscribed to %s", path);\n    zsock_signal (watch->pipe, 0);\n}\n\nstatic void\ns_zdir_watch_unsubscribe (zdir_watch_t *watch, const char *path)\n{\n    if (watch->verbose)\n        zsys_info ("zdir_watch: Unsubscribing from directory path: %s", path);\n\n    zhash_delete (watch->subs, path);\n    if (watch->verbose)\n        zsys_info ("zdir_watch: Successfully unsubscribed from %s", path);\n    zsock_signal (watch->pipe, 0);\n}\n\nstatic int\ns_zdir_watch_timeout (zdir_watch_t *watch, int timeout)\n{\n    if (watch->verbose)\n        zsys_info ("zdir_watch: Setting directory poll timeout to %d", timeout);\n\n    if (watch->read_timer_id != -1) {\n        zloop_timer_end (watch->loop, watch->read_timer_id);\n        watch->read_timer_id = -1;\n    }\n\n    watch->read_timer_id = zloop_timer (watch->loop, timeout, 0, s_on_read_timer, watch);\n\n    if (watch->verbose)\n        zsys_info ("zdir_watch: Successfully set directory poll timeout to %d", timeout);\n    return 0;\n}\n\nstatic zdir_watch_t *\ns_zdir_watch_new (zsock_t *pipe)\n{\n    zdir_watch_t *watch = (zdir_watch_t *) zmalloc (sizeof (zdir_watch_t));\n    if (!watch)\n        return NULL;\n    watch->pipe = pipe;\n    watch->read_timer_id = -1;\n    watch->verbose = false;\n    return watch;\n}\n\nstatic int\ns_on_command (zloop_t *loop, zsock_t *reader, void *arg)\n{\n    zdir_watch_t *watch = (zdir_watch_t *) arg;\n\n    zmsg_t *msg = zmsg_recv (watch->pipe);\n    assert (msg);\n    char *command = zmsg_popstr (msg);\n    assert (command);\n\n    if (watch->verbose)\n        zsys_info ("zdir_watch: Command received: %s", command);\n\n    if (streq (command, "$TERM")) {\n        zstr_free (&command);\n        zmsg_destroy (&msg);\n        return -1;\n    }\n    else\n    if (streq (command, "VERBOSE")) {\n        watch->verbose = true;\n        zsock_signal (watch->pipe, 0);\n    }\n    else\n    if (streq (command, "SUBSCRIBE")) {\n        char *path = zmsg_popstr (msg);\n        if (path) {\n            s_zdir_watch_subscribe (watch, path);\n            freen (path);\n        }\n        else {\n            if (watch->verbose)\n                zsys_error ("zdir_watch: Unable to extract path from SUBSCRIBE message");\n            zsock_signal (watch->pipe, 1);\n        }\n    }\n    else\n    if (streq (command, "UNSUBSCRIBE")) {\n        char *path = zmsg_popstr (msg);\n        if (path) {\n            assert (path);\n            s_zdir_watch_unsubscribe (watch, path);\n            freen (path);\n        }\n        else {\n            if (watch->verbose)\n                zsys_error ("zdir_watch: Unable to extract path from UNSUBSCRIBE message");\n            zsock_signal (watch->pipe, 1);\n        }\n    }\n    else\n    if (streq (command, "TIMEOUT")) {\n        char *timeout_string = zmsg_popstr (msg);\n        if (timeout_string) {\n            int timeout = atoi (timeout_string);\n            zsock_signal (watch->pipe, s_zdir_watch_timeout (watch, timeout));\n            freen (timeout_string);\n        }\n        else {\n            if (watch->verbose)\n                zsys_error ("zdir_watch: Unable to extract time from TIMEOUT message");\n            zsock_signal (watch->pipe, 1);\n        }\n    }\n    else {\n        if (watch->verbose)\n            zsys_warning ("zdir_watch: Unknown command \'%s\'", command);\n        zsock_signal (watch->pipe, 1);\n    }\n\n    freen (command);\n    zmsg_destroy (&msg);\n    return 0;\n}\n\n//  --------------------------------------------------------------------------\n//  Create a new zdir_watch actor instance\n\nvoid\nzdir_watch (zsock_t *pipe, void *unused)\n{\n    zdir_watch_t *watch = s_zdir_watch_new (pipe);\n    assert (watch);\n\n    watch->loop = zloop_new ();\n    assert (watch->loop);\n\n    watch->subs = zhash_new ();\n    assert (watch->subs);\n\n    zloop_reader (watch->loop, pipe, s_on_command, watch);\n    zloop_reader_set_tolerant (watch->loop, pipe); // command pipe needs to be tolerant, otherwise we\'d have a hard time shutting down\n\n    s_zdir_watch_timeout (watch, 250); // default poll time of 250ms\n\n    //  Signal initialization\n    zsock_signal (pipe, 0);\n\n    // Dispatch above handlers\n    zloop_start (watch->loop);\n    if (watch->verbose)\n        zsys_info ("zdir_watch: Complete");\n\n    // signal destruction\n    zsock_signal (watch->pipe, 0);\n\n    // Done - cleanup and exit\n    s_zdir_watch_destroy (&watch);\n}\n\n\n//  --------------------------------------------------------------------------\n//  Self test of this class\n\nvoid\nzdir_test (bool verbose)\n{\n    printf (" * zdir: ");\n\n    //  @selftest\n\n    const char *SELFTEST_DIR_RW = "src/selftest-rw";\n\n    const char *testbasedir  = "zdir-test-dir";\n    const char *testfile1 = "initial_file";\n    const char *testfile2 = "test_abc";\n    char *basedirpath = NULL;   // subdir in a test, under SELFTEST_DIR_RW\n    char *filepath1 = NULL;      // pathname to testfile in a test, in dirpath\n    char *filepath2 = NULL;      // pathname to testfile in a test, in dirpath\n\n    basedirpath = zsys_sprintf ("%s/%s", SELFTEST_DIR_RW, testbasedir);\n    assert (basedirpath);\n    filepath1 = zsys_sprintf ("%s/%s", basedirpath, testfile1);\n    assert (filepath1);\n    filepath2 = zsys_sprintf ("%s/%s", basedirpath, testfile2);\n    assert (filepath2);\n\n/*\n    char *relfilepath2 = NULL;      // pathname to testfile in a test, in dirpath\n    relfilepath2 = zsys_sprintf ("%s/%s", testbasedir, testfile2);\n    assert (relfilepath2);\n*/\n\n    // Make sure old aborted tests do not hinder us\n    zdir_t *dir = zdir_new (basedirpath, NULL);\n    if (dir) {\n        zdir_remove (dir, true);\n        zdir_destroy (&dir);\n    }\n    zsys_file_delete (filepath1);\n    zsys_file_delete (filepath2);\n    zsys_dir_delete  (basedirpath);\n\n    dir = zdir_new ("does-not-exist", NULL);\n    if (dir) {\n        zdir_remove (dir, true);\n        zdir_destroy (&dir);\n    }\n\n    // need to create a file in the test directory we\'re watching\n    // in order to ensure the directory exists\n    zfile_t *initfile = zfile_new (basedirpath, testfile1);\n    assert (initfile);\n    zfile_output (initfile);\n    fprintf (zfile_handle (initfile), "initial file\\n");\n    zfile_close (initfile);\n    zfile_destroy (&initfile);\n\n    zdir_t *older = zdir_new (basedirpath, NULL);\n    assert (older);\n    if (verbose) {\n        printf ("\\n");\n        zdir_dump (older, 0);\n    }\n    zdir_t *newer = zdir_new (SELFTEST_DIR_RW, NULL);\n    assert (newer);\n    zlist_t *patches = zdir_diff (older, newer, "/");\n    assert (patches);\n    while (zlist_size (patches)) {\n        zdir_patch_t *patch = (zdir_patch_t *) zlist_pop (patches);\n        zdir_patch_destroy (&patch);\n    }\n    zlist_destroy (&patches);\n    zdir_destroy (&older);\n    zdir_destroy (&newer);\n\n    zdir_t *nosuch = zdir_new ("does-not-exist", NULL);\n    assert (nosuch == NULL);\n\n    // zdir_watch test:\n    zactor_t *watch = zactor_new (zdir_watch, NULL);\n    assert (watch);\n\n    int synced;\n    if (verbose) {\n        zsock_send (watch, "s", "VERBOSE");\n        synced = zsock_wait(watch);\n        assert ( synced == 0);\n    }\n\n    // wait for initial file to become \'stable\'\n#ifdef CZMQ_BUILD_DRAFT_API\n    zclock_sleep ((int)zsys_file_stable_age_msec() + 50);\n#else\n    zclock_sleep (5050);\n#endif\n\n    zsock_send (watch, "si", "TIMEOUT", 100);\n    synced = zsock_wait(watch);\n    assert (synced == 0);\n\n    zsock_send (watch, "ss", "SUBSCRIBE", basedirpath);\n    synced = zsock_wait(watch);\n    assert(synced == 0);\n\n    zsock_send (watch, "ss", "UNSUBSCRIBE", basedirpath);\n    synced = zsock_wait(watch);\n    assert(synced == 0);\n\n    zsock_send (watch, "ss", "SUBSCRIBE", basedirpath);\n    synced = zsock_wait(watch);\n    assert(synced == 0);\n\n    zfile_t *newfile = zfile_new (basedirpath, testfile2);\n    zfile_output (newfile);\n    fprintf (zfile_handle (newfile), "test file\\n");\n    zfile_close (newfile);\n\n    zpoller_t *watch_poll = zpoller_new (watch, NULL);\n\n    // poll for a certain timeout before giving up and failing the test\n    void* polled = NULL;\n#ifdef CZMQ_BUILD_DRAFT_API\n    polled = zpoller_wait(watch_poll, (int)zsys_file_stable_age_msec() + 150);\n#else\n    polled = zpoller_wait(watch_poll, 5150);\n#endif\n    assert (polled == watch);\n\n    // wait for notification of the file being added\n    char *path;\n    int rc = zsock_recv (watch, "sp", &path, &patches);\n    assert (rc == 0);\n\n    assert (streq (path, basedirpath));\n    freen (path);\n\n    if (verbose)\n        zsys_debug("zdir_test() : added : zlist_size (patches)=%d",\n            zlist_size (patches) );\n    assert (zlist_size (patches) == 1);\n\n    zdir_patch_t *patch = (zdir_patch_t *) zlist_pop (patches);\n    if (verbose)\n        zsys_debug("zdir_test() : added : zdir_patch_path (patch)=\'%s\'",\n            zdir_patch_path (patch) );\n    assert (streq (zdir_patch_path (patch), basedirpath));\n\n    zfile_t *patch_file = zdir_patch_file (patch);\n    if (verbose)\n        zsys_debug("zdir_test() : added : zfile_filename (patch_file, \\"\\")=\'%s\'",\n            zfile_filename (patch_file, "") );\n    assert (streq (zfile_filename (patch_file, ""), filepath2));\n\n    zdir_patch_destroy (&patch);\n    zlist_destroy (&patches);\n\n    // remove the file\n    zfile_remove (newfile);\n    zfile_destroy (&newfile);\n\n    // poll for a certain timeout before giving up and failing the test.\n#ifdef CZMQ_BUILD_DRAFT_API\n    polled = zpoller_wait(watch_poll, (int)zsys_file_stable_age_msec() + 150);\n#else\n    polled = zpoller_wait(watch_poll, 5150);\n#endif\n    assert (polled == watch);\n\n    // wait for notification of the file being removed\n    rc = zsock_recv (watch, "sp", &path, &patches);\n    assert (rc == 0);\n\n    assert (streq (path, basedirpath));\n    freen (path);\n\n    if (verbose)\n        zsys_debug("zdir_test() : removed : zlist_size (patches)=%d",\n            zlist_size (patches) );\n    assert (zlist_size (patches) == 1);\n\n    patch = (zdir_patch_t *) zlist_pop (patches);\n    if (verbose)\n        zsys_debug("zdir_test() : removed : zdir_patch_path (patch)=\'%s\'",\n            zdir_patch_path (patch) );\n    assert (streq (zdir_patch_path (patch), basedirpath));\n\n    patch_file = zdir_patch_file (patch);\n    if (verbose)\n        zsys_debug("zdir_test() : removed : zfile_filename (patch_file, \\"\\")=\'%s\'",\n            zfile_filename (patch_file, "") );\n    assert (streq (zfile_filename (patch_file, ""), filepath2));\n\n    zdir_patch_destroy (&patch);\n    zlist_destroy (&patches);\n\n    zpoller_destroy (&watch_poll);\n    zactor_destroy (&watch);\n\n    // clean up by removing the test directory.\n    dir = zdir_new (basedirpath, NULL);\n    assert (dir);\n    zdir_remove (dir, true);\n    zdir_destroy (&dir);\n\n    zstr_free (&basedirpath);\n    zstr_free (&filepath1);\n    zstr_free (&filepath2);\n\n#if defined (__WINDOWS__)\n    zsys_shutdown();\n#endif\n    //  @end\n\n    printf ("OK\\n");\n}\n', 'message': 'zdir: check for errors\n', 'author_name': 'Goswin von Brederlow', 'lang': 'c'}