shithub: clone

ref: ca40584147f8d3d5e0d8628eba44daeaa1ad51c9
dir: /clone.c/

View raw version
#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);
}