Plan 9 from Bell Labs’s /usr/web/sources/contrib/arisawa/cpdir/cpdir.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


/*
*	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 <u.h>
#include <libc.h>
#include <bio.h>
#include <ctype.h>
#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(hi<lo){ t=lo; lo=hi; hi=t; }
				}
				if(lo<=c && c<=hi) hit=1;
			}
			if(compl) hit=!hit;
			if(!hit) return 0;
			break;
		default:
			if(*p != *s) return 0;
		}
	}
	return *s=='\0';
}

void *
emalloc(ulong n)
{
	void *p;

	if((p = malloc(n)) == 0)
		sysfatal("out of memory");
	return p;
}

char *
mkpath2(char *path1, char *path2)
{
	char *p;
	p = emalloc(strlen(path1)+strlen(path2)+5);
	sprint(p, "%s/%s", path1, path2);
	p = cleanname(p);
	return p;
}

char *
mkpath3(char *path1, char *path2, char *path3)
{
	char *p;
	p = emalloc(strlen(path1)+strlen(path2)+strlen(path3)+5);
	sprint(p, "%s/%s/%s", path1, path2, path3);
	p = cleanname(p);
	return p;
}

char*
strtrim(char *s)
{
	char *t;
	while(*s && isspace(*s))
		s++;
	t = s + strlen(s) - 1;
	if(s < t){
		while(*t && isspace(*t)) t--;
		t++;
		*t = 0;
	}
	return s;
}

int
xmatch(char *path)
{
	List *p;
	p = xlist;
	while(p){
		if(match(path, p->data))
			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<n; i++){
		name = mkpath2(path, db[i].name);
		if(db[i].mode & DMDIR)
			rmdir(name, uid, gid);
		else
			rmfile(name, uid, gid);
		free(name);
	}

	status = remove(path);
	free(db);
	debug("done rmdir %s\n", path);
	return status;
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.