/* * Usage: rdiff [diff-args] [host:]file1 [host:]file2 * * diff-args are anything beginning with a - (except --, which marks * the end of the diff-args) plus, in the case of -[CDFILSUWxX], one * following argument. Exceptions: * * - An option -rsh takes one argument, which is the name of the * rsh-like program to use for remote access; the default is * "ssh". * * - -r and --recursive are not permitted. */ #include #include #include #include #include #include #include extern const char *__progname; static const char *safe_chars = "+,-./0123456789:=@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"; static const char *path_dev_null = "/dev/null"; static int dev_null; static const char *rsh = "ssh"; static const char **diff_args; static int n_diff_args; static char *arg1; static char *host1; static char *file1; static int fd1; static char *arg2; static char *host2; static char *file2; static int fd2; static int diffo; static pid_t kid_1; static pid_t kid_2; static pid_t kid_d; static pid_t kid_f; static int our_ec; static void handleargs(int ac, char **av) { int skip; int errs; int options_ended; const char **args; int args_n; int args_a; static void save_diff_arg(const char *s) { if (args_n >= args_a) args = realloc(args,(args_a=args_n+8)*sizeof(*args)); args[args_n++] = s; } static int file_arg(const char *s, char **hp, char **fp) { const char *colon; const char *slash; int n; char *t; colon = index(s,':'); slash = index(s,'/'); if (!colon || (slash && (colon > slash))) { *hp = 0; *fp = strdup(s); return(0); } n = colon - s; t = malloc(n+1); bcopy(s,t,n); t[n] = '\0'; *hp = t; *fp = strdup(colon+1); return(0); } skip = 0; errs = 0; options_ended = 0; args = 0; args_n = 0; args_a = 0; arg1 = 0; arg2 = 0; save_diff_arg("diff"); for (ac--,av++;ac;ac--,av++) { if (skip > 0) { skip --; continue; } if (options_ended || (**av != '-') || (av[0][1] == '\0')) { options_ended = 1; if (! arg1) { errs += file_arg(arg1=*av,&host1,&file1); } else if (! arg2) { errs += file_arg(arg2=*av,&host2,&file2); } else { fprintf(stderr,"%s: extra argument `%s'\n",__progname,*av); errs ++; } continue; } if (0) { needarg:; fprintf(stderr,"%s: %s needs a following argument\n",__progname,*av); errs ++; continue; } #define WANTARG() do { if (++skip >= ac) goto needarg; } while (0) if (!strcmp(*av,"-r") || !strcmp(*av,"--recursive")) { fprintf(stderr,"%s: recursive diff not supported\n",__progname); errs ++; continue; } if (!strcmp(*av,"-rsh")) { WANTARG(); rsh = av[skip]; continue; } if (!strcmp(*av,"--")) { options_ended = 1; continue; } if (av[0][1] && !av[0][2] && index("CDFILSUWxX",av[0][1])) { WANTARG(); save_diff_arg(*av); skip ++; save_diff_arg(av[skip]); continue; } save_diff_arg(*av); #undef WANTARG } if (! arg2) { fprintf(stderr,"%s: must have two file arguments\n",__progname); errs ++; } if (errs) exit(1); save_diff_arg("--"); save_diff_arg(0); save_diff_arg(0); save_diff_arg(0); diff_args = args; n_diff_args = args_n; } static void fork_rsh(char *host, char *file, pid_t *kidp, int *fdp) { int n; char *cmd; const char *args[4]; int p[2]; pid_t kid; n = strspn(file,safe_chars); if (n == strlen(file)) { if (file[0] == '-') { asprintf(&cmd,"cat ./%s",file); } else { asprintf(&cmd,"cat %s",file); } } else { char *s; int a; int n; char *p; static void save(char c) { if (n >= a) s = realloc(s,a=n+8); s[n++] = c; } s = 0; a = 0; n = 0; save('c'); save('a'); save('t'); save(' '); if (file[0] == '-') { save('.'); save('/'); } for (p=file;*p;p++) { if (index(safe_chars,*p)) { save(*p); } else { save('\\'); save(*p); } } save('\0'); cmd = s; } args[0] = rsh; args[1] = host; args[2] = cmd; args[3] = 0; if (pipe(&p[0]) < 0) { fprintf(stderr,"%s: pipe: %s\n",__progname,strerror(errno)); exit(1); } kid = fork(); if (kid < 0) { fprintf(stderr,"%s: fork: %s\n",__progname,strerror(errno)); exit(1); } if (kid == 0) { dup2(dev_null,0); dup2(p[1],1); close(p[1]); close(p[0]); if (fd1 >= 0) close(fd1); if (fd2 >= 0) close(fd2); execvp(rsh,(const void *)&args[0]); fprintf(stderr,"%s: can't exec %s: %s\n",__progname,rsh,strerror(errno)); exit(1); } close(p[1]); *kidp = kid; *fdp = p[0]; } static void open_dev_null(void) { dev_null = open(path_dev_null,O_RDWR,0); if (dev_null < 0) { fprintf(stderr,"%s: %s: %s\n",__progname,path_dev_null,strerror(errno)); exit(1); } } static void ensure_stdio(void) { int d; do d = dup(dev_null); while (d < 3); close(d); } static void fork_filter(void) { int p[2]; char rbuf[8192]; int rfill; int rptr; char *line; int fd1l; int fd2l; void readrbuf(void) { rfill = read(p[0],&rbuf[0],sizeof(rbuf)); if (rfill < 0) { fprintf(stderr,"%s: filter read: %s\n",__progname,strerror(errno)); exit(1); } } char *collect_line(void) { static char *l = 0; int n; static int a = 0; void savec(int c) { if (n >= a) l = realloc(l,a=n+8); l[n++] = c; } if (rfill < 0) return(0); n = 0; while (1) { if (rptr >= rfill) { readrbuf(); if (rfill == 0) { rfill = -1; if (n < 1) return(0); savec('\0'); return(l); } rptr = 0; } savec(rbuf[rptr++]); if (l[n-1] == '\n') { savec('\0'); return(l); } } } if ((fd1 < 0) && (fd2 < 0)) { diffo = -1; kid_f = -1; return; } fd1l = sprintf(&rbuf[0],"%d",fd1); fd2l = sprintf(&rbuf[0],"%d",fd2); if (pipe(&p[0]) < 0) { fprintf(stderr,"%s: pipe: %s\n",__progname,strerror(errno)); exit(1); } kid_f = fork(); if (kid_f < 0) { fprintf(stderr,"%s: fork: %s\n",__progname,strerror(errno)); exit(1); } if (kid_f > 0) { close(p[0]); diffo = p[1]; return; } close(p[1]); if (fd1 >= 0) close(fd1); if (fd2 >= 0) close(fd2); rfill = 0; rptr = 0; line = collect_line(); if (! line) exit(0); if ( line[0] && (line[1] == line[0]) && (line[2] == line[0]) && (line[3] == ' ') ) { if ( (fd1 >= 0) && !strncmp(&line[4],"/dev/fd/",8) ) { printf("%.4s%s%s",line,arg1,line+12+fd1l); } else { printf("%s",line); } line = collect_line(); if (line) { if ( line[0] && (line[1] == line[0]) && (line[2] == line[0]) && (line[3] == ' ') && (fd2 >= 0) && !strncmp(&line[4],"/dev/fd/",8) ) { printf("%.4s%s%s",line,arg2,line+12+fd2l); } else { printf("%s",line); } } } else { printf("%s",line); } fflush(stdout); if ((rfill > 0) && (rptr < rfill)) write(1,&rbuf[rptr],rfill-rptr); while (1) { readrbuf(); if (rfill < 1) break; write(1,&rbuf[0],rfill); } exit(0); } static void fork_diff(void) { char *t; pid_t kid; if (fd1 >= 0) { asprintf(&t,"/dev/fd/%d",fd1); diff_args[n_diff_args-3] = t; } else { diff_args[n_diff_args-3] = file1; } if (fd2 >= 0) { asprintf(&t,"/dev/fd/%d",fd2); diff_args[n_diff_args-2] = t; } else { diff_args[n_diff_args-2] = file2; } kid = fork(); if (kid < 0) { fprintf(stderr,"%s: fork: %s\n",__progname,strerror(errno)); exit(1); } if (kid == 0) { if ((diffo >= 0) && (diffo != 1)) { dup2(diffo,1); close(diffo); } execvp("diff",(const void *)&diff_args[0]); fprintf(stderr,"%s: can't exec diff: %s\n",__progname,strerror(errno)); exit(1); } if (diffo >= 0) close(diffo); kid_d = kid; } static void wait_kids(void) { pid_t kid; int status; static int check_rsh(pid_t *kidp, char *host) { if (kid != *kidp) return(0); if (WIFEXITED(status)) { if (WEXITSTATUS(status)) { fprintf(stderr,"%s: warning: %s to %s exited with status %d\n", __progname,rsh,host,WEXITSTATUS(status) ); } } else if (WIFSIGNALED(status)) { fprintf(stderr,"%s: warning: %s to %s died on signal %d (%s)%s\n", __progname, rsh, host, WTERMSIG(status), strsignal(WTERMSIG(status)), WCOREDUMP(status)?" (core dumped)":"" ); } else { fprintf(stderr,"%s: warning: impossible exit status %#x from %s to %s\n", __progname,status,rsh,host ); } *kidp = -1; return(1); } while ((kid_1 >= 0) || (kid_2 >= 0) || (kid_d >= 0) || (kid_f >= 0)) { kid = wait(&status); if (kid < 0) { switch (errno) { case ECHILD: fprintf(stderr,"%s: lost all children\n",__progname); exit(1); break; case EINTR: continue; break; default: fprintf(stderr,"%s: wait: %s\n",__progname,strerror(errno)); exit(1); break; } } if (check_rsh(&kid_1,host1) || check_rsh(&kid_2,host2)) continue; if (kid == kid_d) { if (WIFEXITED(status)) { our_ec = WEXITSTATUS(status); } else if (WIFSIGNALED(status)) { fprintf(stderr,"%s: diff died on signal %d (%s)%s\n", __progname, WTERMSIG(status), strsignal(WTERMSIG(status)), WCOREDUMP(status)?" (core dumped)":"" ); our_ec = 2; } else { fprintf(stderr,"%s: impossible exit status %#x from diff\n",__progname,status); our_ec = 2; } kid_d = -1; } if (kid == kid_f) kid_f = -1; } } int main(int, char **); int main(int ac, char **av) { handleargs(ac,av); open_dev_null(); ensure_stdio(); fd1 = -1; fd2 = -1; kid_1 = -1; kid_2 = -1; kid_d = -1; kid_f = -1; our_ec = 0; if (host1) fork_rsh(host1,file1,&kid_1,&fd1); if (host2) fork_rsh(host2,file2,&kid_2,&fd2); fork_filter(); fork_diff(); wait_kids(); exit(our_ec); }