ref: 5c4efb5b2b0da340d23189a371a73088c1f1d92c
dir: /clone.c/
#include <u.h> #include <libc.h> #include <thread.h> enum { Nfileprocs = 4, Nblkprocs = 16, Blksz = 128*1024, }; typedef struct Waitgroup Waitgroup; typedef struct File File; typedef struct Blk Blk; struct Waitgroup { Rendez; QLock; Ref; }; struct File { Dir; Waitgroup wg; Channel *errchan; char *src, *dst; int sfd, dfd; }; struct Blk { File *f; long sz; vlong offset; }; int errors = 0; int multisrc = 0; int keepmode = 0; int keepmtime = 0; int keepuser = 0; int keepgroup = 0; int notemp = 0; int blksz = Blksz; int fileprocs = Nfileprocs; int blkprocs = Nblkprocs; long salt; Dir *skipdir; Channel *filechan; /* chan(File*) */ Channel *blkchan; /* chan(Blk*) */ void usage(void) { fprint(2, "usage: %s [-guxT] [-b blocksize] [-p fileprocs:blockprocs] from ... to\n", argv0); exits("usage"); } void error(char *fmt, ...) { va_list arg; char err[ERRMAX]; errors = 1; snprint(err, sizeof err, "%s: %s\n", argv0, fmt); va_start(arg, fmt); vfprint(2, err, arg); va_end(arg); } void * emalloc(ulong n) { void *p; p = malloc(n); if(p == nil) sysfatal("malloc: %r"); return p; } char * estrdup(char *s) { char *p; p = strdup(s); if(p == nil) sysfatal("strdup: %r"); return p; } extern int cas(long *p, long ov, long nv); void wginit(Waitgroup *wg, long n) { memset(wg, 0, sizeof(*wg)); wg->l = &wg->QLock; if(cas(&wg->ref, 0, n) == 0) sysfatal("wginit: cas failed"); } void wgadd(Waitgroup *wg, long n) { long v; v = wg->ref; while(cas(&wg->ref, v, v+n) == 0) v = wg->ref; } void wgdone(Waitgroup *wg) { if(decref(wg) == 0){ qlock(wg); rwakeupall(wg); qunlock(wg); } } void wgwait(Waitgroup *wg) { qlock(wg); while(wg->ref != 0) rsleep(wg); qunlock(wg); } char * filename(char *s) { char *p; p = strrchr(s, '/'); if(p == nil || p == s) return s; if(p[1] == 0){ *p = 0; return filename(s); } return p + 1; } int same(Dir *a, Dir *b) { if(a->type == b->type && a->dev == b->dev && a->qid.path == b->qid.path && a->qid.type == b->qid.type && a->qid.vers == b->qid.vers) return 1; return 0; } File * filenew(char *src, char *dst, Dir *d) { File *f; f = emalloc(sizeof(File)); memmove(f, d, sizeof(Dir)); f->uid = estrdup(d->uid); f->gid = estrdup(d->gid); f->src = estrdup(src); f->dst = estrdup(dst); f->sfd = -1; f->dfd = -1; f->errchan = chancreate(sizeof(ulong), 0); return f; } void filefree(File *f) { if(f->sfd >= 0) close(f->sfd); if(f->dfd >= 0) close(f->dfd); free(f->uid); free(f->gid); free(f->src); free(f->dst); chanfree(f->errchan); free(f); } int cloneattr(int fd, Dir *d) { Dir dd; if(!(keepmode || keepuser || keepgroup || keepmtime)) return 1; nulldir(&dd); if(keepmode) dd.mode = d->mode&DMDIR ? d->mode|0200 : d->mode; if(keepmtime) dd.mtime = d->mtime; if(keepuser) dd.uid = d->uid; if(keepgroup) dd.gid = d->gid; if(dirfwstat(fd, &dd) < 0){ error("can't wstat: %r"); return -1; } return 1; } int mkdir(char *src, char *dst, Dir *sd, Dir **dd) { int fd; Dir d; if(!(sd->mode & 0400)){ error("can't clone directory: '%s' permission denied", src); return -1; } d = *sd; d.mode = d.mode | DMDIR | 0200; fd = create(dst, 0, d.mode); if(fd < 0){ error("can't create directory: %r"); return -1; } if(cloneattr(fd, &d) < 0){ close(fd); return -1; } if(dd){ *dd = dirfstat(fd); if(*dd == nil){ error("can't stat: %r"); close(fd); return -1; } } close(fd); return 1; } void clonedir(char *src, char *dst) { int fd; long n; char *sn, *dn; Dir *dirs, *d; File *f; dirs = nil; fd = open(src, OREAD); if(fd < 0){ error("can't open: %r"); return; } n = dirreadall(fd, &dirs); if(n < 0){ error("can't read directory: %r"); close(fd); return; } close(fd); for(d = dirs; n; n--, d++){ if(d->mode & DMDIR && same(skipdir, d)) continue; sn = smprint("%s/%s", src, d->name); dn = smprint("%s/%s", dst, d->name); if(d->mode & DMDIR){ if(mkdir(sn, dn, d, nil) < 0) continue; clonedir(sn, dn); }else{ f = filenew(sn, dn, d); sendp(filechan, f); } free(sn); free(dn); } free(dirs); } void clone(char *src, char *dst) { char *dn; Dir *sd, *dd; File *f; dn = estrdup(dst); dd = nil; sd = dirstat(src); if(sd == nil){ error("can't stat: %r"); return; } if(access(dn, AEXIST) >= 0){ dd = dirstat(dn); if(dd == nil){ error("can't stat: %r"); goto End; } }else if(multisrc){ if(mkdir(src, dn, sd, &dd) < 0) goto End; skipdir = dd; } /* clone a file */ if(!(sd->mode & DMDIR)){ if(dd && dd->mode & DMDIR) dn = smprint("%s/%s", dn, filename(src)); f = filenew(src, dn, sd); sendp(filechan, f); goto End; } /* clone a directory */ if(dd) dn = smprint("%s/%s", dn, filename(src)); if(skipdir){ if(mkdir(src, dn, sd, nil) < 0) goto End; }else{ if(mkdir(src, dn, sd, &skipdir) < 0) goto End; } clonedir(src, dn); End: free(dn); free(sd); free(dd); } vlong blklist(File *f, Blk **bp) { long odd; vlong i, nblk; Blk *b, *p; if(f->length == 0) return 0; odd = f->length % blksz; nblk = f->length / blksz + (odd > 0); b = p = emalloc(sizeof(Blk) * nblk); for(i = 0; i < nblk; i++, p++){ p->f = f; p->sz = blksz; p->offset = blksz * i; } if(odd > 0) b[nblk-1].sz = odd; *bp = b; return nblk; } int clonefile(File *f) { int ret; vlong n; Blk *blks, *b, *be; enum {Anext, Aerr, Aend}; Alt alts[] = { [Anext] {blkchan, &b, CHANSND}, [Aerr] {f->errchan, nil, CHANRCV}, [Aend] {nil, nil, CHANEND}, }; ret = 1; n = blklist(f, &blks); if(n == 0) return 1; wginit(&f->wg, 0); for(b = blks, be = b + n; b != be; b++) switch(alt(alts)){ case Anext: wgadd(&f->wg, 1); break; case Aerr: ret = -1; goto End; } End: chanclose(f->errchan); wgwait(&f->wg); free(blks); return ret; } long preadn(int fd, void *buf, long nbytes, vlong offset) { long nr, n; vlong o; char *p; nr = 0, n = nbytes, o = offset, p = buf; while(nr < nbytes){ if((n = pread(fd, p, n, o)) < 0) return -1; if(n == 0) break; nr += n, o += n, p += n; } return nr; } void blkproc(void *) { int sfd, dfd; long n; char *buf; File *f; Blk *b; threadsetname("blkproc"); buf = emalloc(blksz); for(;;){ b = recvp(blkchan); if(b == nil) break; f = b->f; sfd = f->sfd; dfd = f->dfd; if((n = preadn(sfd, buf, b->sz, b->offset)) < 0){ error("can't read: %r"); sendul(f->errchan, ~0); } if(n > 0 && pwrite(dfd, buf, n, b->offset) != n){ error("can't write: %r"); sendul(f->errchan, ~0); } wgdone(&f->wg); } } void fileproc(void *v) { char *dst; Dir d; File *f; Waitgroup *wg; threadsetname("fileproc"); wg = v; for(;;){ f = recvp(filechan); if(f == nil) break; dst = nil; f->sfd = open(f->src, OREAD); if(f->sfd < 0){ error("can't open: %r"); goto End; } if(notemp) dst = estrdup(f->dst); else dst = smprint("%s.clone.%ld", f->dst, salt); f->dfd = create(dst, OWRITE, f->mode); if(f->dfd < 0){ error("can't create: %r"); goto End; } if(clonefile(f) < 0){ if(remove(dst) < 0) error("can't remove: %r"); goto End; } cloneattr(f->dfd, f); if(notemp) goto End; if(dirstat(f->dst) != nil && remove(f->dst) < 0){ error("can't remove: %r"); goto End; } nulldir(&d); d.name = filename(f->dst); if(dirfwstat(f->dfd, &d) < 0){ error("dirfwstat: %r"); goto End; } End: filefree(f); free(dst); } wgdone(wg); } void threadmain(int argc, char *argv[]) { int i; char *dst, *p; Waitgroup filewg; salt = time(0); ARGBEGIN{ case 'b': blksz = strtol(EARGF(usage()), nil, 0); break; case 'p': fileprocs = strtol(EARGF(usage()), &p, 0); *p++ = 0; blkprocs = strtol(p, nil, 0); break; case 'x': keepmode = keepmtime = 1; break; case 'u': keepuser = 1; break; case 'g': keepgroup = 1; break; case 'T': notemp = 1; break; }ARGEND; if(argc < 2) usage(); if(argc > 2) multisrc = 1; dst = argv[argc - 1]; filechan = chancreate(sizeof(File*), fileprocs); blkchan = chancreate(sizeof(Blk*), blkprocs); wginit(&filewg, fileprocs); for(i = 0; i < fileprocs; i++) proccreate(fileproc, &filewg, mainstacksize); for(i = 0; i < blkprocs; i++) proccreate(blkproc, nil, mainstacksize); for(i = 0; i < argc -1; i++) clone(argv[i], dst); chanclose(filechan); wgwait(&filewg); if(errors) threadexitsall("errors"); threadexitsall(nil); }