/* ** Copyright (C) 2006-2007 by Carnegie Mellon University. ** ** @OPENSOURCE_HEADER_START@ ** ** Use of the SILK system and related source code is subject to the terms ** of the following licenses: ** ** GNU Public License (GPL) Rights pursuant to Version 2, June 1991 ** Government Purpose License Rights (GPLR) pursuant to DFARS 252.225-7013 ** ** NO WARRANTY ** ** ANY INFORMATION, MATERIALS, SERVICES, INTELLECTUAL PROPERTY OR OTHER ** PROPERTY OR RIGHTS GRANTED OR PROVIDED BY CARNEGIE MELLON UNIVERSITY ** PURSUANT TO THIS LICENSE (HEREINAFTER THE "DELIVERABLES") ARE ON AN ** "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY ** KIND, EITHER EXPRESS OR IMPLIED AS TO ANY MATTER INCLUDING, BUT NOT ** LIMITED TO, WARRANTY OF FITNESS FOR A PARTICULAR PURPOSE, ** MERCHANTABILITY, INFORMATIONAL CONTENT, NONINFRINGEMENT, OR ERROR-FREE ** OPERATION. CARNEGIE MELLON UNIVERSITY SHALL NOT BE LIABLE FOR INDIRECT, ** SPECIAL OR CONSEQUENTIAL DAMAGES, SUCH AS LOSS OF PROFITS OR INABILITY ** TO USE SAID INTELLECTUAL PROPERTY, UNDER THIS LICENSE, REGARDLESS OF ** WHETHER SUCH PARTY WAS AWARE OF THE POSSIBILITY OF SUCH DAMAGES. ** LICENSEE AGREES THAT IT WILL NOT MAKE ANY WARRANTY ON BEHALF OF ** CARNEGIE MELLON UNIVERSITY, EXPRESS OR IMPLIED, TO ANY PERSON ** CONCERNING THE APPLICATION OF OR THE RESULTS TO BE OBTAINED WITH THE ** DELIVERABLES UNDER THIS LICENSE. ** ** Licensee hereby agrees to defend, indemnify, and hold harmless Carnegie ** Mellon University, its trustees, officers, employees, and agents from ** all claims or demands made against them (and any related losses, ** expenses, or attorney's fees) arising out of, or relating to Licensee's ** and/or its sub licensees' negligent use or willful misuse of or ** negligent conduct or willful misconduct regarding the Software, ** facilities, or other rights or assistance granted by Carnegie Mellon ** University under this License, including, but not limited to, any ** claims of product liability, personal injury, death, damage to ** property, or violation of any laws or regulations. ** ** Carnegie Mellon University Software Engineering Institute authored ** documents are sponsored by the U.S. Department of Defense under ** Contract F19628-00-C-0003. Carnegie Mellon University retains ** copyrights in all material produced under this contract. The U.S. ** Government retains a non-exclusive, royalty-free license to publish or ** reproduce these documents, or allow others to do so, for U.S. ** Government purposes only pursuant to the copyright license under the ** contract clause at 252.227.7013. ** ** @OPENSOURCE_HEADER_END@ */ /* ** Library for polling directories for new files ** */ #include "silk.h" RCSIDENT("$SiLK: skpolldir.c 6544 2007-03-05 00:53:17Z mwd $"); #include "redblack.h" #include "skvector.h" #include "sklinkedlist.h" #include "skpolldir.h" #include "sktimer.h" #include "utils.h" /* A file entry */ typedef struct file_t { char *name; off_t size; unsigned int seen : 1; unsigned int queued : 1; } file_t; /* A polldir queue */ typedef struct sk_polldir_queue_t { skDeque_t queue; sk_link_list_t *pds; skPollDir_t errpd; } sk_polldir_queue_t; /* The type of items in the queue */ typedef struct file_item_t { char *path; char *name; } file_item_t; /* The type of skPollDir_t objects */ typedef struct sk_polldir_t { char *directory; size_t filename_offset; struct rbtree *tree; skPollDirQueue_t queue; skTimer_t timer; skpdErr_t error; } sk_polldir_t; /* Comparison function for file_t items (used by red black tree) */ static int compare( const void *va, const void *vb, const void *UNUSED(unused)) { const file_t *a = (const file_t *)va; const file_t *b = (const file_t *)vb; return strcmp(a->name, b->name); } /* Walk through the tree removing files that were not seen. Marks * files that were not removed as unseen in preperation for the next * pass. */ static void remove_unseen(sk_polldir_t *pd) { RBLIST *list = NULL; file_t *x; sk_vector_t *dellist = NULL; int rv; size_t i; dellist = skVectorNew(sizeof(file_t *)); list = rbopenlist(pd->tree); if (list == NULL) { pd->error = PDERR_MEMORY; goto end; } /* Loop through all files */ while ((x = (file_t *)rbreadlist(list))) { if (x->seen) { /* Seen. Reset to zero for next pass. */ x->seen = 0; } else { /* Not seen. Add to delete list. */ rv = skVectorAppendValue(dellist, &x); if (-1 == rv) { pd->error = PDERR_MEMORY; goto end; } } } /* Remove items in the delete list */ for (i = 0; i < skVectorGetCount(dellist); i++) { rv = skVectorGetValue(&x, dellist, i); assert(0 == rv); rbdelete(x, pd->tree); free(x->name); free(x); } end: if (list) { rbcloselist(list); } if (dellist) { skVectorDestroy(dellist); } } /* Free all file_t structures in the tree. */ static void free_tree_nodes(sk_polldir_t *pd) { RBLIST *list; file_t *x; list = rbopenlist(pd->tree); if (list == NULL) { pd->error = PDERR_MEMORY; goto end; } while ((x = (file_t *)rbreadlist(list))) { free(x->name); free(x); } end: rbcloselist(list); } /* Actually poll the directory. */ static skTimerRepeat_t pollDir(void *vpd) { skPollDir_t pd = (skPollDir_t)vpd; DIR *dir; struct dirent *entry; struct stat st; int rv; char path[PATH_MAX]; file_t *node, *found; file_item_t *item; node = NULL; dir = opendir(pd->directory); if (NULL == dir) { pd->error = PDERR_FILESYSTEM; goto error; } rv = sprintf(path, "%s/", pd->directory); assert(rv == (ssize_t)pd->filename_offset); /* Loop over all files in the directory */ while (PDERR_NONE == pd->error && (entry = readdir(dir))) { /* Ignore dot files. */ if (entry->d_name[0] == '.') { continue; } assert(strlen(entry->d_name) + pd->filename_offset < sizeof(path)); strcpy(path + pd->filename_offset, entry->d_name); /* Ignore files that are empty, or that aren't either regular files or symbolic links. */ rv = stat(path, &st); if (rv == -1 || !(st.st_mode & (S_IFREG | S_IFLNK)) || 0 == st.st_size) { continue; } /* Allocate an entry, if one doesn't already exist. */ if (node == NULL) { node = malloc(sizeof(*node)); if (NULL == node) { pd->error = PDERR_MEMORY; continue; } node->queued = 0; } node->name = entry->d_name; node->size = st.st_size; /* Find or insert the entry into the tree. */ found = (file_t *)rbsearch(node, pd->tree); if (found != node) { /* Node already existed. */ if (!found->queued && node->size == found->size) { /* Size has stabalized, add to queue */ found->queued = 1; item = (file_item_t *)malloc(sizeof(file_item_t)); if (NULL == item) { pd->error = PDERR_MEMORY; continue; } item->path = strdup(path); if (NULL == item->path) { free(item); pd->error = PDERR_MEMORY; continue; } item->name = item->path + pd->filename_offset; skDequePushFront(pd->queue->queue, item); } } else { /* New node has been added to the tree */ /* Make the name persistant */ found->name = strdup(found->name); if (NULL == found->name) { pd->error = PDERR_MEMORY; continue; } /* Allocate a new node on the next run through the loop. */ node = NULL; } /* Mark this file as seen this time around*/ found->seen = 1; } closedir(dir); /* Free node, if left over. */ if (node) { free(node); } /* Remove entries we did not see, and re-mark tree as unseen. */ if (PDERR_NONE == pd->error) { remove_unseen(pd); } /* Repeat if no error */ if (PDERR_NONE == pd->error) { return SK_TIMER_REPEAT; } error: skDequeUnblock(pd->queue->queue); return SK_TIMER_END; } /* Destroy a polldir object */ static void skPollDirDestroySimple(skPollDir_t pd) { assert(pd); /* Stop the timer */ if (pd->timer) { skTimerDestroy(pd->timer); } /* Empty and destroy the tree */ if (pd->tree) { free_tree_nodes(pd); rbdestroy(pd->tree); } /* Free the directory name */ if (pd->directory) { free(pd->directory); } /* Finally, free the polldir object */ free(pd); } /* Destroy a polldir object and disassociate it from its queue */ void skPollDirDestroy(skPollDir_t pd) { sk_link_item_t *item; sk_link_err_t err; skPollDir_t tpd; err = skLinkGetHead(&item, pd->queue->pds); while (SKLINK_OK == err) { err = skLinkGetData((void **)&tpd, item); if (SKLINK_OK != err) { break; } if (tpd == pd) { err = skLinkRemoveNodeKeepData(pd->queue->pds, item); break; } err = skLinkGetNext(&item, item); } skPollDirDestroySimple(pd); } /* Create a directory polling queue. */ skPollDirQueue_t skPollDirQueueCreate() { skPollDirQueue_t q; sk_link_err_t err; q = (skPollDirQueue_t)calloc(1, sizeof(*q)); if (NULL == q) { return NULL; } q->queue = skDequeCreate(); if(NULL == q->queue) { free(q); return NULL; } err = skLinkAllocList(&q->pds, (sk_link_free_fn_t)skPollDirDestroySimple); if (err != SKLINK_OK) { skDequeDestroy(q->queue); free(q); return NULL; } return q; } /* Destroy a directory polling queue. */ void skPollDirQueueDestroy(skPollDirQueue_t queue) { file_item_t *item; assert(queue); /* Destroy the associated polldir objects */ skLinkFreeList(queue->pds); /* Empty and destoy the queue */ while (skDequePopFrontNB(queue->queue, (void **)&item) == SKDQ_SUCCESS) { free(item->path); free(item); } skDequeDestroy(queue->queue); free(queue); } /* Create a directory polling object. */ skPollDir_t skPollDirCreate( const char *directory, uint32_t poll_interval, skPollDirQueue_t queue) { skPollDir_t pd; int rv; sk_link_err_t e; assert(directory); assert(queue); if (!dirExists(directory)) { return NULL; } pd = (skPollDir_t)calloc(1, sizeof(*pd)); if (NULL == pd) { return NULL; } pd->directory = strdup(directory); if (NULL == pd->directory) { goto err; } pd->filename_offset = strlen(directory) + 1; if (pd->filename_offset >= PATH_MAX) { goto err; } e = skLinkAppendData(queue->pds, pd); if (SKLINK_OK != e) { goto err; } pd->queue = queue; pd->tree = rbinit(compare, NULL); if (NULL == pd->tree) { goto err; } /* Initial population of tree */ pollDir(pd); /* Start timer */ rv = skTimerCreate(&pd->timer, poll_interval, pollDir, pd); if (0 != rv) { goto err; } return pd; err: /* Clean up after any errors */ skPollDirDestroy(pd); return NULL; } /* Get the next added entry to a directory. */ int skPollDirGetNextFile(skPollDirQueue_t queue, char *path, char **filename) { file_item_t *item; skDQErr_t err; assert(queue); assert(path); do { item = NULL; err = skDequePopBack(queue->queue, (void **)&item); if (SKDQ_UNBLOCKED == err) { return -1; } if (SKDQ_DESTROYED == err) { return 1; } if (SKDQ_SUCCESS != err) { if (item) { free(item->path); free(item); } return -1; } assert(item->path); assert(strlen(item->path) < PATH_MAX); strcpy(path, item->path); if (filename) { *filename = path + (item->name - item->path); } free(item->path); free(item); } while (!fileExists(path)); return 0; } /* Get the directory being polled by a polldir object. */ const char *skPollDirGetDir(skPollDir_t pd) { assert(pd); return pd->directory; } /* Get the error state of a polldir object. */ skpdErr_t skPollDirGetError(skPollDir_t pd) { assert(pd); return pd->error; }