/*
* 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;
}
|