/*
** SSYNC: Simple minded filesystem sync utility
** Copyright (C) 2002 Michael W. Shaffer <mwshaffer@angrypot.com>
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.  
**
** You should have received a copy of the GNU General Public License
** along with this program (see the file COPYING). If not, write to:
**
** The Free Software Foundation, Inc.
** 59 Temple Place, Suite 330,
** Boston, MA  02111-1307  USA
*/

#include <unistd.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <dirent.h>
#include <utime.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "hash.h"
#include "list.h"
#include "conf.h"
#include "config_file.h"
#include "ssync.h"
#include "log.h"
#include "platform.h"

struct flags flags;
struct stats stats;
struct hash_table work;
struct hash_table dups;

enum {
	data_buffer_size = 65537
};

static unsigned char * data_buffer = NULL;

/* static unsigned long min (unsigned long a, unsigned long b); */
static unsigned long max (unsigned long a, unsigned long b);
static void add_bytes (unsigned long b);
static void sync_data (char *src, struct stat *s, char *dst, struct stat *d);
static void sync_meta (char *src, struct stat *s, char *dst, struct stat *d);
static void sync_time (char *src, struct stat *s, char *dst, struct stat *d);
static void load_dir (char *dir, struct hash_table *h);
static void check_type (char *src, struct stat *s, char *dst, struct stat *d);
static void process_dir (char *src, struct stat *s, char *dst, struct stat *d);
static void process_reg (char *src, struct stat *s, char *dst, struct stat *d);
static void process_lnk (char *src, struct stat *s, char *dst, struct stat *d);
static void recursive_unlink (char *path);

void get_flags (char *buf, unsigned long bufsize)
{
	if (!(buf && (bufsize > 5)))
		       goto EXIT;

	memset (buf, 0, bufsize);
	strncpy (buf, "dtmux", strlen ("dtmux"));

	if (flags.no_sync_data)
		buf[0] = 'D';
	if (flags.no_sync_time)
		buf[1] = 'T';
	if (flags.no_sync_meta)
		buf[2] = 'M';
	if (flags.update_only)
		buf[3] = 'U';
	if (flags.test)
		buf[4] = 'X';

EXIT:
	return;
}

void get_bytes (char *buf, unsigned long bufsize)
{
	if (!(buf && (bufsize > 0)))
		       goto EXIT;

	memset (buf, 0, bufsize);

	if (stats.ebytes > 0) {
		snprintf (buf, (bufsize - 1),
			"%ld.%ldEiB",
			stats.ebytes, (stats.pbytes / 103));
	} else if (stats.pbytes > 0) {
		snprintf (buf, (bufsize - 1),
			"%ld.%ldPiB",
			stats.pbytes, (stats.tbytes / 103));
	} else if (stats.tbytes > 0) {
		snprintf (buf, (bufsize - 1),
			"%ld.%ldTiB",
			stats.tbytes, (stats.gbytes / 103));
	} else if (stats.gbytes > 0) {
		snprintf (buf, (bufsize - 1),
			"%ld.%ldGiB",
			stats.gbytes, (stats.mbytes / 103));
	} else if (stats.mbytes > 0) {
		snprintf (buf, (bufsize - 1),
			"%ld.%ldMiB",
			stats.mbytes, (stats.kbytes / 103));
	} else if (stats.kbytes > 0) {
		snprintf (buf, (bufsize - 1),
			"%ld.%ldKiB",
			stats.kbytes, (stats.bytes / 103));
	} else {
		snprintf (buf, (bufsize - 1),
			"%ldB", stats.bytes);
	}

EXIT:
	return;
}

void ssync_shutdown (void) {
	LOG (info, "ssync shutdown");
	hash_table_free (&work);
	hash_table_free (&dups);
	if (data_buffer)
		free (data_buffer);

	return;
}

int ssync_startup (int argc, char **argv) {
	char msg[256];
	char sflags[6];
	char *cfg = NULL;
	char src_path[MAXPATHLEN + 1];
	char dst_path[MAXPATHLEN + 1];
	struct datum d;

	memset (&work, 0, sizeof (struct hash_table));
	hash_table_init (&work);

	memset (&dups, 0, sizeof (struct hash_table));
	dups.size = 2000;
	hash_table_init (&dups);

	parse_config_file (get_config_string ("work-file"), &work);

	memset (src_path, 0, sizeof (src_path));
	memset (dst_path, 0, sizeof (dst_path));

	if ((cfg = get_config_string ("src-path")))
		strncpy (src_path, cfg, (sizeof (src_path) - 1));
	if ((cfg = get_config_string ("dst-path")))
		strncpy (dst_path, cfg, (sizeof (dst_path) - 1));

	if ((strlen (src_path) > 0) && (strlen (dst_path) > 0)) {
		memset (&d, 0, sizeof (struct datum));
		d.key = src_path;
		d.ksize = strlen (src_path);
		d.val = dst_path;
		d.vsize = strlen (dst_path);
		hash_table_insert (&work, &d);
	}

	if (!(data_buffer = malloc (data_buffer_size * sizeof (unsigned char)))) {
		memset (msg, 0, sizeof (msg));
		snprintf (msg, (sizeof (msg) - 1),
			"ssync_startup: malloc() for data_buffer of %d bytes failed: %s",
			data_buffer_size, strerror (errno));
		LOG (fatal, msg);
		return -1;
	}

	memset (&flags, 0, sizeof (struct flags));

	if ((cfg = get_config_string ("no-sync-data")) &&
		((cfg[0] == 'y') || (cfg[0] == 'Y'))) {
		flags.no_sync_data++;
	}
	if ((cfg = get_config_string ("no-sync-time")) &&
		((cfg[0] == 'y') || (cfg[0] == 'Y'))) {
		flags.no_sync_time++;
	}
	if ((cfg = get_config_string ("no-sync-meta")) &&
		((cfg[0] == 'y') || (cfg[0] == 'Y'))) {
		flags.no_sync_meta++;
	}
	if ((cfg = get_config_string ("update-only")) &&
		((cfg[0] == 'y') || (cfg[0] == 'Y'))) {
		flags.update_only++;
	}
	if ((cfg = get_config_string ("test")) &&
		((cfg[0] == 'y') || (cfg[0] == 'Y'))) {
		flags.test++;
	}

	memset (msg, 0, sizeof (msg));
	snprintf (msg, (sizeof (msg) - 1),
		"ssync %s startup",
		SSYNC_VERSION);
	LOG (info, msg);

	memset (sflags, 0, sizeof (sflags));
	get_flags (sflags, sizeof (sflags));
	memset (msg, 0, sizeof (msg));
	snprintf (msg, (sizeof (msg) - 1),
		"flags: %s", sflags);
	LOG (info, msg);

	return 0;
}

void process (char *src, struct stat *s, char *dst, struct stat *d)
{
	char msg[256];

	if (!(src && s && dst && d))
		goto EXIT;

	if (S_ISDIR (s->st_mode)) {
		check_type (src, s, dst, d);
		process_dir (src, s, dst, d); 
	} else if (S_ISREG (s->st_mode)) {
		check_type (src, s, dst, d);
		process_reg (src, s, dst, d); 
	} else if (S_ISLNK (s->st_mode)) {
		check_type (src, s, dst, d);
		process_lnk (src, s, dst, d); 
	} else {
		memset (msg, 0, sizeof (msg));
		snprintf (msg, (sizeof (msg) - 1),
			"skipping node '%s' of type 0x%08x",
			src, (S_IFMT & s->st_mode));
		LOG (warn, msg); 
		stats.unknowns++;
	}

EXIT:
	return;
}

/*
static unsigned long min (unsigned long a, unsigned long b)
{
	return ((a < b) ? a : b);
}
*/

static unsigned long max (unsigned long a, unsigned long b)
{
	return ((a > b) ? a : b);
}

static void add_bytes (unsigned long b)
{
	if (b < 0)
		goto EXIT;

	stats.bytes += b;

	if (stats.bytes > 1024) {
		stats.kbytes += (stats.bytes / 1024);
		stats.bytes %= 1024;
	}
	if (stats.kbytes > 1024) {
		stats.mbytes += (stats.kbytes / 1024);
		stats.kbytes %= 1024;
	}
	if (stats.mbytes > 1024) {
		stats.gbytes += (stats.mbytes / 1024);
		stats.mbytes %= 1024;
	}
	if (stats.gbytes > 1024) {
		stats.tbytes += (stats.gbytes / 1024);
		stats.gbytes %= 1024;
	}
	if (stats.tbytes > 1024) {
		stats.pbytes += (stats.tbytes / 1024);
		stats.tbytes %= 1024;
	}
	if (stats.pbytes > 1024) {
		stats.ebytes += (stats.pbytes / 1024);
		stats.pbytes %= 1024;
	}

EXIT:
	return;
}

static void sync_data (char *src, struct stat *s, char *dst, struct stat *d)
{
	int sfd = -1;
	int dfd = -1;
	char msg[256];
	unsigned long count = 0;
	unsigned long bufsize = 0;

	if (flags.no_sync_data || !(src && s && dst && d))
		goto EXIT;

	memset (data_buffer, 0, data_buffer_size);

	bufsize = (max (s->st_blksize, d->st_blksize) + 1);
	if (bufsize < 1025)
		bufsize = 1025;
	if (bufsize > data_buffer_size)
		bufsize = data_buffer_size;

	if ((sfd = open (src, O_RDONLY)) < 0) {
		memset (msg, 0, sizeof (msg));
		snprintf (msg, (sizeof (msg) - 1),
			"sync_data: open() on node '%s' failed: %s",
			src, strerror (errno));
		LOG (warn, msg);
		stats.errors++;
		goto EXIT;
	}

	if (!flags.test) {
		if ((dfd = open (dst, O_CREAT|O_WRONLY|O_TRUNC, 0600)) < 0) {
			memset (msg, 0, sizeof (msg));
			snprintf (msg, (sizeof (msg) - 1),
				"sync_data: open() on node '%s' failed: %s",
				dst, strerror (errno));
			LOG (warn, msg);
			stats.errors++;
			goto EXIT;
		}
	}

	memset (data_buffer, 0, bufsize);
	while ((count = read (sfd, data_buffer, (bufsize - 1))) > 0) {
		if (!flags.test) {
			if (write (dfd, data_buffer, count) == count) {
				add_bytes (count);
			} else {
				memset (msg, 0, sizeof (msg));
				snprintf (msg, (sizeof (msg) - 1),
					"sync_data: write() on node '%s' failed: %s",
					dst, strerror (errno));
				LOG (warn, msg);
				stats.errors++;
				goto EXIT;
			}
		}
		memset (data_buffer, 0, bufsize);
	}

EXIT:
	if (sfd > 0)
		close (sfd);
	if (dfd > 0)
		close (dfd);
	return;
}

static void sync_meta (char *src, struct stat *s, char *dst, struct stat *d)
{
	char msg[256];

	if (flags.test || flags.no_sync_meta || !(src && s && dst && d))
		goto EXIT;

	if (chown_nofollow (dst, s->st_uid, s->st_gid) < 0) {
		memset (msg, 0, sizeof (msg));
		snprintf (msg, (sizeof (msg) - 1),
			"sync_meta: chown_nofollow() on node '%s' failed: %s",
			dst, strerror (errno));
		LOG (warn, msg);
		stats.errors++;
	}

	if (S_ISDIR (d->st_mode) || S_ISREG (d->st_mode)) {
		if (chmod (dst, (07777 & s->st_mode)) < 0) {
			memset (msg, 0, sizeof (msg));
			snprintf (msg, (sizeof (msg) - 1),
				"sync_meta: chmod() on node '%s' failed: %s",
				dst, strerror (errno));
			LOG (warn, msg);
			stats.errors++;
		}
	}

EXIT:
	return;
}

static void sync_time (char *src, struct stat *s, char *dst, struct stat *d)
{
	char msg[256];
	struct utimbuf u;

	if (flags.test || flags.no_sync_time || !(src && s && dst && d))
		goto EXIT;

	lstat (src, s);
	lstat (dst, d);

	memset (&u, 0, sizeof (struct utimbuf));
	u.actime = s->st_atime;
	u.modtime = s->st_mtime;
	if (utime (dst, &u) < 0) {
		memset (msg, 0, sizeof (msg));
		snprintf (msg, (sizeof (msg) - 1),
			"sync_time: utime() on node '%s' failed: %s",
			dst, strerror (errno));
		LOG (warn, msg);
		stats.errors++;
	}

	lstat (src, s);
	lstat (dst, d);

EXIT:
	return;
}

static void load_dir (char *dir, struct hash_table *h)
{
	DIR *dp = NULL;
	struct dirent *de = NULL;
	struct datum d;
	struct stat s;
	char path[MAXPATHLEN + 32];
	char msg[256];

	if (!(dir && h))
		goto EXIT;

	if (!(dp = opendir (dir))) {
		if (!flags.test) {
			memset (msg, 0, sizeof (msg));
			snprintf (msg, (sizeof (msg) - 1),
				"load_dir: opendir() on node '%s' failed: %s",
				dir, strerror (errno));
			LOG (warn, msg);
			stats.errors++;
		}
		goto EXIT;
	}

	memset (&d, 0, sizeof (struct datum));

	d.val = &s;
	d.vsize = sizeof (struct stat);

	while ((de = readdir (dp))) {
		if ((!de->d_name) || (!strcmp (".", de->d_name)) || (!strcmp ("..", de->d_name)))
			continue;

		d.key = de->d_name;
		d.ksize = strlen (d.key);

		memset (path, 0, sizeof (path));
		snprintf (path, (sizeof (path) - 1),
			"%s/%s", dir, de->d_name);

		memset (&s, 0, sizeof (struct stat));
		if (lstat (path, &s) < 0) {
			memset (msg, 0, sizeof (msg));
			snprintf (msg, (sizeof (msg) - 1),
				"load_dir: lstat() on node '%s' failed: %s",
			path, strerror (errno));
			LOG (warn, msg);
		}
		hash_table_insert (h, &d);
	}

EXIT:
	if (dp)
		closedir (dp);
	return;
}

static void check_type (char *src, struct stat *s, char *dst, struct stat *d)
{
	int fd = -1;
	char msg[256];
	struct utimbuf u;

	if ((flags.test || !(src && s && dst && d)) ||
		((S_IFMT & s->st_mode) == (S_IFMT & d->st_mode)) ||
		(flags.update_only && !(s->st_mtime > d->st_mtime))) {
		goto EXIT;
	}

	recursive_unlink (dst);
	memset (&u, 0, sizeof (struct utimbuf));

	if (S_ISDIR (s->st_mode)) {
		if (mkdir (dst, 0700) < 0) {
			memset (msg, 0, sizeof (msg));
			snprintf (msg, (sizeof (msg) - 1),
				"check_type: mkdir() on node '%s' failed: %s",
				dst, strerror (errno));
			LOG (warn, msg);
			stats.errors++;
			goto EXIT;
		}

		if (utime (dst, &u) < 0) {
			memset (msg, 0, sizeof (msg));
			snprintf (msg, (sizeof (msg) - 1),
				"check_type: utime() on node '%s' failed: %s",
				dst, strerror (errno));
			LOG (warn, msg);
			stats.errors++;
			goto EXIT;
		}
	} else if (S_ISREG (s->st_mode)) {
		if ((fd = open (dst, O_CREAT|O_WRONLY|O_TRUNC, 0600)) < 0) {
			memset (msg, 0, sizeof (msg));
			snprintf (msg, (sizeof (msg) - 1),
				"check_type: open() on node '%s' failed: %s",
				dst, strerror (errno));
			LOG (warn, msg);
			stats.errors++;
			goto EXIT;
		}
		close (fd);

		if (utime (dst, &u) < 0) {
			memset (msg, 0, sizeof (msg));
			snprintf (msg, (sizeof (msg) - 1),
				"check_type: utime() on node '%s' failed: %s",
				dst, strerror (errno));
			LOG (warn, msg);
			stats.errors++;
			goto EXIT;
		}
	} else if (S_ISLNK (s->st_mode)) {
		if (symlink (".", dst) < 0) {
			memset (msg, 0, sizeof (msg));
			snprintf (msg, (sizeof (msg) - 1),
				"check_type: symlink() on node '%s' failed: %s",
				dst, strerror (errno));
			LOG (warn, msg);
			stats.errors++;
			goto EXIT;
		}
	} else {
		goto EXIT;
	}


	lstat (dst, d);

EXIT:
	return;
}

static void process_dir (char *src, struct stat *s, char *dst, struct stat *d)
{
	int i = 0;
	struct hash_table sdir;
	struct hash_table ddir;
	struct list_item *curr = NULL;
	struct stat t;
	struct stat *st = NULL;
	struct stat *dt = NULL;
	struct datum *sd = NULL;
	struct datum *dd = NULL;
	char *sname = NULL;
	char *dname = NULL;
	char spath[MAXPATHLEN + 32];
	char dpath[MAXPATHLEN + 32];
	int f_update = 0;
	int f_newerdir = 0;
	char msg[256];

	memset (&sdir, 0, sizeof (struct hash_table));
	memset (&ddir, 0, sizeof (struct hash_table));
	hash_table_init (&sdir);
	hash_table_init (&ddir);

	if (!(src && s && dst && d))
		goto EXIT;

	load_dir (src, &sdir);
	load_dir (dst, &ddir);

	if (s->st_mtime > d->st_mtime) {
		f_newerdir++;
	}

	if (!flags.update_only) {
		for (i = 0 ; i < ddir.size ; i++) {
			for (curr = ddir.tbl[i].head ; curr ; curr = curr->next) {
				if (!(dd = (struct datum *) curr->data))
					continue;

				if (!(sd = hash_table_search (&sdir, dd))) {
					dname = (char *) dd->key;
					memset (dpath, 0, sizeof (dpath));
					snprintf (dpath, (sizeof (dpath) - 1),
						"%s/%s", dst, dname);
					recursive_unlink (dpath);

					memset (msg, 0, sizeof (msg));
					snprintf (msg, (sizeof (msg) - 1),
						"deleted: %s", dpath);
					LOG (trace, msg);
					stats.deleted++;
				}
			}
		}
	}

	for (i = 0 ; i < sdir.size ; i++) {
		for (curr = sdir.tbl[i].head ; curr ; curr = curr->next) {
			if (!(sd = (struct datum *) curr->data))
				continue;

			sname = (char *) sd->key;
			st = (struct stat *) sd->val;
			memset (spath, 0, sizeof (spath));
			snprintf (spath, (sizeof (spath) - 1),
				"%s/%s", src, sname);

			if ((dd = hash_table_search (&ddir, sd))) {
				dname = (char *) dd->key;
				dt = (struct stat *) dd->val;
				memset (dpath, 0, sizeof (dpath));
				snprintf (dpath, (sizeof (dpath) - 1),
					"%s/%s", dst, dname);
			} else {
				memset (dpath, 0, sizeof (dpath));
				snprintf (dpath, (sizeof (dpath) - 1),
					"%s/%s", dst, sname);
				memset (&t, 0, sizeof (struct stat));
				dt = &t;
			}

			process (spath, st, dpath, dt);
		}
	}

	if (flags.update_only) {
		if (f_newerdir) {
			sync_time (src, s, dst, d);

			if (!((s->st_uid == d->st_uid) &&
				(s->st_gid == d->st_gid) &&
				(s->st_mode == d->st_mode))) {	
				sync_meta (src, s, dst, d);
			}

			f_update++;
		}
	} else {
		if (!(s->st_mtime == d->st_mtime)) {
			sync_time (src, s, dst, d);
			f_update++;
		}
		if (!((s->st_uid == d->st_uid) &&
			(s->st_gid == d->st_gid) &&
			(s->st_mode == d->st_mode))) {	
			sync_meta (src, s, dst, d);
			f_update++;
		}
	}

	if (f_update) {
		memset (msg, 0, sizeof (msg));
		snprintf (msg, (sizeof (msg) - 1),
			"updated: %s", dst);
		LOG (trace, msg);
		stats.dirs_updated++;
	}

	stats.dirs++;

EXIT:
	hash_table_free (&sdir);
	hash_table_free (&ddir);
	return;
}

struct ino_key {
	dev_t dev;
	ino_t ino;
};

static void process_reg (char *src, struct stat *s, char *dst, struct stat *d)
{
	char msg[256];
	char path[MAXPATHLEN + 32];
	struct datum curr;
	struct datum *orig = NULL;
	struct ino_key ik;
	int f_update = 0;

	if (!(src && s && dst && d))
		goto EXIT;

	if (s->st_nlink > 1) {
		memset (&curr, 0, sizeof (struct datum));

		curr.key = &ik;
		ik.dev = s->st_dev;
		ik.ino = s->st_ino;
		curr.ksize = sizeof (struct ino_key);

		curr.val = path;
		memset (path, 0, sizeof (path));
		snprintf (path, (sizeof (path) - 1),
			"%s", dst);
		curr.vsize = strlen (curr.val);

		if ((orig = hash_table_search (&dups, &curr))) {
			if (orig->val && (strlen (orig->val) > 0)) {
				recursive_unlink (dst);
				if (link ((char *) orig->val, dst) < 0) {
					memset (msg, 0, sizeof (msg));
					snprintf (msg, (sizeof (msg) - 1),
						"process_reg: link() %s --> %s failed: %s",
					dst, (char *) orig->val, strerror (errno));
					LOG (warn, msg);
					stats.errors++;
					goto EXIT;
				}
				stats.links++;
			}
			goto EXIT;
		} else {
			hash_table_insert (&dups, &curr);
		}
	}

	if (flags.update_only) {
		if (s->st_mtime > d->st_mtime) {
			sync_data (src, s, dst, d);
			sync_time (src, s, dst, d);

			if (!((s->st_uid == d->st_uid) &&
				(s->st_gid == d->st_gid) &&
				(s->st_mode == d->st_mode))) {	
				sync_meta (src, s, dst, d);
			}

			f_update++;
		}
	} else {
		if (!((s->st_mtime == d->st_mtime) &&
			(s->st_size == d->st_size))) {
			sync_data (src, s, dst, d);
			sync_time (src, s, dst, d);
			f_update++;
		}
		if (!((s->st_uid == d->st_uid) &&
			(s->st_gid == d->st_gid) &&
			(s->st_mode == d->st_mode))) {	
			sync_meta (src, s, dst, d);
			f_update++;
		}
	}

	if (f_update) {
		memset (msg, 0, sizeof (msg));
		snprintf (msg, (sizeof (msg) - 1),
			"updated: %s", dst);
		LOG (trace, msg);
		stats.regs_updated++;
	}

	stats.files++;

EXIT:
	return;
}

static void process_lnk (char *src, struct stat *s, char *dst, struct stat *d)
{
	char msg[256];
	char buf0[MAXPATHLEN + 32];
	char buf1[MAXPATHLEN + 32];

	if (!(src && s && dst && d))
		goto EXIT;

	memset (buf0, 0, sizeof (buf0));
	if (readlink (src, buf0, (sizeof (buf0) - 1)) < 0) {
		memset (msg, 0, sizeof (msg));
		snprintf (msg, (sizeof (msg) - 1),
			"process_lnk: readlink() on node '%s' failed: %s",
			src, strerror (errno));
		LOG (warn, msg);
		stats.errors++;
		goto EXIT;
	}

	memset (buf1, 0, sizeof (buf1));
	if (readlink (dst, buf1, (sizeof (buf1) - 1)) < 0) {
		memset (msg, 0, sizeof (msg));
		snprintf (msg, (sizeof (msg) - 1),
			"process_lnk: readlink() on node '%s' failed: %s",
			src, strerror (errno));
		LOG (warn, msg);
		stats.errors++;
		goto EXIT;
	}

	stats.links++;

	if (!strcmp (buf0, buf1))
		goto EXIT;

	if (!flags.test) {
		recursive_unlink (dst);

		if (symlink (buf0, dst) < 0) {
			memset (msg, 0, sizeof (msg));
			snprintf (msg, (sizeof (msg) - 1),
				"process_lnk: symlink() %s --> %s failed: %s",
				dst, buf0, strerror (errno));
			LOG (warn, msg);
			stats.errors++;
			goto EXIT;
		}
	}

	sync_meta (src, s, dst, d);

	memset (msg, 0, sizeof (msg));
	snprintf (msg, (sizeof (msg) - 1),
		"updated: %s", dst);
	LOG (trace, msg);

	stats.lnks_updated++;

EXIT:
	return;
}

static void recursive_unlink (char *path)
{
	int i = 0;
	struct stat s;
	struct hash_table dir;
	struct datum *d = NULL;
	struct list_item *curr = NULL;
	char cpath[MAXPATHLEN + 32];
	char msg[256];

	memset (&dir, 0, sizeof (struct hash_table));
	hash_table_init (&dir);

	if (flags.test || !(path && (strlen (path) > 0)))
		goto EXIT;

	memset (&s, 0, sizeof (struct stat));
	if (lstat (path, &s) < 0)
		goto EXIT;

	if (S_ISDIR (s.st_mode)) {
		load_dir (path, &dir);

		for (i = 0 ; i < dir.size ; i++) {
			for (curr = dir.tbl[i].head ; curr ; curr = curr->next) {
				if (!(d = (struct datum *) curr->data))
					continue;

				memset (cpath, 0, sizeof (cpath));
				snprintf (cpath, (sizeof (cpath) - 1),
					"%s/%s", path, (char *) d->key);
				recursive_unlink (cpath);
			}
		}

		if (rmdir (path) < 0) {
			memset (msg, 0, sizeof (msg));
			snprintf (msg, (sizeof (msg) - 1),
				"recursive_unlink: rmdir() on node '%s' failed: %s",
				path, strerror (errno));
			LOG (warn, msg);
			stats.errors++;
		}
	} else {
		if (unlink (path) < 0) {
			memset (msg, 0, sizeof (msg));
			snprintf (msg, (sizeof (msg) - 1),
				"recursive_unlink: unlink() on node '%s' failed: %s",
				path, strerror (errno));
			LOG (warn, msg);
			stats.errors++;
		}
	}

EXIT:
	hash_table_free (&dir);
	return;
}



syntax highlighted by Code2HTML, v. 0.9.1