/* remsync 1.5 - remotely synchronize file trees Author: Kees J. Bot * 10 Jun 1994 */ #define nil 0 #include #include #include #include #include #include #include #include #include #include #include #include #include #define arraysize(a) (sizeof(a) / sizeof((a)[0])) #define arraylimit(a) ((a) + arraysize(a)) #ifndef major #define major(dev) ((int) ((dev) >> 8)) #define minor(dev) ((int) ((dev) & 0xFF)) #endif #ifndef S_ISLNK /* There were no symlinks in medieval times. */ #define S_ISLNK(mode) (0) #define lstat stat #define symlink(path1, path2) (errno= ENOSYS, -1) #define readlink(path, buf, len) (errno= ENOSYS, -1) #endif int sflag; /* Make state file. */ int dflag; /* Make list of differences. */ int uflag; /* Only update files with newer versions. */ int xflag; /* Do not cross device boundaries. */ int Dflag; /* Debug: Readable differences, no file contents. */ int vflag; /* Verbose. */ #define NO_DEVICE (-1) dev_t xdev= NO_DEVICE; /* The device that you should stay within. */ int excode= 0; /* Exit(excode); */ #define BASE_INDENT 2 /* State file basic indent. */ void report(const char *label) { fprintf(stderr, "remsync: %s: %s\n", label, strerror(errno)); excode= 1; } void fatal(const char *label) { report(label); exit(1); } void *allocate(void *mem, size_t size) { if ((mem= mem == nil ? malloc(size) : realloc(mem, size)) == nil) { fprintf(stderr, "remsync: Out of memory: %s\n", strerror(errno)); exit(1); } return mem; } void deallocate(void *mem) { if (mem != nil) free(mem); } /* One needs to slowly forget two sets of objects: for the code that reads * the state file, and for the code that traverses trees. */ int keep; #define KEEP_STATE 0 #define KEEP_TRAVERSE 1 void forget(void *mem) /* Some objects must be deleted in time, but not just yet. */ { static void *death_row[2][50]; static void **dp[2]= { death_row[0], death_row[1] }; deallocate(*dp[keep]); *dp[keep]++= mem; if (dp[keep] == arraylimit(death_row[keep])) dp[keep]= death_row[keep]; } char *copystr(const char *s) { char *c= allocate(nil, (strlen(s) + 1) * sizeof(c[0])); strcpy(c, s); return c; } typedef struct pathname { char *path; /* The actual pathname. */ size_t idx; /* Index for the terminating null byte. */ size_t lim; /* Actual length of the path array. */ } pathname_t; void path_init(pathname_t *pp) /* Initialize a pathname to the null string. */ { pp->path= allocate(nil, (pp->lim= 16) * sizeof(pp->path[0])); pp->path[pp->idx= 0]= 0; } void path_add(pathname_t *pp, const char *name) /* Add a component to a pathname. */ { size_t lim; char *p; int slash; lim= pp->idx + strlen(name) + 2; if (lim > pp->lim) { pp->lim= lim + lim/2; /* add an extra 50% growing space. */ pp->path= allocate(pp->path, pp->lim * sizeof(pp->path[0])); } p= pp->path + pp->idx; slash= (pp->idx > 0); if (pp->idx == 1 && p[-1] == '/') p--; while (*name != 0) { if (*name == '/') { slash= 1; } else { if (slash) { *p++ = '/'; slash= 0; } *p++= *name; } name++; } if (slash && p == pp->path) *p++= '/'; *p = 0; pp->idx= p - pp->path; } void path_trunc(pathname_t *pp, size_t didx) /* Delete part of a pathname to a remembered length. */ { pp->path[pp->idx= didx]= 0; } #if kept_for_comments_only const char *path_name(const pathname_t *pp) /* Return the actual name as a char array. */ { return pp->path; } size_t path_length(const pathname_t *pp) /* The length of the pathname. */ { return pp->idx; } void path_drop(pathname_t *pp) /* Release the storage occupied by the pathname. */ { free(pp->path); } #endif #define path_name(pp) ((const char *) (pp)->path) #define path_length(pp) ((pp)->idx) #define path_drop(pp) free((void *) (pp)->path) typedef struct namelist { /* Obviously a list of names. */ struct namelist *next; char *name; } namelist_t; char *rdlink(const char *link, off_t size) /* Look where "link" points. */ { static char *path= nil; static size_t len= 0; size_t n; if (len <= size) { path= allocate(path, (len= size * 2) * sizeof(path[0])); } if ((n= readlink(link, path, len)) == -1) return nil; path[n]= 0; return path; } void sort(namelist_t **anl) /* A stable mergesort disguised as line noise. Must be called like this: * if (L!=nil && L->next!=nil) sort(&L); */ { /* static */ namelist_t *nl1, **mid; /* Need not be local */ namelist_t *nl2; nl1= *(mid= &(*anl)->next); do { if ((nl1= nl1->next) == nil) break; mid= &(*mid)->next; } while ((nl1= nl1->next) != nil); nl2= *mid; *mid= nil; if ((*anl)->next != nil) sort(anl); if (nl2->next != nil) sort(&nl2); nl1= *anl; for (;;) { if (strcmp(nl1->name, nl2->name)<=0) { if ((nl1= *(anl= &nl1->next)) == nil) { *anl= nl2; break; } } else { *anl= nl2; nl2= *(anl= &nl2->next); *anl= nl1; if (nl2 == nil) break; } } } namelist_t *collect(const char *dir) /* Return a sorted list of directory entries. Returns null with errno != 0 * on error. */ { namelist_t *names, **pn= &names; DIR *dp; struct dirent *entry; if ((dp= opendir(dir)) == nil) return nil; while ((entry= readdir(dp)) != nil) { if (entry->d_name[0] == '.' && (entry->d_name[1] == 0 || (entry->d_name[1] == '.' && entry->d_name[2] == 0))) { continue; } *pn= allocate(nil, sizeof(**pn)); (*pn)->name= copystr(entry->d_name); pn= &(*pn)->next; } closedir(dp); *pn= nil; errno= 0; if (names != nil && names->next != nil) sort(&names); return names; } char *pop_name(namelist_t **names) /* Return one name of a name list. */ { char *name; namelist_t *junk; junk= *names; *names= junk->next; name= junk->name; deallocate(junk); forget(name); return name; } typedef enum filetype { /* The files we know about. */ F_DIR, F_FILE, F_BLK, F_CHR, F_PIPE, F_LINK } filetype_t; typedef struct entry { /* One file. */ int depth; /* Depth in directory tree. */ const char *name; /* Name of entry. */ const char *path; /* Path name. */ int ignore; /* Ignore this entry (errno number.) */ unsigned long fake_ino; /* Fake inode number for hard links. */ int linked; /* Is the file hard linked? */ int lastlink; /* Is it the last link? */ char *link; /* Where a (sym)link points to. */ filetype_t type; mode_t mode; /* Not unlike those in struct stat. */ uid_t uid; gid_t gid; off_t size; time_t mtime; dev_t rdev; } entry_t; void linked(entry_t *entry, struct stat *stp) /* Return an "inode number" if a file could have links (link count > 1). * Also return a path to the first link if you see the file again. */ { static unsigned long new_fake_ino= 0; static struct links { struct links *next; char *path; ino_t ino; dev_t dev; nlink_t nlink; unsigned long fake_ino; } *links[1024]; struct links **plp, *lp; entry->linked= entry->lastlink= 0; entry->fake_ino= 0; entry->link= nil; if (S_ISDIR(stp->st_mode) || stp->st_nlink < 2) return; plp= &links[stp->st_ino % arraysize(links)]; while ((lp= *plp) != nil && (lp->ino != stp->st_ino || lp->dev != stp->st_dev)) plp= &lp->next; if (lp == nil) { /* New file, store it with a new fake inode number. */ *plp= lp= allocate(nil, sizeof(*lp)); lp->next= nil; lp->path= copystr(entry->path); lp->ino= stp->st_ino; lp->dev= stp->st_dev; lp->nlink= stp->st_nlink; lp->fake_ino= ++new_fake_ino; } else { entry->link= lp->path; entry->linked= 1; } entry->fake_ino= lp->fake_ino; if (--lp->nlink == 0) { /* No need to remember this one, no more links coming. */ *plp= lp->next; forget(lp->path); deallocate(lp); entry->lastlink= 1; } } char *tree; /* Tree to work on. */ FILE *statefp; /* State file. */ char *state_file; FILE *difffp; /* File of differences. */ char *diff_file; entry_t *traverse(void) /* Get one name from the directory tree. */ { static int depth; static pathname_t path; static entry_t entry; static namelist_t **entries; static size_t *trunc; static size_t deep; static namelist_t *newentries; struct stat st; recurse: keep= KEEP_TRAVERSE; if (deep == 0) { /* Initialize for the root of the tree. */ path_init(&path); path_add(&path, tree); entries= allocate(nil, 1 * sizeof(entries[0])); entries[0]= allocate(nil, sizeof(*entries[0])); entries[0]->next= nil; entries[0]->name= copystr("/"); trunc= allocate(nil, 1 * sizeof(trunc[0])); trunc[0]= path_length(&path); deep= 1; } else if (newentries != nil) { /* Last entry was a directory, need to go down. */ if (entry.ignore) { /* Ouch, it is to be ignored! */ while (newentries != nil) (void) pop_name(&newentries); goto recurse; } if (++depth == deep) { deep++; entries= allocate(entries, deep * sizeof(entries[0])); trunc= allocate(trunc, deep * sizeof(trunc[0])); } entries[depth]= newentries; newentries= nil; trunc[depth]= path_length(&path); } else { /* Pop up out of emptied directories. */ while (entries[depth] == nil) { if (depth == 0) return nil; /* Back at the root. */ /* Go up one level. */ depth--; } } entry.name= pop_name(&entries[depth]); path_trunc(&path, trunc[depth]); path_add(&path, entry.name); if (depth == 0) { entry.path= "/"; } else { entry.path= path_name(&path) + trunc[0]; if (entry.path[0] == '/') entry.path++; } entry.depth= depth; entry.ignore= 0; if (lstat(path_name(&path), &st) < 0) { if (depth == 0 || errno != ENOENT) { /* Something wrong with this entry, complain about * it and ignore it further. */ entry.ignore= errno; report(path_name(&path)); return &entry; } else { /* Entry strangely nonexistent; simply continue. */ goto recurse; } } /* Don't cross mountpoints if -x is set. */ if (xflag) { if (xdev == NO_DEVICE) xdev= st.st_dev; if (st.st_dev != xdev) { /* Ignore the mountpoint. */ entry.ignore= EXDEV; return &entry; } } entry.mode= st.st_mode & 07777; entry.uid= st.st_uid; entry.gid= st.st_gid; entry.size= st.st_size; entry.mtime= st.st_mtime; entry.rdev= st.st_rdev; linked(&entry, &st); if (S_ISDIR(st.st_mode)) { /* A directory. */ entry.type= F_DIR; /* Gather directory entries for the next traverse. */ if ((newentries= collect(path_name(&path))) == nil && errno != 0) { entry.ignore= errno; report(path_name(&path)); } } else if (S_ISREG(st.st_mode)) { /* A plain file. */ entry.type= F_FILE; } else if (S_ISBLK(st.st_mode)) { /* A block special file. */ entry.type= F_BLK; } else if (S_ISCHR(st.st_mode)) { /* A character special file. */ entry.type= F_CHR; } else if (S_ISFIFO(st.st_mode)) { /* A named pipe. */ entry.type= F_PIPE; } else if (S_ISLNK(st.st_mode)) { /* A symbolic link. */ entry.type= F_LINK; if ((entry.link= rdlink(path_name(&path), st.st_size)) == nil) { entry.ignore= errno; report(path_name(&path)); } } else { /* Unknown type of file. */ entry.ignore= EINVAL; } return &entry; } void checkstate(void) { if (ferror(statefp)) fatal(state_file); } void indent(int depth) /* Provide indentation to show directory depth. */ { int n= BASE_INDENT * (depth - 1); while (n >= 8) { if (putc('\t', statefp) == EOF) checkstate(); n-= 8; } while (n > 0) { if (putc(' ', statefp) == EOF) checkstate(); n--; } } int print_name(FILE *fp, const char *name) /* Encode a name. */ { const char *p; int c; for (p= name; (c= (unsigned char) *p) != 0; p++) { if (c <= ' ' || c == '\\') { fprintf(fp, "\\%03o", c); if (ferror(fp)) return 0; } else { if (putc(c, fp) == EOF) return 0; } } return 1; } void mkstatefile(void) /* Make a state file out of the directory tree. */ { entry_t *entry; while ((entry= traverse()) != nil) { indent(entry->depth); if (!print_name(statefp, entry->name)) checkstate(); if (entry->ignore) { fprintf(statefp, "\tignore (%s)\n", strerror(entry->ignore)); checkstate(); continue; } switch (entry->type) { case F_DIR: fprintf(statefp, "\td%03o %u %u", (unsigned) entry->mode, (unsigned) entry->uid, (unsigned) entry->gid); break; case F_FILE: fprintf(statefp, "\t%03o %u %u %lu %lu", (unsigned) entry->mode, (unsigned) entry->uid, (unsigned) entry->gid, (unsigned long) entry->size, (unsigned long) entry->mtime); break; case F_BLK: case F_CHR: fprintf(statefp, "\t%c%03o %u %u %x", entry->type == F_BLK ? 'b' : 'c', (unsigned) entry->mode, (unsigned) entry->uid, (unsigned) entry->gid, (unsigned) entry->rdev); break; case F_PIPE: fprintf(statefp, "\tp%03o %u %u", (unsigned) entry->mode, (unsigned) entry->uid, (unsigned) entry->gid); break; case F_LINK: fprintf(statefp, "\t-> "); checkstate(); (void) print_name(statefp, entry->link); break; } checkstate(); if (entry->fake_ino != 0) fprintf(statefp, " %lu", entry->fake_ino); if (entry->lastlink) fprintf(statefp, " last"); if (fputc('\n', statefp) == EOF) checkstate(); } fflush(statefp); checkstate(); } char *read1line(FILE *fp) /* Read one line from a file. Return null on EOF or error. */ { static char *line; static size_t len; size_t idx; int c; if (len == 0) line= allocate(nil, (len= 16) * sizeof(line[0])); idx= 0; while ((c= getc(fp)) != EOF && c != '\n') { if (c < '\t') { /* Control characters are not possible. */ fprintf(stderr, "remsync: control character in data file!\n"); exit(1); } line[idx++]= c; if (idx == len) { line= allocate(line, (len*= 2) * sizeof(line[0])); } } if (c == EOF) { if (ferror(fp)) return nil; if (idx == 0) return nil; } line[idx]= 0; return line; } void getword(char **pline, char **parg, size_t *plen) /* Get one word from a line, interpret octal escapes. */ { char *line= *pline; char *arg= *parg; size_t len= *plen; int i; int c; size_t idx; idx= 0; while ((c= *line) != 0 && c != ' ' && c != '\t') { line++; if (c == '\\') { c= 0; for (i= 0; i < 3; i++) { if ((unsigned) (*line - '0') >= 010) break; c= (c << 3) | (*line - '0'); line++; } } arg[idx++]= c; if (idx == len) arg= allocate(arg, (len*= 2) * sizeof(arg[0])); } arg[idx]= 0; *pline= line; *parg= arg; *plen= len; } void splitline(char *line, char ***pargv, size_t *pargc) /* Split a line into an array of words. */ { static char **argv; static size_t *lenv; static size_t len; size_t idx; idx= 0; for (;;) { while (*line == ' ' || *line == '\t') line++; if (*line == 0) break; if (idx == len) { len++; argv= allocate(argv, len * sizeof(argv[0])); lenv= allocate(lenv, len * sizeof(lenv[0])); argv[idx]= allocate(nil, 16 * sizeof(argv[idx][0])); lenv[idx]= 16; } getword(&line, &argv[idx], &lenv[idx]); idx++; } *pargv= argv; *pargc= idx; } int getattributes(entry_t *entry, int argc, char **argv) /* Convert state or difference file info into file attributes. */ { int i; int attr; #define A_MODE1 0x01 /* Some of these attributes follow the name */ #define A_MODE 0x02 #define A_OWNER 0x04 #define A_SIZETIME 0x08 #define A_DEV 0x10 #define A_LINK 0x20 switch (argv[0][0]) { case 'd': /* Directory. */ entry->type= F_DIR; attr= A_MODE1 | A_OWNER; break; case 'b': /* Block device. */ entry->type= F_BLK; attr= A_MODE1 | A_OWNER | A_DEV; break; case 'c': /* Character device. */ entry->type= F_CHR; attr= A_MODE1 | A_OWNER | A_DEV; break; case 'p': /* Named pipe. */ entry->type= F_PIPE; attr= A_MODE1 | A_OWNER; break; case '-': /* Symlink. */ entry->type= F_LINK; attr= A_LINK; break; default: /* Normal file. */ entry->type= F_FILE; attr= A_MODE | A_OWNER | A_SIZETIME; } if (attr & (A_MODE | A_MODE1)) { entry->mode= strtoul(argv[0] + (attr & A_MODE1), nil, 010); } i= 1; if (attr & A_OWNER) { if (i + 2 > argc) return 0; entry->uid= strtoul(argv[i++], nil, 10); entry->gid= strtoul(argv[i++], nil, 10); } if (attr & A_SIZETIME) { if (i + 2 > argc) return 0; entry->size= strtoul(argv[i++], nil, 10); entry->mtime= strtoul(argv[i++], nil, 10); } if (attr & A_DEV) { if (i + 1 > argc) return 0; entry->rdev= strtoul(argv[i++], nil, 0x10); } if (attr & A_LINK) { if (i + 1 > argc) return 0; entry->link= argv[i++]; } entry->linked= entry->lastlink= 0; if (i < argc) { /* It has a fake inode number, so it is a hard link. */ static struct links { /* List of hard links. */ struct links *next; unsigned long fake_ino; char *path; } *links[1024]; struct links **plp, *lp; unsigned long fake_ino; fake_ino= strtoul(argv[i++], nil, 10); plp= &links[fake_ino % arraysize(links)]; while ((lp= *plp) != nil && lp->fake_ino != fake_ino) plp= &lp->next; if (lp == nil) { /* New link. */ *plp= lp= allocate(nil, sizeof(*lp)); lp->next= nil; lp->fake_ino= fake_ino; lp->path= copystr(entry->path); } else { /* Linked to. */ entry->link= lp->path; entry->linked= 1; } if (i < argc) { if (strcmp(argv[i++], "last") != 0) return 0; /* Last hard link of a file. */ forget(lp->path); *plp= lp->next; deallocate(lp); entry->lastlink= 1; } } if (i != argc) return 0; return 1; } void state_syntax(off_t line) { fprintf(stderr, "remsync: %s: syntax error on line %lu\n", state_file, (unsigned long) line); exit(1); } entry_t *readstate(void) /* Read one entry from the state file. */ { static entry_t entry; static pathname_t path; static size_t *trunc; static size_t trunc_len; static base_indent; char *line; char **argv; size_t argc; static off_t lineno; int indent, depth; recurse: keep= KEEP_STATE; if (feof(statefp) || (line= read1line(statefp)) == nil) { checkstate(); return nil; } lineno++; /* How far is this entry indented? */ indent= 0; while (*line != 0) { if (*line == ' ') indent++; else if (*line == '\t') indent= (indent + 8) & ~7; else break; line++; } if (indent > 0 && base_indent == 0) base_indent= indent; depth= (base_indent == 0 ? 0 : indent / base_indent) + 1; if (entry.ignore && depth > entry.depth) { /* If the old directory is ignored, then so are its entries. */ goto recurse; } entry.depth= depth; splitline(line, &argv, &argc); if (argc < 2) state_syntax(lineno); if (trunc == nil) { /* The root of the tree, initialize path. */ if (argv[0][0] != '/') state_syntax(lineno); path_init(&path); path_add(&path, "/"); trunc= allocate(nil, (trunc_len= 16) * sizeof(trunc[0])); /* The root has depth 0. */ entry.depth= 0; trunc[0]= 0; } else { if (entry.depth > trunc_len) { trunc= allocate(trunc, (trunc_len*= 2) * sizeof(trunc[0])); } path_trunc(&path, trunc[entry.depth - 1]); path_add(&path, argv[0]); trunc[entry.depth]= path_length(&path); } entry.path= path_name(&path); entry.name= argv[0]; entry.link= nil; if ((entry.ignore= strcmp(argv[1], "ignore") == 0)) { return &entry; } if (!getattributes(&entry, argc - 1, argv + 1)) state_syntax(lineno); return &entry; } void checkdiff(void) { if (ferror(difffp)) fatal(diff_file); } enum { DELETE, REPLACE, COPY, SIMILAR, EQUAL, ADD } compare(entry_t *remote, entry_t *local) /* Compare the local and remote entries and tell what need to be done. */ { int cmp; /* Surplus entries? */ if (local == nil) return DELETE; if (remote == nil) return ADD; /* Extra directory entries? */ if (remote->depth > local->depth) return DELETE; if (local->depth > remote->depth) return ADD; /* Compare names. */ cmp= strcmp(remote->name, local->name); if (cmp < 0) return DELETE; if (cmp > 0) return ADD; /* The files have the same name. Ignore one, ignore the other. */ if (remote->ignore || local->ignore) { remote->ignore= local->ignore= 1; return EQUAL; } /* Reasons for replacement? */ if (remote->type != local->type) return REPLACE; /* Should be hard linked to the same file. */ if (remote->linked || local->linked) { if (!remote->linked || !local->linked) return REPLACE; if (strcmp(remote->link, local->link) != 0) return REPLACE; } switch (remote->type) { case F_FILE: if (uflag) { if (remote->mtime < local->mtime) return COPY; } else { if (remote->size != local->size || remote->mtime != local->mtime) return COPY; } goto check_modes; case F_BLK: case F_CHR: if (remote->rdev != local->rdev) return REPLACE; goto check_modes; case F_DIR: case F_PIPE: check_modes: if (remote->mode != local->mode || remote->uid != local->uid || remote->gid != local->gid) return SIMILAR; break; case F_LINK: if (strcmp(remote->link, local->link) != 0) return REPLACE; break; } return EQUAL; } void delete(entry_t *old) /* Emit an instruction to remove an entry. */ { if (old->ignore) return; if (uflag) return; fprintf(difffp, "rm "); checkdiff(); if (!print_name(difffp, old->path)) checkdiff(); if (putc('\n', difffp) == EOF) checkdiff(); if (vflag) fprintf(stderr, "rm %s\n", old->path); } void change_modes(entry_t *old, entry_t *new) /* Emit an instruction to change the attributes of an entry. */ { if (new->ignore) return; fprintf(difffp, "chmod "); checkdiff(); if (!print_name(difffp, new->path)) checkdiff(); fprintf(difffp, " %03o %u %u\n", (unsigned) new->mode, (unsigned) new->uid, (unsigned) new->gid); checkdiff(); if (vflag && old->mode != new->mode) { fprintf(stderr, "chmod %s %03o %u %u\n", new->path, (unsigned) new->mode, (unsigned) new->uid, (unsigned) new->gid); } } int cat(int f, off_t size) /* Include the contents of a file in the differences file. */ { ssize_t n; unsigned char buf[1024 << sizeof(int)]; unsigned char *p; int c; if (Dflag) return 1; /* Debug: Don't need the file contents. */ while ((n= read(f, buf, sizeof(buf))) > 0) { p= buf; do { if (size == 0) { /* File suddenly larger. */ errno= EINVAL; return 0; } c= *p++; if (putc(c, difffp) == EOF) checkdiff(); size--; } while (--n != 0); } if (size > 0) { int err= errno; /* File somehow shrunk, pad it out. */ do { if (putc(0, difffp) == EOF) checkdiff(); } while (--size != 0); errno= n == 0 ? EINVAL : err; n= -1; } return n == 0; } void add(entry_t *old, entry_t *new) /* Emit an instruction to add an entry. */ { pathname_t file; int f; if (new->ignore) return; if (new->linked) { /* This file is to be a hard link to an existing file. */ fprintf(difffp, "ln "); checkdiff(); if (!print_name(difffp, new->link)) checkdiff(); if (fputc(' ', difffp) == EOF) checkdiff(); if (!print_name(difffp, new->path)) checkdiff(); if (fputc('\n', difffp) == EOF) checkdiff(); if (vflag) { fprintf(stderr, "ln %s %s\n", new->link, new->path); } return; } /* Add some other type of file. */ fprintf(difffp, "add "); checkdiff(); if (!print_name(difffp, new->path)) checkdiff(); switch (new->type) { case F_DIR: fprintf(difffp, " d%03o %u %u\n", (unsigned) new->mode, (unsigned) new->uid, (unsigned) new->gid); if (vflag) fprintf(stderr, "mkdir %s\n", new->path); break; case F_FILE: path_init(&file); path_add(&file, tree); path_add(&file, new->path); if ((f= open(path_name(&file), O_RDONLY)) < 0) { report(path_name(&file)); path_drop(&file); fprintf(difffp, " ignore\n"); break; } fprintf(difffp, " %03o %u %u %lu %lu\n", (unsigned) new->mode, (unsigned) new->uid, (unsigned) new->gid, (unsigned long) new->size, (unsigned long) new->mtime); checkdiff(); if (!cat(f, new->size)) { int err= errno; report(path_name(&file)); fprintf(difffp, "old "); checkdiff(); print_name(difffp, err == EINVAL ? "File changed when copied" : strerror(err)); fputc('\n', difffp); checkdiff(); } else { if (vflag) { fprintf(stderr, "%s %s\n", old == nil ? "add" : old->mtime > new->mtime ? "restore" : "update", new->path); } } close(f); path_drop(&file); break; case F_BLK: case F_CHR: fprintf(difffp, " %c%03o %u %u %lx\n", new->type == F_BLK ? 'b' : 'c', (unsigned) new->mode, (unsigned) new->uid, (unsigned) new->gid, (unsigned long) new->rdev); if (vflag) fprintf(stderr, "mknod %s\n", new->path); break; case F_PIPE: fprintf(difffp, " p%03o %u %u\n", (unsigned) new->mode, (unsigned) new->uid, (unsigned) new->gid); if (vflag) fprintf(stderr, "mkfifo %s\n", new->path); break; case F_LINK: fprintf(difffp, " -> "); checkdiff(); (void) print_name(difffp, new->link); checkdiff(); fputc('\n', difffp); if (vflag) { fprintf(stderr, "ln -s %s %s\n", new->link, new->path); } break; } checkdiff(); } void mkdifferences(void) { entry_t *remote; entry_t *local; remote= readstate(); local= traverse(); while (remote != nil || local != nil) { switch (compare(remote, local)) { case DELETE: /* Remove the remote file. */ delete(remote); remote->ignore= 1; remote= readstate(); break; case REPLACE: /* Replace the remote file with the local one. */ if (remote->type == F_FILE && local->type == F_FILE && !local->linked) { /* Don't overwrite, remove first. */ delete(remote); } /*FALL THROUGH*/ case COPY: /* Overwrite the remote file with the local one. */ add(remote, local); remote->ignore= 1; goto skip2; case SIMILAR: /* About the same, but the attributes need changing. */ change_modes(remote, local); goto skip2; case EQUAL: skip2: /* Skip two files. */ remote= readstate(); local= traverse(); break; case ADD: /* Add the local file. */ add(nil, local); local= traverse(); break; } } fprintf(difffp, "end\n"); fflush(difffp); checkdiff(); } void apply_remove(pathname_t *pp) /* Remove an obsolete file. */ { struct stat st; if (lstat(path_name(pp), &st) < 0) { if (errno != ENOENT) report(path_name(pp)); return; } if (S_ISDIR(st.st_mode)) { /* Recursively delete directories. */ size_t len; namelist_t *entries; if ((entries= collect(path_name(pp))) == nil && errno != 0) { report(path_name(pp)); return; } len= path_length(pp); while (entries != nil) { path_add(pp, pop_name(&entries)); apply_remove(pp); path_trunc(pp, len); } if (rmdir(path_name(pp)) < 0) { report(path_name(pp)); return; } if (vflag) fprintf(stderr, "rmdir %s\n", path_name(pp)); } else { /* Some other type of file. */ if (unlink(path_name(pp)) < 0) { report(path_name(pp)); return; } if (vflag) fprintf(stderr, "rm %s\n", path_name(pp)); } } void apply_mkold(const char *file, const char *err) /* Make a file very old. (An error occurred when it was added.) */ { struct utimbuf utb; utb.actime= utb.modtime= 0; if (utime(file, &utb) < 0) { report(file); return; } fprintf(stderr, "made %s look old", file); fprintf(stderr, err == nil ? "\n" : " due to a remote problem: %s\n", err); } void apply_chmod(const char *file, mode_t mode, uid_t uid, gid_t gid, int talk) /* Change mode and ownership. */ { struct stat st; if (lstat(file, &st) < 0) { report(file); return; } if ((st.st_mode & 07777) != mode) { if (chmod(file, mode) < 0) { report(file); return; } if (vflag && talk) { fprintf(stderr, "chmod %03o %s\n", (unsigned) mode, file); } } if (st.st_uid != uid || st.st_gid != gid) { if (chown(file, uid, gid) < 0) { if (errno != EPERM) report(file); return; } if (vflag && talk) { fprintf(stderr, "chown %u:%u %s\n", (unsigned) uid, (unsigned) gid, file); } } } void apply_add(pathname_t *pp, entry_t *entry) /* Add or replace a file. */ { const char *file; off_t size; int f; unsigned char buf[1024 << sizeof(int)]; unsigned char *p; int c; int dirty; struct stat st; struct utimbuf utb; if (entry->ignore) return; if (lstat(path_name(pp), &st) >= 0 && (entry->type != F_FILE || !S_ISREG(st.st_mode))) { apply_remove(pp); } file= path_name(pp); switch (entry->type) { case F_DIR: if (mkdir(file, entry->mode) < 0) { report(file); return; } if (vflag) fprintf(stderr, "mkdir %s\n", file); break; case F_FILE: size= entry->size; f= -1; st.st_mode= 0; if (lstat(file, &st) < 0 || S_ISREG(st.st_mode)) { f= open(file, O_WRONLY | O_CREAT | O_TRUNC, entry->mode); if (f < 0) { (void) chmod(file, entry->mode | 0200); f= open(file, O_WRONLY | O_CREAT | O_TRUNC, entry->mode); } if (f < 0) { (void) unlink(file); f= open(file, O_WRONLY | O_CREAT | O_TRUNC, entry->mode); } if (f < 0) report(file); } dirty= (f >= 0); p= buf; while (size > 0 && (c= getc(difffp)) != EOF) { size--; *p++= c; if (p == arraylimit(buf) || size == 0) { if (f >= 0 && write(f, buf, p - buf) < 0) { report(file); close(f); f= -1; } p= buf; } } if (size > 0) { if (ferror(difffp)) report(diff_file); if (feof(difffp)) { fprintf(stderr, "remspec: %s: premature EOF\n", diff_file); } if (dirty) apply_mkold(file, nil); exit(1); } if (f < 0) { if (dirty) apply_mkold(file, nil); return; } close(f); if (vflag) { fprintf(stderr, st.st_mode == 0 ? "add %s\n" : entry->mtime >= st.st_mtime ? "update %s\n" : "restore %s\n", file); } utb.actime= time(nil); utb.modtime= entry->mtime; if (utime(file, &utb) < 0) report(file); break; case F_BLK: if (mknod(file, S_IFBLK | entry->mode, entry->rdev) < 0) { report(file); return; } if (vflag) { fprintf(stderr, "mknod %s b %d %d\n", file, major(entry->rdev), minor(entry->rdev)); } break; case F_CHR: if (mknod(file, S_IFCHR | entry->mode, entry->rdev) < 0) { report(file); return; } if (vflag) { fprintf(stderr, "mknod %s c %d %d\n", file, major(entry->rdev), minor(entry->rdev)); } break; case F_PIPE: if (mknod(file, S_IFIFO | entry->mode, 0) < 0) { report(file); return; } if (vflag) fprintf(stderr, "mknod %s p\n", file); break; case F_LINK: if (symlink(entry->link, file) < 0) { report(file); return; } if (vflag) fprintf(stderr, "ln -s %s %s\n", entry->link, file); return; } apply_chmod(file, entry->mode, entry->uid, entry->gid, 0); } void apply_link(const char *file, pathname_t *pp) /* Hard link *pp to file. */ { struct stat st1, st2; if (lstat(file, &st1) < 0) { report(file); return; } if (lstat(path_name(pp), &st2) >= 0) { if (st1.st_ino == st2.st_ino && st1.st_dev == st2.st_dev) return; apply_remove(pp); if (lstat(path_name(pp), &st2) >= 0) return; } if (link(file, path_name(pp)) < 0) { fprintf(stderr, "remsync: ln %s %s: %s\n", file, path_name(pp), strerror(errno)); excode= 1; return; } if (vflag) fprintf(stderr, "ln %s %s\n", file, path_name(pp)); } void diff_syntax(const char *line) { fprintf(stderr, "remsync: %s: syntax error on this line: %s\n", diff_file, line); exit(1); } void apply_differences(void) /* Update a tree to a list of differences derived from a remote tree. */ { char *line; char **argv; size_t argc; pathname_t path, link; size_t trunc; path_init(&path); path_init(&link); path_add(&path, tree); path_add(&link, tree); trunc= path_length(&path); while (!feof(difffp) && (line= read1line(difffp)) != nil) { splitline(line, &argv, &argc); if (argc == 0) diff_syntax(line); path_trunc(&path, trunc); if (strcmp(argv[0], "add") == 0) { entry_t entry; if (argc < 3) diff_syntax(line); path_add(&path, argv[1]); entry.ignore= (strcmp(argv[2], "ignore") == 0); if (!entry.ignore && !getattributes(&entry, argc - 2, argv + 2)) diff_syntax(line); apply_add(&path, &entry); } else if (strcmp(argv[0], "rm") == 0) { if (argc != 2) diff_syntax(line); path_add(&path, argv[1]); apply_remove(&path); } else if (strcmp(argv[0], "ln") == 0) { if (argc != 3) diff_syntax(line); path_trunc(&link, trunc); path_add(&link, argv[1]); path_add(&path, argv[2]); apply_link(path_name(&link), &path); } else if (strcmp(argv[0], "chmod") == 0) { if (argc != 5) diff_syntax(line); path_add(&path, argv[1]); apply_chmod(path_name(&path), strtoul(argv[2], nil, 010), strtoul(argv[3], nil, 10), strtoul(argv[4], nil, 10), 1); } else if (strcmp(argv[0], "old") == 0) { if (argc != 3) diff_syntax(line); path_add(&path, argv[1]); apply_mkold(path_name(&path), argv[2]); } else if (strcmp(argv[0], "end") == 0) { if (argc != 1) diff_syntax(line); break; } else { diff_syntax(line); } } checkdiff(); } void usage(void) { fprintf(stderr, "Usage: remsync -sxv tree [state-file]\n"); fprintf(stderr, " remsync -duxvD tree [state-file [diff-file]]\n"); fprintf(stderr, " remsync [-xv] tree [diff-file]\n"); exit(1); } int main(int argc, char **argv) { int i; for (i= 1; i < argc && argv[i][0] == '-'; i++) { char *p= argv[i] + 1; if (p[0] == '-' && p[1] == 0) { i++; break; } while (*p != 0) { switch (*p++) { case 's': sflag= 1; break; case 'd': dflag= 1; break; case 'u': uflag= 1; break; case 'x': xflag= 1; break; case 'D': Dflag= 1; break; case 'v': vflag= 1; break; default: usage(); } } } if (sflag && dflag) usage(); if (sflag && uflag) usage(); if (!sflag && !dflag && uflag) usage(); if (!dflag && Dflag) usage(); if (i == argc) usage(); tree= argv[i++]; if (sflag) { /* Make a state file. */ state_file= i < argc ? argv[i++] : "-"; if (i != argc) usage(); statefp= stdout; if (strcmp(state_file, "-") != 0) { if ((statefp= fopen(state_file, "w")) == nil) fatal(state_file); } mkstatefile(); } else if (dflag) { /* Make a file of differences. */ state_file= i < argc ? argv[i++] : "-"; diff_file= i < argc ? argv[i++] : "-"; if (i != argc) usage(); statefp= stdin; if (strcmp(state_file, "-") != 0) { if ((statefp= fopen(state_file, "r")) == nil) fatal(state_file); } difffp= stdout; if (strcmp(diff_file, "-") != 0) { if ((difffp= fopen(diff_file, "w")) == nil) fatal(diff_file); } mkdifferences(); } else { /* Apply a file of differences. */ diff_file= i < argc ? argv[i++] : "-"; if (i != argc) usage(); difffp= stdin; if (strcmp(diff_file, "-") != 0) { if ((difffp= fopen(diff_file, "r")) == nil) fatal(diff_file); } apply_differences(); } exit(excode); }