/* * name: cpdir * version: 1.5e * date: 2005/02/19 * os: plan9 v4 * usage: cpdir [-mstugvR] [-l file] srcdir dstdir [path ...] * srcdir: source directory * dstdir: destination directory * path: path to copy. file or directory. * options: * -m: merge (this option is used if dstdir is already exist) * -u: copy owner info. * -g: copy groupe info. * -v: verbose * -R: remove redundant files in destination * -s: safe mode in removing files; just renames foo to _foo * -t: ignore mtime * -l file: path list and pattern list file * * path or pattern list is a file that contains path or pattern per line. * * Note: * Not tested enough. * * auther: Kenji Arisawa (Kenar) * E-mail: arisawa@aichi-u.ac.jp */ #include #include #include #include #define MAXPATH 4096 #define debug if(Dflag) print char *mkpath2(char *path1, char *path2); char *mkpath3(char *path1, char *path2, char *path3); char *strtrim(char *s); int growto(Dir **dirbuf, int ndirbuf, long n); int compar(Dir *a, Dir *b); void cpdir(char *path, char *uid, char *gid); int test(char *path); int empty(char *path); int dirfinfo(Dir **dirbuf, int fd); int mkdir(char *dstpath, char *uid, char *gid); void cpfile(char *path, char *uid, char *gid); int copyfile(int sfd, int dfd); int cpattr(Dir *sd, Dir *dd); int rmdir(char *path, char *uid, char *gid); int rmfile(char *path, char *uid, char *gid); int setugid(int fd,char *uid,char *gid); int protectedf(char *path, char *uid, char *gid); char *src, *dst; int vflag; // verbose int Rflag; // remove redundant files int uflag; // user flag int gflag; // groupe flag int mflag; // merge flag int tflag; // ignore mtime int Dflag; // debug int safe = 0; int quit = 0; enum { NON, FIL, DIR, }; typedef struct List List; struct List{ char *data; List *next; }; List *xlist; void usage(void) { fprint(2,"usage: cpdir [-mstugvR] [-l file] srcdir dstdir [path ....]\n"); exits("usage"); } void appendList(List **j, char *d) { List *p, *q; q = nil; p = *j; while(p){ q = p; p = p->next; } // then p is nil p = malloc(sizeof(List)); if(q) q ->next = p; else *j = p; p->data = strdup(d); p->next = nil; } static int notefun(void *a, char *msg) { USED(a); USED(msg); fprint(2,"# %s\n",msg); quit = 1; noted(NCONT); return 0; } #ifdef NOTE * shell style path matching function * I modified to simplify original one * and made some modification of the matching rule. * now, '/' is a simplly regular charactor except * "/*/" is match to "/". * ref: /sys/src/cmd/rc/glob.c * -Kenar- * * Does the string s match the pattern p * * matches any sequence of characters * ? matches any single character * [...] matches the enclosed list of characters * ~ in the beginning of [...] means compliment * - in [...] means range in ascii order * * return value * 1: matched * 0: not matched #endif int match(char *s, char *p) { char *s0; int compl, hit, lo, hi, t, c; s0 = s; for(;*p; s++,p++){ switch(*p){ case '*': ++p; if((*p == '/') && (s > s0)) if(match(s-1, p)) return 1; for(;;){ if(match(s, p)) return 1; if(!*s) break; s++; } return 0; case '?': if(*s == '\0') return 0; break; case '[': if(*s == '\0') return 0; c=*s; p++; compl = (*p == '~'); if(compl) p++; hit=0; while(*p!=']'){ if(*p=='\0') return 0; /* syntax error */ lo=*p; p++; if(*p!='-') hi=lo; else{ p++; if(*p=='\0') return 0; /* syntax error */ hi=*p; p++; if(hidata)) return 1; p = p->next; } return 0; } void readxlist(char *file, List **plist) { int fd; Biobuf in; char *line; char *path; int n; fd = open(file, OREAD); if(fd < 0) sysfatal("%s not open:%r", file); Binit(&in, fd, OREAD); while(line = Brdline(&in,'\n')){/* assign = */ n = Blinelen(&in); line[n-1] = 0; path = strtrim(line); if(path[0] != '!') continue; path++; path = strtrim(path); if(*path == 0) break; appendList(plist, path); //print("file... %s\n", path); } Bterm(&in); // note: Bterm close the file if(0) { // DEBUG List *p; p = *plist; while(p){ print("%s\n", p->data); p = p->next; } } } /* returns: * 1: protected by uid and gid pair * 0: not protected */ int protected(Dir *d, char *uid, char *gid) { int r; //debug("(%s,%s) (%s,%s)\n", uid,gid,d->uid,d->gid); r = (uid && (strcmp(d->uid,uid) != 0))||(gid && (strcmp(d->gid,gid) != 0)); if(r) werrstr("protected"); debug("protected: %d\n", r); return r; } /* returns: * 1: guared by uid and gid pair * 0: not protected * -1: none existent */ int protectedf(char *path, char *uid, char *gid) { Dir *d; int r; d = dirstat(path); if(d == nil){ fprint(2,"# %s not exist: %r\n",path); return -1; } r = (uid && (strcmp(d->uid,uid) != 0))||(gid && (strcmp(d->gid,gid) != 0)); free(d); if(r) werrstr("protected"); debug("protectedf: %d\n", r); return r; } void setugidd(Dir *d,char *uid,char *gid) { if(uid) d->uid = uid; if(gid) d->gid = gid; } int setugid(int fd,char *uid,char *gid) { Dir *d; int status; d = dirfstat(fd); setugidd(d, uid,gid); status = dirfwstat(fd, d); free(d); return status; } /* * safe protection should be out of delete() * and also out of rmdir(), rmfile() * so that delete(path,nil,nil) works */ int delete(char *path, char *uid, char *gid) { int status; if(vflag) print("removing %s\n", path); status = 0; switch(test(path)){ case NON: // none existent break; case FIL: // file if(protectedf(path,uid,gid)) status = -1; else status = remove(path); break; case DIR: // dir if(empty(path)) status = remove(path); else status = rmdir(path, uid, gid); break; default: break; } if(status) fprint(2,"# not removed %s: %r\n", path); return status; } /* * rename: rename path/to/foo to path/to/_foo */ int rename(char *path) { Dir null; char buf[MAXPATH]; char *p; int status=0; debug("rename %s\n", path); p = strrchr(path, '/'); if(p){ *p = 0; snprint(buf, sizeof buf, "%s/_%s", path, p+1); *p = '/'; p = strrchr(buf, '/') + 1; } else{ snprint(buf, sizeof buf, "_%s", path); p = buf; } /* if renamed file (_foo) already exist, we must * unconditionnally delete it */ if(delete(buf,nil,nil) < 0) goto L; nulldir(&null); null.name = p; //debug("dirwstat: %s %s\n", path, p); if((status = dirwstat(path, &null)) < 0) fprint(2, "# dirwstat: %r\n"); L: debug("done rename %s %d\n", path, test(path)); return status; } void dofile(char *path) { Biobuf in; char *line; int fd; int n; char *args[3]; char *uid, *gid; fd = open(path, OREAD); if(fd<0) sysfatal("%s not open\n", path); Binit(&in, fd, OREAD); while(line = Brdline(&in,'\n')){/* assign = */ n = Blinelen(&in); line[n-1] = 0; line = strtrim(line); if(*line == '#' || *line == '!') continue; n = tokenize(line, args, 3); if(n <1) continue; path = args[0]; // NOTE: cleanname converts null string to "." path = cleanname(path); uid = gid = nil; if(n > 1) uid = args[1]; if(n > 2) gid = args[2]; if(uid && (strcmp(uid,"*")==0)) uid = nil; if(gid && (strcmp(gid,"*")==0)) gid = nil; if(vflag) print("looking %s (%s,%s)\n", path, uid, gid); cpdir(path,uid, gid); } Bterm(&in); // note: Bterm close the file } void main(int argc, char *argv[]) { char *file = 0; char *path; USED(argc); ARGBEGIN{ case 't': tflag = 1; break; case 'l': file = ARGF(); break; case 'v': vflag = 1; break; case 'R': Rflag = 1; break; case 'u': uflag = 1; break; case 'g': gflag = 1; break; case 'm': mflag = 1; break; case 's': safe = 1; break; case 'D': Dflag = 1; break; default: usage(); }ARGEND if(*argv) src = cleanname(*argv++); if(*argv) dst = cleanname(*argv++); if(!src || !dst) usage(); if(!mflag && Rflag) usage(); if(file && *argv) sysfatal("-l option and %s ... incompatible",*argv); /* test src whether it is not empty */ switch(test(src)){ case NON: sysfatal("%s is non-existent", src); case FIL: sysfatal( "%s is not a directory", src); case DIR: if(empty(src)) sysfatal( "%s is empty", src); break; default: break; } /* test dst whether it exits */ switch(test(dst)){ case NON: /* OK */ if(mflag) sysfatal("%s absent. remove merge option -m",dst); break; case FIL: sysfatal("%s is not a directory", dst); default: /*already exist */ if(mflag) break; sysfatal("%s already exist. merge option -m is required", dst); } /* confirm that dst is not in the subtree of src. * if it is, we must abort processing */ if((strlen(dst) >= strlen(src)) && (strncmp(dst,src,strlen(src)) == 0)){ /* then be careful */ if(dst[strlen(src)] == '/'){ fprint(2,"# %s is a subtree of %s\n", dst, src); exits("error"); } } /* * file is read twice but don't mined. * data in the file can be big. don't put it on memory. * for example, data extracted from plan9.db */ if(file) readxlist(file, &xlist); if(!mflag) /* create the dir */ mkdir(dst, nil, nil); atnotify(notefun,1); /* and process pathlist */ if(file) dofile(file); else if(*argv) while(*argv){ path = cleanname(*argv++); if(vflag) print("looking %s\n", path); cpdir(path, nil, nil); } else cpdir("/", nil, nil); /* needed to avoid err exit in rc script */ exits(nil); } void cpdir(char *path, char *uid, char *gid) { int sfd,dfd; int nsrcdir; int ndstdir; Dir *srcdirbuf, *sd, *sd0, *sdend, *ddend; Dir *dstdirbuf, *dd, ndd; char *newpath; char srcpath[MAXPATH]; char dstpath[MAXPATH]; if(quit) exits("interrupt"); if(path[0] == '/') path++; debug("cpdir %s\n", path); if(strcmp(src,"/") == 0) snprint(srcpath,sizeof(srcpath),"/%s", path); else snprint(srcpath,sizeof(srcpath),"%s/%s", src, path); if(xmatch(path)){ /* xmatch protection should be only here */ if(vflag) print("skipping %s\n", path); return; } snprint(dstpath,sizeof(dstpath),"%s/%s", dst, path); sfd = open(srcpath,OREAD); if(sfd == -1){ switch(test(srcpath)){ case NON: fprint(2,"# %s not exist\n",srcpath); if(Rflag) delete(dstpath,uid,gid); break; default: fprint(2,"# %s unreadable, skipped.\n", srcpath); break; } return; } sd = dirfstat(sfd); if(sd == nil){ fprint(2,"# %s can't dirstat: %r\n", srcpath); close(sfd); return; } if((sd->mode & DMDIR) == 0){ /* file */ dd = dirstat(dstpath); if(dd == nil){ /* we assume dstpath is non-existent */ free(sd); close(sfd); cpfile(path, uid, gid); return; } if(tflag || (sd->mtime > dd->mtime)){ /* we update. protection is in cpfile. */ free(sd); free(dd); close(sfd); cpfile(path, uid, gid); return; } if((tflag || (sd->mtime == dd->mtime)) && !protected(dd,uid,gid)){ nulldir(&ndd); setugidd(&ndd,uid,gid); if(cpattr(sd,&ndd) > 0 && dirwstat(dstpath,&ndd) < 0) fprint(2,"# can't wstat %s: %r\n", dstpath); //debug("dirfwstat: OK\n"); } free(sd); free(dd); close(sfd); return; } /* now srcpath is a directory */ nsrcdir = dirfinfo(&srcdirbuf, sfd); close(sfd); if(nsrcdir < 0){ /* something wrong. skip this copy */ fprint(2,"# %s unreadable: %r\n", srcpath); free(sd); return; } dfd = open(dstpath,OREAD); //print("open: %s %d\n", dstpath,dfd); if(dfd < 0){// try to create a dir if(mkdir(dstpath, uid, gid) < 0){ fprint(2,"# unreadable to create %s: %r\n", dstpath); free(sd); return; } dfd = open(dstpath, OREAD); if(dfd < 0){ fprint(2,"# created but not open %s: %r\n", dstpath); free(sd); return; } } dd = dirfstat(dfd); if(dd == nil){ /* something wrong */ fprint(2,"# dirfstat %s: %r\n", dstpath); free(sd); return; }; if((dd->mode & DMDIR) == 0){ /* source is a directory, but destination is a file * time is critical for this case */ if(!tflag && (sd->mtime < dd->mtime)){ fprint(2,"# %s untouched\n", path); free(sd);free(dd);close(dfd); return; } if(tflag || (sd->mtime > dd->mtime)){ /* we remove */ free(dd);close(dfd); if(safe) rename(dstpath); else delete(dstpath,uid,gid); if((dfd = create(dstpath, OREAD|OEXCL, sd->mode)) < 0){ fprint(2,"# create %s: %r\n", dstpath); free(sd); return; } } } /* now we have sd, dfd * sd will be used later */ sd0 = sd; /* end of flag f */ ndstdir = dirfinfo(&dstdirbuf, dfd); if(ndstdir < 0){ /* skip this copy */ fprint(2, "# %s unreadable: %r\n", dstpath); return; } sdend = srcdirbuf + nsrcdir; ddend = dstdirbuf + ndstdir; /* copy them */ if(0 && vflag) print("looking %s\n",path); for(sd = srcdirbuf, dd = dstdirbuf; !quit && sd < sdend && dd < ddend;){ debug("comparing %s %s\n",sd->name, dd->name); if(strcmp(sd->name, dd->name) < 0){ /* we must create destination */ newpath = mkpath2(path, sd->name); if(sd->mode & DMDIR){ /* create the directory and go on */ char *p; p = mkpath2(dst, newpath); //mkdir(p, uid, gid); mkdir(p,nil,nil); free(p); } cpdir(newpath, uid, gid); free(newpath); sd++; continue; } else if(strcmp(sd->name, dd->name) > 0){ if(Rflag){ /* remove destination */ char *p; p = mkpath3(dst, path, dd->name); delete(p,nil,nil); free(p); } dd++; continue; } else{ /* both names exist, we descend */ newpath = mkpath2(path, sd->name); cpdir(newpath, uid, gid); free(newpath); } debug("next step\n"); sd++; dd++; } if(quit) exits("interrupted"); if(sd < sdend){ char *p; for(; !quit && sd < sdend; sd++){ newpath = mkpath2(path, sd->name); p = mkpath3(dst, path, sd->name); if(sd->mode & DMDIR){ /* create the directory and go on */ mkdir(p, uid, gid); cpdir(newpath, uid, gid); } else cpfile(newpath, uid, gid); free(newpath); free(p); } } else if(Rflag && (dd < ddend)){ char *p; for(; !quit && dd < ddend; dd++){ p = mkpath3(dst, path, dd->name); if(vflag) print("removing %s\n", p); if(safe) rename(p); else if(dd->mode & DMDIR) rmdir(p, uid, gid); else rmfile(p, uid, gid); free(p); } } /* free dirbuf */ free(srcdirbuf); free(dstdirbuf); /* sync stat to src */ dd = dirfstat(dfd); nulldir(&ndd); ndd.uid = dd->uid; ndd.gid = dd->gid; if(cpattr(sd0,&ndd) > 0 && dirfwstat(dfd, &ndd) < 0) fprint(2,"# could not wstat %s: %r\n", dstpath); //debug("dirfwstat: OK\n"); free(dd); close(dfd); free(sd0); if(quit) exits("interrupted"); debug("done cpdir %s\n", path); } /* test: return values NON not exist FIL file DIR directory */ int test(char *path) { ulong m; Dir *d; d = dirstat(path); if(d == nil) return NON; // non existent m = d->mode; free(d); if((m & DMDIR) == 0) return FIL; // not a directory return DIR; } int empty(char *path) { int fd; long n; Dir *db; fd = open(path, OREAD); if(fd < 0){ fprint(2,"# %s not open: %r", path); return -1; } n = dirread(fd, &db); close(fd); free(db); return (n==0)?1:0; } /* * dirfinfo * * return value * case -1: not exit * case 0: empty * others: the numbers of files/directory * * note: * free(dirbuf) if return value is not -1 * * ref: /sys/src/cmd/ls.c */ int dirfinfo(Dir **dirbufp, int fd) { Dir *db; int n; n = dirreadall(fd,&db); if(n == -1) return -1; /* sort by name */ qsort(db, n, sizeof(Dir), (int (*)(void*, void*))compar); *dirbufp = db; return n; } int compar(Dir *a, Dir *b) { return strcmp(a->name, b->name); } int mkdir(char *dstpath, char *uid, char *gid) /* dstpath: absolute path to the destination * we can asume the destination is not present */ { int fd; char *p, *q; int m; p = dstpath + strlen(dst) + 1; if(vflag) print("creating %s\n", dstpath); while((q = strchr(p, '/'))){ // assign = *q = 0; m = test(dstpath); if(m == DIR){ *q = '/'; p = q + 1; continue; } if(m == FIL) delete(p,uid,gid); fd = create(dstpath, OREAD|OEXCL, DMDIR | 0775); if(fd < 0){ fprint(2,"# can't create %s: %r\n", dstpath); return -1; } if(setugid(fd, uid,gid) < 0) fprint(2,"# can't wstat %s: %r\n", dstpath); close(fd); *q = '/'; p = q + 1; } fd = create(dstpath, OREAD|OEXCL, DMDIR | 0775); if(fd < 0){ fprint(2,"# can't create %s: %r\n", dstpath); return -1; } if(setugid(fd, uid,gid) < 0) fprint(2,"# can't wstat %s: %r\n", dstpath); close(fd); debug("done mkdir: %s %s\n", uid, gid); return 0; } int copyfile(int sfd, int dfd) { char buf[4096]; int n; for(;;){ n = read(sfd, buf, sizeof buf); if(n <= 0) break; if(write(dfd, buf, n) != n) return -1; } return 0; } int cpattr(Dir *sd, Dir *dd) { int f = 0; debug("cpattr ...\n"); /* && ++f might be tricky */ if((dd->mode != sd->mode) && ++f) dd->mode = sd->mode; if((dd->mtime != sd->mtime) && ++f) dd->mtime = sd->mtime; if(gflag && (strcmp(dd->gid,sd->gid)!=0) && ++f) dd->gid = sd->gid; if(uflag && (strcmp(dd->uid,sd->uid)!=0) && ++f) dd->uid = sd->uid; debug("cpattr %d\n", f); return f; } /* src/path should be a file * cpfile check destination for some protections * safe protection * uid/gid protection * and set attribute for the destination */ void cpfile(char *path, char *uid, char *gid) { char srcpath[MAXPATH]; char dstpath[MAXPATH]; Dir *sd, *dd, ndd; int sfd, dfd; int f=0; debug("cpfile: %s\n", path); if(path[0] == '/') path++; if(strcmp(src,"/") == 0) snprint(srcpath,sizeof(srcpath),"/%s", path); else snprint(srcpath,sizeof(srcpath),"%s/%s", src, path); snprint(dstpath,sizeof(dstpath),"%s/%s", dst, path); /* examine if the destination is already exist * avoid removing protected files */ dd = dirstat(dstpath); if(dd){ debug("%s %d\n", path, protected(dd,uid,gid)); if(((dd->mode & DMDIR) == 0) && protected(dd,uid,gid)){ fprint(2,"# %s untouched\n", path); free(dd); goto L0; } if(vflag) print("updating %s\n", dstpath); if(safe || (dd->mode & DMDIR)){ rename(dstpath); if(vflag) print("%s renamed\n", dstpath); } f = 1; free(dd); } /* here destination is file or none * if file, protection check has been already done */ sfd = open(srcpath, OREAD); if(sfd < 0){ fprint(2,"# open error: %s %r\n", srcpath); goto L0; } sd = dirfstat(sfd); if(!sd){ // something wrong fprint(2,"# cannot read stat: %s %r\n", srcpath); goto L1; } if(sd->mode & DMDIR){ //illegal usage fprint(2,"# bug of this program: %s\n",srcpath); goto L2; } if(vflag && !f) print("creating %s\n", dstpath); dfd = create(dstpath, OWRITE, 0777); if(dfd < 0){ char *p; int status; char err[ERRMAX]; errstr(err,ERRMAX); if(strstr(err,"permission")){ /* we have not permission to write */ fprint(2,"# %s: %s\n",dstpath,err); goto L2; } /* then the failure comes from the absence of * intermediate path. we try mkdir */ p = strrchr(dstpath,'/'); if(p){ *p = 0; status = mkdir(dstpath,uid,gid); *p = '/'; if(status == 0){ /* try again */ dfd = create(dstpath, OWRITE, 0777); } } } if(dfd < 0){ fprint(2,"# unable to create %s: %r\n", dstpath); goto L2; } if(copyfile(sfd,dfd) < 0){ fprint(2, "# write error: %s: %r\n", dstpath); goto L3; } nulldir(&ndd); setugidd(&ndd,uid,gid); if(cpattr(sd,&ndd) > 0 && dirfwstat(dfd,&ndd)<0) fprint(2, "# can't wstat %s: %r\n", dstpath); L3: close(dfd); L2: free(sd); L1: close(sfd); L0: return; } int rmfile(char *path, char *uid, char *gid) { int status; debug("rmfile %s\n", path); if(protectedf(path,uid,gid)) status = -1; else if((status = remove(path)) < 0) fprint(2, "# %s not removed: %r", path); debug("done rmfile %s %d\n", path, status); return status; } int rmdir(char *path, char *uid, char *gid) { char *name; int fd; Dir *db; int status; long i,n; debug("rmdir %s\n", path); fd = open(path, OREAD); if(fd == -1){ fprint(2,"# %s not open: %r\n",path); return -1; } n = dirreadall(fd,&db); close(fd); if(n == -1) return -1; for(i=0; i