Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
Watcher.c
/* |
File: Watcher.c |
Abstract: A simple demonstration of the FSEvents API. |
Version: <1.3> |
Disclaimer: IMPORTANT: This Apple software is supplied to you by |
Apple Inc. ("Apple") in consideration of your agreement to the |
following terms, and your use, installation, modification or |
redistribution of this Apple software constitutes acceptance of these |
terms. If you do not agree with these terms, please do not use, |
install, modify or redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and |
subject to these terms, Apple grants you a personal, non-exclusive |
license, under Apple's copyrights in this original Apple software (the |
"Apple Software"), to use, reproduce, modify and redistribute the Apple |
Software, with or without modifications, in source and/or binary forms; |
provided that if you redistribute the Apple Software in its entirety and |
without modifications, you must retain this notice and the following |
text and disclaimers in all such redistributions of the Apple Software. |
Neither the name, trademarks, service marks or logos of Apple Inc. |
may be used to endorse or promote products derived from the Apple |
Software without specific prior written permission from Apple. Except |
as expressly stated in this notice, no other rights or licenses, express |
or implied, are granted by Apple herein, including but not limited to |
any patent rights that may be infringed by your derivative works or by |
other works in which the Apple Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE |
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION |
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS |
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND |
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL |
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, |
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED |
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), |
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE |
POSSIBILITY OF SUCH DAMAGE. |
Copyright (C) 2007 Apple Inc. All Rights Reserved. |
*/ |
// |
// This program is a simple example of using the FSEvents |
// framework that monitors a directory hierarchy and keeps |
// track of the total size of data contained in it. When |
// a directory inside of it changes, it recalculates the |
// size of that directory and updates the total size. |
// |
// The program is intentionally simplistic but demonstrates |
// the use of the FSEvents api in a hopefully clear fashion. |
// |
// To compile: |
// cc -I /System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework/Headers |
// -Wall -g -o watcher watcher.c -framework CoreServices -framework CoreFoundation |
// |
// |
#include <stdio.h> |
#include <stdlib.h> |
#include <string.h> |
#include <signal.h> |
#include <errno.h> |
#include <fcntl.h> |
#include <unistd.h> |
#include <sys/stat.h> |
#include <sys/param.h> |
#include <sys/mount.h> |
#include <sys/event.h> |
#include <dirent.h> |
#include <assert.h> |
#include <CoreFoundation/CoreFoundation.h> |
#include <CoreServices/CoreServices.h> |
#include <pthread.h> |
typedef struct _settings_t { |
dev_t dev; |
FSEventStreamEventId since_when; |
CFAbsoluteTime latency; |
const char *fullpath; |
CFUUIDRef dev_uuid; |
char mount_point[MAXPATHLEN]; |
} settings_t; |
// |
// Prototypes |
// |
void scan_directory(const char *path, int add, int recursive, int depth); |
int save_dir_items(const char *name); |
int load_dir_items(const char *name); |
void discard_all_dir_items(void); |
int remove_dir_and_children(const char *name); |
int check_children_of_dir(const char *dirname); |
off_t get_total_size(void); |
void save_stream_info(uint64_t last_id, CFUUIDRef dev_uuid); |
int load_stream_info(uint64_t *since_when, CFUUIDRef *uuid_ref_ptr); |
int setup_run_loop_signal_handler(CFRunLoopRef loop); |
void cleanup_run_loop_signal_handler(CFRunLoopRef loop); |
int get_dev_info(settings_t *settings); |
void usage(const char *progname); |
void parse_settings(int argc, const char *argv[], settings_t *settings); |
CFMutableArrayRef create_cfarray_from_path(const char *path); |
// |
//-------------------------------------------------------------------------------- |
// The FSEventsStreamCallback |
// |
static void |
fsevents_callback(FSEventStreamRef streamRef, void *clientCallBackInfo, |
int numEvents, |
const char *const eventPaths[], |
const FSEventStreamEventFlags *eventFlags, |
const uint64_t *eventIDs) |
{ |
settings_t *settings = (settings_t *)clientCallBackInfo; |
const char *full_path = (const char *)settings->fullpath; |
int i, len, recursive = 0; |
char path_buff[PATH_MAX]; |
for (i=0; i < numEvents; i++) { |
// |
// First, make a copy of the event path so we can modify it. |
// |
strcpy(path_buff, eventPaths[i]); |
len = strlen(path_buff); |
if (path_buff[len-1] == '/') { |
// chop off a trailing slash so that scan_directory() works |
path_buff[--len] = '\0'; |
} |
// |
// Now check the flags for this event to see if we have to |
// do anything special. |
// |
// If we get a HistoryDone event we can just skip it. |
// |
// If we get a RootChanged event, check if the root exists |
// or not. If not, throw away our state. If it does, then |
// rebuild our state. |
// |
// Then of course if the MustScanSubDirs flag is set we |
// have to do a recursive scan to update our state. |
// |
if (eventFlags[i] & kFSEventStreamEventFlagHistoryDone) { |
printf("Done processing historical events. Current total size is: %lld for path: %s\n", get_total_size(), settings->fullpath); |
continue; |
} else if (eventFlags[i] & kFSEventStreamEventFlagRootChanged) { |
struct stat st; |
if (stat(full_path, &st) == 0) { |
printf("Root path %s now exists!\n", full_path); |
recursive = 1; |
} else { |
printf("Root path %s no longer exists!\nNew total size: 0\n", full_path); |
discard_all_dir_items(); |
continue; |
} |
} else if (eventFlags[i] & kFSEventStreamEventFlagMustScanSubDirs) { |
recursive = 1; |
if (eventFlags[i] & kFSEventStreamEventFlagUserDropped) { |
printf("BAD NEWS! We dropped events.\n"); |
strlcpy(path_buff, full_path, sizeof(path_buff)); |
} else if (eventFlags[i] & kFSEventStreamEventFlagKernelDropped) { |
printf("REALLY BAD NEWS! The kernel dropped events.\n"); |
strlcpy(path_buff, full_path, sizeof(path_buff)); |
} |
} else { |
recursive = 0; |
} |
// |
// Now go update our state and print out the new size |
// of the directory hierarchy we're watching. |
// |
if (recursive) { |
remove_dir_and_children(path_buff); |
scan_directory(path_buff, 1, 1, 0); |
} else { |
check_children_of_dir(path_buff); |
} |
printf("New total size: %lld (change made to: %s) for path: %s\n", |
get_total_size(), path_buff, full_path); |
} |
} |
static void |
watch_dir_hierarchy(settings_t *settings) |
{ |
FSEventStreamContext context = {0, NULL, NULL, NULL, NULL}; |
FSEventStreamRef stream_ref = NULL; |
CFMutableArrayRef cfarray_of_paths; |
int need_initial_scan = 0; |
CFUUIDRef uuid_ref; |
// |
// Figure out the device of the path we're watching and |
// get its FSEventStream UUID. |
// |
if (get_dev_info(settings) != 0) { |
return; |
} |
// |
// Try to load the stored stream info |
// |
if (load_stream_info(&settings->since_when, &uuid_ref) == 0) { |
// |
// If we loaded the stream info cleanly, check if the uuid's match. |
// |
if (CFEqual(uuid_ref, settings->dev_uuid)) { |
// |
// The uuid's matched so now load the stored state for |
// this hierarchy |
// |
if (load_dir_items("diritems.txt") == 0) { |
printf("Stored total size is: %lld for path: %s (since_when %lld)\n", |
get_total_size(), settings->fullpath, settings->since_when); |
} else { |
settings->since_when = kFSEventStreamEventIdSinceNow; |
need_initial_scan = 1; |
} |
} else { |
printf("UUID mis-match! Ditching stored history id %lld\n", settings->since_when); |
settings->since_when = kFSEventStreamEventIdSinceNow; |
need_initial_scan = 1; |
} |
CFRelease(uuid_ref); |
} else { |
need_initial_scan = 1; |
} |
cfarray_of_paths = create_cfarray_from_path(settings->fullpath); |
if (cfarray_of_paths == NULL) { |
printf("failed to create the array for: %s\n", settings->fullpath); |
CFRelease(settings->dev_uuid); |
settings->dev_uuid = NULL; |
return; |
} |
context.info = (void *)settings; |
stream_ref = FSEventStreamCreate(kCFAllocatorDefault, |
(FSEventStreamCallback)&fsevents_callback, |
&context, |
cfarray_of_paths, |
settings->since_when, |
settings->latency, |
kFSEventStreamCreateFlagNone); |
// kFSEventStreamCreateFlagWatchRoot); |
CFRelease(cfarray_of_paths); |
if (stream_ref == NULL) { |
printf("failed to create the stream for: %s\n", settings->fullpath); |
CFRelease(settings->dev_uuid); |
settings->dev_uuid = NULL; |
return; |
} |
setup_run_loop_signal_handler(CFRunLoopGetCurrent()); |
FSEventStreamScheduleWithRunLoop(stream_ref, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); |
if (!FSEventStreamStart(stream_ref)) { |
fprintf(stderr, "failed to start the FSEventStream\n"); |
goto out; |
} |
if (need_initial_scan) { |
// |
// NOTE: we get the initial size *after* we start the |
// FSEventStream so that there is no window |
// during which we would miss events. |
// |
scan_directory(settings->fullpath, 1, 1, 0); |
printf("Initial total size is: %lld for path: %s\n", get_total_size(), settings->fullpath); |
} |
// |
// Run |
// |
CFRunLoopRun(); |
// Although it's not strictly necessary, make sure we see any pending events... |
FSEventStreamFlushSync(stream_ref); |
FSEventStreamStop(stream_ref); |
out: |
// |
// Save out information about the last event id and uuid for the |
// the device we're watching. |
// |
save_stream_info(FSEventStreamGetLatestEventId(stream_ref), settings->dev_uuid); |
// |
// Save the directory item state |
// |
save_dir_items("diritems.txt"); |
// |
// Invalidation and final shutdown of the stream |
// |
FSEventStreamInvalidate(stream_ref); |
FSEventStreamRelease(stream_ref); |
CFRelease(settings->dev_uuid); |
settings->dev_uuid = NULL; |
cleanup_run_loop_signal_handler(CFRunLoopGetCurrent()); |
return; |
} |
// |
//-------------------------------------------------------------------------------- |
// |
int |
main(int argc, const char * argv[]) |
{ |
char fullpath[PATH_MAX]; |
settings_t _settings, *settings = &_settings; |
parse_settings(argc, argv, settings); |
if (settings->fullpath == NULL) { |
// no path given to monitor! |
usage(argv[0]); |
} |
if (realpath(settings->fullpath, fullpath) == NULL) { |
if (settings->fullpath[0] != '/') { |
int len; |
getcwd(fullpath, sizeof(fullpath)); |
len = strlen(fullpath); |
fullpath[len] = '/'; |
strlcpy(&fullpath[len+1], settings->fullpath, sizeof(fullpath) - (len+1)); |
} else { |
strlcpy(fullpath, settings->fullpath, sizeof(fullpath)); |
} |
} |
settings->fullpath = fullpath; |
watch_dir_hierarchy(settings); |
return 0; |
} |
// |
//-------------------------------------------------------------------------------- |
// |
int |
get_dev_info(settings_t *settings) |
{ |
struct stat st; |
dev_t dev = 0; |
struct statfs sfs; |
char path[MAXPATHLEN]; |
settings->dev = 0; |
settings->mount_point[0] = '\0'; |
strlcpy(path, settings->fullpath, sizeof(path)); |
do { |
if (lstat(path, &st) == 0) { |
dev = st.st_dev; |
} else { |
char *ptr; |
ptr = strrchr(path, '/'); |
if (ptr) { |
*ptr = '\0'; |
} else { |
path[0] = '\0'; |
} |
} |
} while(dev == 0 && path[0] != '\0'); |
if (path[0] == '\0') { |
return -1; |
} |
settings->dev = dev; |
settings->dev_uuid = FSEventsCopyUUIDForDevice(settings->dev); |
if (settings->dev_uuid == NULL) { |
return -1; |
} |
if (statfs(path, &sfs) == 0) { |
strlcpy(settings->mount_point, sfs.f_mntonname, sizeof(settings->mount_point)); |
} else { |
CFRelease(settings->dev_uuid); |
settings->dev_uuid = NULL; |
return -1; |
} |
return 0; |
} |
// |
//-------------------------------------------------------------------------------- |
// |
#define STREAM_INFO_NAME "stream-info.txt" |
void |
save_stream_info(uint64_t last_id, CFUUIDRef uuid_ref) |
{ |
FILE *fp; |
CFStringRef cfstr; |
char uuid_str[64]; |
fp = fopen(STREAM_INFO_NAME, "w"); |
if (fp) { |
if (last_id == kFSEventStreamEventIdSinceNow || last_id == 0) { |
last_id = FSEventsGetCurrentEventId(); |
} |
printf("saving state: last_id %lld\n", last_id); |
fprintf(fp, "%lld\n", last_id); |
cfstr = CFUUIDCreateString(NULL, uuid_ref); |
if (cfstr && CFStringGetCString(cfstr, uuid_str, sizeof(uuid_str), kCFStringEncodingUTF8)) { |
fprintf(fp, "%s\n", uuid_str); |
} else { |
fprintf(fp, "unknown-uuid\n"); |
} |
fclose(fp); |
if (cfstr) { |
CFRelease(cfstr); |
} |
} |
} |
int |
load_stream_info(uint64_t *since_when, CFUUIDRef *uuid_ref_ptr) |
{ |
FILE *fp; |
char uuid_str[64]; |
CFStringRef cfstr; |
int ret=ENOMEM; |
fp = fopen(STREAM_INFO_NAME, "r"); |
if (fp == NULL) { |
return ENOENT; |
} |
if (fscanf(fp, "%lld\n", since_when) != 1) { |
printf("error getting last id.\n"); |
*since_when = kFSEventStreamEventIdSinceNow; |
} |
fscanf(fp, "%s\n", uuid_str); |
cfstr = CFStringCreateWithCString(NULL, uuid_str, kCFStringEncodingUTF8); |
if (cfstr) { |
*uuid_ref_ptr = CFUUIDCreateFromString(NULL, cfstr); |
if (*uuid_ref_ptr == NULL) { |
printf("failed to create the dev uuid from str: %s\n", uuid_str); |
*since_when = kFSEventStreamEventIdSinceNow; |
ret = EINVAL; |
} else { |
ret = 0; |
} |
CFRelease(cfstr); |
} |
fclose(fp); |
return ret; |
} |
// |
//-------------------------------------------------------------------------------- |
// |
void usage(const char *progname) |
{ |
printf("\n"); |
printf("Usage: %s <options> <path>\n", progname); |
printf("Options:\n"); |
printf(" -sinceWhen <when> Specify a time from whence to search for applicable events\n"); |
printf(" -latency <seconds> Specify latency\n"); |
printf("\n"); |
exit(-1); |
} |
void |
parse_settings(int argc, const char *argv[], settings_t *settings) |
{ |
int i; |
memset(settings, 0, sizeof(settings_t)); |
settings->since_when = kFSEventStreamEventIdSinceNow; |
settings->latency = 0.5; |
for (i=1; i < argc; i++) { |
if (strcmp(argv[i], "-usage") == 0) { |
usage(argv[0]); |
} else if (strcmp(argv[i], "-since_when") == 0) { |
settings->since_when = strtoull(argv[++i], NULL, 0); |
} else if (strcmp(argv[i], "-latency") == 0) { |
settings->latency = strtod(argv[++i], NULL); |
} else { |
// Done parsing flags, the rest of the arguments must be paths. |
break; |
} |
} |
if (i < argc) { |
settings->fullpath = argv[i]; |
} else { |
settings->fullpath = NULL; |
} |
} |
// |
//-------------------------------------------------------------------------------- |
// Simple wrapper to create a CFArray that contains a single |
// CFString in it (in this program it's the path we want to |
// watch). |
// |
// |
CFMutableArrayRef |
create_cfarray_from_path(const char *path) |
{ |
CFMutableArrayRef cfArray; |
cfArray = CFArrayCreateMutable(kCFAllocatorDefault, 1, &kCFTypeArrayCallBacks); |
if (cfArray == NULL) { |
fprintf(stderr, "%s: ERROR: CFArrayCreateMutable() => NULL\n", __FUNCTION__); |
return NULL; |
} |
CFStringRef cfStr = CFStringCreateWithCString(kCFAllocatorDefault, path, kCFStringEncodingUTF8); |
if (cfStr == NULL) { |
CFRelease(cfArray); |
return NULL; |
} |
CFArraySetValueAtIndex(cfArray, 0, cfStr); |
CFRelease(cfStr); |
return cfArray; |
} |
// |
//-------------------------------------------------------------------------------- |
// Routines to keep track of the size of the directory hierarchy |
// we are watching. |
// |
// This code is not exemplary in any way. It works but is |
// probably not the best way to keep track of directory |
// state. |
// |
typedef struct dir_item { |
char *dirname; |
short int depth; |
short int state; |
off_t size; |
} dir_item; |
dir_item *dir_items=NULL; |
int num_dir_items=0; |
int max_dir_items=0; |
#define DIR_ITEM_INCR 128 |
static int |
compare_dir_items(const void *_a, const void *_b) |
{ |
dir_item *a = (dir_item *)_a, *b = (dir_item *)_b; |
if (a->dirname == NULL) { |
if (b->dirname == NULL) { |
return 0; |
} |
return 1; |
} else if (b->dirname == NULL) { |
return -1; |
} |
return strcmp(a->dirname, b->dirname); |
} |
static int |
add_dir_item(const char *name, off_t size, int depth) |
{ |
if (num_dir_items+1 >= max_dir_items) { |
dir_item *new; |
new = (dir_item *)realloc(dir_items, (max_dir_items+DIR_ITEM_INCR)*sizeof(dir_item)); |
if (new == NULL) { |
return ENOSPC; |
} |
dir_items = new; |
max_dir_items += DIR_ITEM_INCR; |
} |
dir_items[num_dir_items].dirname = strdup(name); |
dir_items[num_dir_items].depth = depth; |
dir_items[num_dir_items].state = 0; |
dir_items[num_dir_items].size = size; |
num_dir_items++; |
return 0; |
} |
void |
discard_all_dir_items(void) |
{ |
int i; |
for(i=0; i < num_dir_items; i++) { |
if (dir_items[i].dirname) { |
free(dir_items[i].dirname); |
dir_items[i].dirname = NULL; |
} |
dir_items[i].size = 0; |
} |
num_dir_items = 0; |
} |
int |
save_dir_items(const char *name) |
{ |
int i; |
FILE *fp; |
fp = fopen(name, "w"); |
if (fp == NULL) { |
printf("can't create %s\n", name); |
return -1; |
} |
for(i=0; i < num_dir_items; i++) { |
if (dir_items[i].dirname) { |
fprintf(fp, "%d %lld %s\n", (int)dir_items[i].depth, dir_items[i].size, dir_items[i].dirname); |
} |
} |
fclose(fp); |
return 0; |
} |
int |
load_dir_items(const char *name) |
{ |
FILE *fp; |
char buff[MAXPATHLEN]; |
int depth; |
off_t size; |
fp = fopen(name, "r"); |
if (fp == NULL) { |
printf("can't read %s\n", name); |
return -1; |
} |
while(fscanf(fp, "%d %lld %s\n", &depth, &size, buff) == 3) { |
add_dir_item(buff, size, depth); |
} |
fclose(fp); |
return 0; |
} |
static int |
update_dir_item(const char *name, off_t size) |
{ |
int i; |
for(i=0; i < num_dir_items; i++) { |
if (dir_items[i].dirname != NULL && strcmp(name, dir_items[i].dirname) == 0) { |
dir_items[i].size = size; |
return 0; |
} |
} |
return ENOENT; |
} |
static void |
cleanup_dir_items(void) |
{ |
int i; |
// |
// resort the list and then cleanup any dead guys. |
// |
qsort(dir_items, num_dir_items, sizeof(dir_item), compare_dir_items); |
for(i=num_dir_items-1; i >= 0; i--) { |
if (dir_items[i].dirname == NULL) { |
num_dir_items--; |
} |
} |
} |
int |
remove_dir_and_children(const char *name) |
{ |
int i, start_depth; |
for(i=0; i < num_dir_items; i++) { |
if (dir_items[i].dirname != NULL && strcmp(name, dir_items[i].dirname) == 0) { |
break; |
} |
} |
if (i >= num_dir_items) { |
return ENOENT; |
} |
start_depth = dir_items[i].depth; |
for(; i < num_dir_items; i++) { |
if (dir_items[i].depth <= start_depth) { |
break; |
} |
if (dir_items[i].dirname) { |
free(dir_items[i].dirname); |
dir_items[i].dirname = NULL; |
} |
dir_items[i].size = 0; |
} |
cleanup_dir_items(); |
return 0; |
} |
// note: this returns zero if the directory exists |
// otherwise it returns one (i.e. true). |
static int |
dir_does_not_exist(const char *name) |
{ |
int i; |
for(i=0; i < num_dir_items; i++) { |
if (dir_items[i].dirname != NULL && strcmp(name, dir_items[i].dirname) == 0 && dir_items[i].size != 0) { |
return 0; |
} |
} |
return 1; |
} |
// note: this returns zero if the directory did not |
// exist in the list |
static int |
get_dir_depth(const char *name) |
{ |
int i; |
for(i=0; i < num_dir_items; i++) { |
if (dir_items[i].dirname != NULL && strcmp(name, dir_items[i].dirname) == 0) { |
return dir_items[i].depth; |
} |
} |
return 0; |
} |
off_t |
get_total_size(void) |
{ |
int i; |
off_t size=0; |
for(i=0; i < num_dir_items; i++) { |
if (dir_items[i].dirname) { |
size += dir_items[i].size; |
} |
} |
return size; |
} |
static off_t |
iterate_subdirs(const char *dirname, int add, int recursive, int depth) |
{ |
char *fullpath; |
DIR *dir; |
struct dirent *dirent; |
struct stat st; |
off_t size=0, result=0; |
fullpath = malloc(PATH_MAX); |
if (fullpath == NULL) { |
return -1; |
} |
if (add) { |
add_dir_item(dirname, 0, depth); |
} |
if (depth == 0) { |
depth = get_dir_depth(dirname); |
} |
dir = opendir(dirname); |
if (dir == NULL) { |
if (errno == ENOENT) { // it may have been deleted. |
update_dir_item(dirname, 0); |
return 0; |
} |
printf("failed to opendir(%s) (%s)\n", dirname, strerror(errno)); |
return -1; |
} |
dirent = NULL; |
while ((dirent = readdir(dir)) != NULL) { |
if (strcmp(dirent->d_name, ".") == 0 || strcmp(dirent->d_name, "..") == 0) |
continue; |
snprintf(fullpath, PATH_MAX, "%s/%s", dirname, dirent->d_name); |
if (lstat(fullpath, &st) != 0) { |
printf("Error stating %s : %s\n", fullpath, strerror(errno)); |
continue; |
} |
size += st.st_size; |
if (S_ISDIR(st.st_mode) && (recursive || dir_does_not_exist(fullpath))) { |
result = iterate_subdirs(fullpath, add, 1, depth+1); |
if (result < 0) { |
printf("error getting size for %s\n", fullpath); |
} |
} |
} |
closedir(dir); |
free(fullpath); |
if (update_dir_item(dirname, size) == ENOENT) { |
add_dir_item(dirname, size, depth); |
} |
return size; |
} |
int |
check_children_of_dir(const char *dirname) |
{ |
int i, current_depth, start_idx, end_idx; |
struct stat st; |
off_t dir_size; |
DIR *dir; |
struct dirent *dirent; |
for(i=0; i < num_dir_items; i++) { |
if (dir_items[i].dirname != NULL && strcmp(dirname, dir_items[i].dirname) == 0) { |
break; |
} |
} |
if (i >= num_dir_items) { |
return -1; |
} |
current_depth = dir_items[i].depth; |
start_idx = i; |
i++; |
for(; i < num_dir_items; i++) { |
if (dir_items[i].depth <= current_depth) { |
break; |
} |
} |
end_idx = i; |
dir = opendir(dirname); |
if (dir == NULL) { |
if (errno == ENOENT) { |
for(i=start_idx; i < end_idx; i++) { |
dir_items[i].size = 0; |
} |
return 0; |
} |
} |
dir_size = 0; |
while((dirent = readdir(dir)) != NULL) { |
char fullpath[MAXPATHLEN]; |
if (strcmp(dirent->d_name, ".") == 0 || strcmp(dirent->d_name, "..") == 0) |
continue; |
snprintf(fullpath, MAXPATHLEN, "%s/%s", dirname, dirent->d_name); |
for(i=start_idx; i < end_idx; i++) { |
if (dir_items[i].dirname == NULL) { |
continue; |
} |
if (dir_items[i].depth == current_depth+1 && strcmp(dir_items[i].dirname, fullpath) == 0) { |
// flag that it exists |
dir_items[i].state = 1; |
break; |
} |
} |
if (lstat(fullpath, &st) == 0) { |
if (S_ISDIR(st.st_mode)) { |
if (i >= end_idx) { |
// printf("NEW item: %s\n", fullpath); |
iterate_subdirs(fullpath, 1, 1, current_depth+1); |
} |
dir_size += st.st_size; |
} else { |
dir_size += st.st_size; |
} |
} |
} |
i = start_idx; |
dir_items[i].size = dir_size; |
for(i=start_idx; i < end_idx; i++) { |
if (dir_items[i].depth == current_depth+1) { |
if (dir_items[i].dirname != NULL && dir_items[i].state == 0) { |
int j; |
// printf("DELETED item: %s\n", dir_items[i].dirname); |
// go through and clear out that directory and all of its |
// children. |
for(j=i; j < end_idx; j++) { |
if (j > i && dir_items[j].depth <= current_depth+1) { |
break; |
} else if (dir_items[j].dirname) { |
free(dir_items[j].dirname); |
dir_items[j].dirname = NULL; |
dir_items[j].size = 0; |
} |
} |
} else { |
dir_items[i].state = 0; |
} |
} |
} |
cleanup_dir_items(); |
// for(i=0; i < num_dir_items; i++) { |
// printf("%d %s\n", dir_items[i].depth, dir_items[i].dirname); |
// } |
// printf("--------------------------------------------\n"); |
return 0; |
} |
void |
scan_directory(const char *dirname, int add, int recursive, int depth) |
{ |
iterate_subdirs(dirname, add, recursive, depth); |
qsort(dir_items, num_dir_items, sizeof(dir_item), compare_dir_items); |
} |
// |
// ----------------- run loop signal handling stuff --------------------- |
// |
// This code provides a way for a command-line CFRunLoop based |
// application to exit cleanly when you press ^C or get sent a |
// signal like SIGHUP. |
// |
// It sets up signal handlers and then creates a kqueue to |
// watch for signal events. The kqueue is then plugged into |
// a CFFileDescriptorRef which calls a callback when there |
// is activity on the kqueue. The callback stops the run |
// loop and that lets everything shuts down cleanly. |
// |
CFFileDescriptorRef kq_cffd = NULL; |
CFRunLoopSourceRef kq_rl_src = NULL; |
int kq_fd = -1; |
static void |
sig_handler(int sig) |
{ |
// there's nothing to do in the signal handler... |
// the real work happens in kq_cffd_callback where |
// we can safely call CFRunLoopStop() |
} |
static void |
kq_cffd_callback(CFFileDescriptorRef kq_cffd, CFOptionFlags callBackTypes, void *info) |
{ |
CFRunLoopRef loop = (CFRunLoopRef)info; |
CFRunLoopStop(loop); |
} |
int |
setup_run_loop_signal_handler(CFRunLoopRef loop) |
{ |
CFFileDescriptorContext my_context; |
struct kevent kev[4]; |
signal(SIGINT, sig_handler); |
signal(SIGQUIT, sig_handler); |
signal(SIGTERM, sig_handler); |
signal(SIGHUP, sig_handler); |
kq_fd = kqueue(); |
if (kq_fd < 0) { |
return -1; |
} |
EV_SET(&kev[0], SIGINT, EVFILT_SIGNAL, EV_ADD, 0, 0, 0); |
EV_SET(&kev[1], SIGQUIT, EVFILT_SIGNAL, EV_ADD, 0, 0, 0); |
EV_SET(&kev[2], SIGTERM, EVFILT_SIGNAL, EV_ADD, 0, 0, 0); |
EV_SET(&kev[3], SIGHUP, EVFILT_SIGNAL, EV_ADD, 0, 0, 0); |
if (kevent(kq_fd, &kev[0], 4, NULL, 0, NULL) != 0) { |
close(kq_fd); |
return -1; |
} |
memset(&my_context, 0, sizeof(CFFileDescriptorContext)); |
my_context.info = (void *)loop; |
kq_cffd = CFFileDescriptorCreate(NULL, kq_fd, 1, kq_cffd_callback, &my_context); |
if (kq_cffd == NULL) { |
close(kq_fd); |
return -1; |
} |
kq_rl_src = CFFileDescriptorCreateRunLoopSource(NULL, kq_cffd, (CFIndex)0); |
if (kq_rl_src == NULL) { |
// Dispose the kq_cffd |
CFFileDescriptorInvalidate(kq_cffd); |
CFRelease(kq_cffd); |
kq_cffd = NULL; |
return -1; |
} |
CFRunLoopAddSource(loop, kq_rl_src, kCFRunLoopDefaultMode); |
CFFileDescriptorEnableCallBacks(kq_cffd, kCFFileDescriptorReadCallBack); |
return 0; |
} |
void |
cleanup_run_loop_signal_handler(CFRunLoopRef loop) |
{ |
CFRunLoopRemoveSource(loop, kq_rl_src, kCFRunLoopDefaultMode); |
CFFileDescriptorInvalidate(kq_cffd); |
CFRelease(kq_rl_src); |
CFRelease(kq_cffd); |
close(kq_fd); |
kq_rl_src = NULL; |
kq_cffd = NULL; |
kq_fd = -1; |
} |
Copyright © 2006 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2006-08-04