/* ** SSYNC: Simple minded filesystem sync utility ** Copyright (C) 2002 Michael W. Shaffer ** ** 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #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; }