shithub: purgatorio

ref: a411870ee4640241e3c494367d922847da84f972
dir: /appl/cmd/fcp.b/

View raw version
implement Fcp;

include "sys.m";
	sys: Sys;
include "draw.m";
include "arg.m";
include "readdir.m";
	readdir: Readdir;

Fcp: module
{
	init:	fn(nil: ref Draw->Context, argv: list of string);
};

stderr: ref Sys->FD;
errors := 0;

fdc: chan of (ref Sys->FD, ref Sys->FD);

init(nil: ref Draw->Context, argv: list of string)
{
	sys = load Sys Sys->PATH;
	stderr = sys->fildes(2);

	arg := load Arg Arg->PATH;
	if (arg == nil) {
		sys->fprint(stderr, "fcp: cannot load %s: %r\n", Arg->PATH);
		raise "fail:bad module";
	}
	recursive := 0;
	nreaders := nwriters := 8;
	arg->init(argv);
	arg->setusage("\tfcp [-r] [-R nproc] [-W nproc] src target\n\tfcp [-r] [-R nproc] [-W nproc] src ... directory");
	while ((opt := arg->opt()) != 0) {
		case opt {
		'R' =>
			nreaders = int arg->earg();
		'W' =>
			nwriters = int arg->earg();
		'r' =>
			recursive = 1;
		* =>
			arg->usage();
		}
	}
	if(nreaders < 1 || nwriters < 1)
		arg->usage();
	if(nreaders > 1 || nwriters > 1){
		fdc = chan of (ref Sys->FD, ref Sys->FD);
		spawn mstream(fdc, Sys->ATOMICIO, nreaders, nwriters);
	}
	argv = arg->argv();
	argc := len argv;
	if (argc < 2)
		arg->usage();
	arg = nil;

	dst: string;
	for (t := argv; t != nil; t = tl t)
		dst = hd t;

	(ok, dir) := sys->stat(dst);
	todir := (ok != -1 && (dir.mode & Sys->DMDIR));
	if (argc > 2 && !todir) {
		sys->fprint(stderr, "fcp: %s  not a directory\n", dst);
		raise "fail:error";
	}
	if (recursive)
		cpdir(argv, dst);
	else {
		for (; tl argv != nil; argv = tl argv) {
			if (todir)
				cp(hd argv, dst, basename(hd argv));
			else
				cp(hd argv, dst, nil);
		}
	}
	if(fdc != nil)
		fdc <-= (nil, nil);
	if (errors)
		raise "fail:error";
}

basename(s: string): string
{
	for ((nil, ls) := sys->tokenize(s, "/"); ls != nil; ls = tl ls)
		s = hd ls;
	return s;
}

cp(src, dst: string, newname: string)
{
	ok: int;
	ds, dd: Sys->Dir;

	if (newname != nil)
		dst += "/" + newname;
	(ok, ds) = sys->stat(src);
	if (ok < 0) {
		warning(sys->sprint("%s: %r", src));
		return;
	}
	if (ds.mode & Sys->DMDIR) {
		warning(src + " is a directory");
		return;
	}
	(ok, dd) = sys->stat(dst);
	if (ok != -1 &&
			ds.qid.path == dd.qid.path &&
			ds.dev == dd.dev &&
			ds.dtype == dd.dtype) {
		warning(src + " and " + dst + " are the same file");
		return;
	}
	sfd := sys->open(src, sys->OREAD);
	if (sfd == nil) {
		warning(sys->sprint("cannot open %s: %r", src));
		return;
	}
	dfd := sys->create(dst, sys->OWRITE, ds.mode);
	if (dfd == nil) {
		warning(sys->sprint("cannot create %s: %r", dst));
		return;
	}
	copy(sfd, dfd, src, dst);
}

mkdir(d: string, mode: int): int
{
	dfd := sys->create(d, sys->OREAD, sys->DMDIR | mode);
	if (dfd == nil) {
		warning(sys->sprint("cannot make directory %s: %r", d));
		return -1;
	}
	return 0;
}

copy(sfd, dfd: ref Sys->FD, src, dst: string): int
{
	if(fdc != nil){
		fdc <-= (sfd, dfd);
		return 0;
	}
	buf := array[Sys->ATOMICIO] of byte;
	for (;;) {
		r := sys->read(sfd, buf, Sys->ATOMICIO);
		if (r < 0) {
			warning(sys->sprint("error reading %s: %r", src));
			return -1;
		}
		if (r == 0)
			return 0;
		if (sys->write(dfd, buf, r) != r) {
			warning(sys->sprint("error writing %s: %r", dst));
			return -1;
		}
	}
}

cpdir(argv: list of string, dst: string)
{
	readdir = load Readdir Readdir->PATH;
	if (readdir == nil) {
		sys->fprint(stderr, "fcp: cannot load %s: %r\n", Readdir->PATH);
		raise "fail:bad module";
	}
	cache = array[NCACHE] of list of ref Sys->Dir;
	dexists := 0;
	(ok, dd) := sys->stat(dst);
	 # destination file exists
	if (ok != -1) {
		if ((dd.mode & Sys->DMDIR) == 0) {
			warning(dst + ": destination not a directory");
			return;
		}
		dexists = 1;
	}
	for (; tl argv != nil; argv = tl argv) {
		ds: Sys->Dir;
		src := hd argv;
		(ok, ds) = sys->stat(src);
		if (ok < 0) {
			warning(sys->sprint("can't stat %s: %r", src));
			continue;
		}
		if ((ds.mode & Sys->DMDIR) == 0) {
			cp(hd argv, dst, basename(hd argv));
		} else if (dexists) {
			if (ds.qid.path==dd.qid.path &&
					ds.dev==dd.dev &&
					ds.dtype==dd.dtype) {
				warning("cannot copy " + src + " into itself");
				continue;
			}
			copydir(src, dst + "/" + basename(src), ds.mode);
		} else {
			copydir(src, dst, ds.mode);
		}
	}
}

copydir(src, dst: string, srcmode: int)
{
	(ok, nil) := sys->stat(dst);
	if (ok != -1) {
		warning("cannot copy " + src + " onto another directory");
		return;
	}
	tmode := srcmode | 8r777;	# Fix for Nt
	if (mkdir(dst, tmode) == -1)
		return;
	(entries, n) := readdir->init(src, Readdir->COMPACT);
	for (i := 0; i < n; i++) {
		e := entries[i];
		path := src + "/" + e.name;
		if ((e.mode & Sys->DMDIR) == 0)
			cp(path, dst, e.name);
		else if (seen(e))
			warning(path + ": directory loop found");
		else
			copydir(path, dst + "/" + e.name, e.mode);
	}
	chmod(dst, srcmode);
}

# Avoid loops in tangled namespaces. (from du.b)
NCACHE: con 64; # must be power of two
cache: array of list of ref sys->Dir;

seen(dir: ref sys->Dir): int
{
	savlist := cache[int dir.qid.path&(NCACHE-1)];
	for(c := savlist; c!=nil; c = tl c){
		sav := hd c;
		if(dir.qid.path==sav.qid.path &&
			dir.dtype==sav.dtype && dir.dev==sav.dev)
			return 1;
	}
	cache[int dir.qid.path&(NCACHE-1)] = dir :: savlist;
	return 0;
}

warning(e: string)
{
	sys->fprint(stderr, "fcp: %s\n", e);
	errors++;
}

chmod(s: string, mode: int): int
{
	(ok, d) := sys->stat(s);
	if (ok < 0)
		return -1;

	if(d.mode == mode)
		return 0;
	d = sys->nulldir;
	d.mode = mode;
	if (sys->wstat(s, d) < 0) {
		warning(sys->sprint("cannot wstat %s: %r", s));
		return -1;
	}
	return 0;
}

mstream(fdc: chan of (ref Sys->FD, ref Sys->FD), bufsize: int, nin, nout: int)
{
	inc := chan of (ref Sys->FD, big, int, ref Sys->FD);
	outc := chan of (ref Sys->FD, big, array of byte);
	for(i := 0; i < nin; i++)
		spawn readproc(inc, outc);
	for(i = 0; i < nout; i++)
		spawn writeproc(outc);
	while(((src, dst) := <-fdc).t0 != nil){
		(ok, stat) := sys->fstat(src);
		if(ok == -1)
			continue;
		tot := stat.length;
		o := big 0;
		while((n := tot - o) > big 0){
			if(n < big bufsize)
				inc <-= (src, o, int n, dst);
			else
				inc <-= (src, o, bufsize, dst);
			o += big bufsize;
		}
	}
	for(i = 0; i < nin; i++)
		inc <-= (nil, big 0, 0, nil);
	for(i = 0; i < nout; i++)
		outc <-= (nil, big 0, nil);
}

readproc(inc: chan of (ref Sys->FD, big, int, ref Sys->FD), outc: chan of (ref Sys->FD, big, array of byte))
{
	buf: array of byte;
	while(((src, o, nb, dst) := <-inc).t0 != nil){
		if(len buf < nb)
			buf = array[nb*2] of byte;
		n := sys->pread(src, buf, nb, o);
		if(n > 0){
			outc <-= (dst, o, buf[0:n]);
			buf = buf[n:];
		}
	}
}

writeproc(outc: chan of (ref Sys->FD, big, array of byte))
{
	while(((dst, o, buf) := <-outc).t0 != nil)
		sys->pwrite(dst, buf, len buf, o);
}