1252 lines
24 KiB
C
1252 lines
24 KiB
C
#include "all.h"
|
|
|
|
#define Nwork 16
|
|
|
|
int localdirstat(char*, Dir*);
|
|
int ismatch(char*);
|
|
void conflict(char*, char*, ...);
|
|
void error(char*, ...);
|
|
int isdir(char*);
|
|
|
|
void worker(int fdf, int fdt, char *from, char *to);
|
|
vlong nextoff(void);
|
|
void failure(void *, char *note);
|
|
|
|
QLock lk;
|
|
vlong off;
|
|
|
|
int errors;
|
|
int nconf;
|
|
int donothing;
|
|
int verbose;
|
|
char **match;
|
|
int nmatch;
|
|
int tempspool = 1;
|
|
int safeinstall = 1;
|
|
char *lroot;
|
|
char *rroot;
|
|
Db *clientdb;
|
|
int skip;
|
|
int douid;
|
|
char *mkname(char*, int, char*, char*);
|
|
char localbuf[10240];
|
|
char remotebuf[10240];
|
|
int copyfile(char*, char*, char*, Dir*, int, int*);
|
|
ulong maxnow;
|
|
int maxn;
|
|
char *timefile;
|
|
int timefd;
|
|
int samecontents(char*, char*);
|
|
|
|
Db *copyerr;
|
|
|
|
typedef struct Res Res;
|
|
struct Res
|
|
{
|
|
char c;
|
|
char *name;
|
|
};
|
|
|
|
Res *res;
|
|
int nres;
|
|
|
|
void
|
|
addresolve(int c, char *name)
|
|
{
|
|
if(name[0] == '/')
|
|
name++;
|
|
res = erealloc(res, (nres+1)*sizeof res[0]);
|
|
res[nres].c = c;
|
|
res[nres].name = name;
|
|
nres++;
|
|
}
|
|
|
|
int
|
|
resolve(char *name)
|
|
{
|
|
int i, len;
|
|
|
|
for(i=0; i<nres; i++){
|
|
len = strlen(res[i].name);
|
|
if(len == 0)
|
|
return res[i].c;
|
|
if(strncmp(name, res[i].name, len) == 0 && (name[len]=='/' || name[len] == 0))
|
|
return res[i].c;
|
|
}
|
|
return '?';
|
|
}
|
|
|
|
void
|
|
readtimefile(void)
|
|
{
|
|
int n;
|
|
char buf[24];
|
|
|
|
if((timefd = open(timefile, ORDWR)) < 0
|
|
&& (timefd = create(timefile, ORDWR|OEXCL, 0666)) < 0)
|
|
return;
|
|
|
|
n = readn(timefd, buf, sizeof buf);
|
|
if(n < sizeof buf)
|
|
return;
|
|
|
|
maxnow = atoi(buf);
|
|
maxn = atoi(buf+12);
|
|
}
|
|
|
|
void
|
|
writetimefile(void)
|
|
{
|
|
char buf[24+1];
|
|
|
|
snprint(buf, sizeof buf, "%11lud %11d ", maxnow, maxn);
|
|
pwrite(timefd, buf, 24, 0);
|
|
}
|
|
|
|
static void membogus(char**);
|
|
|
|
void
|
|
addce(char *local)
|
|
{
|
|
char e[ERRMAX];
|
|
Dir d;
|
|
|
|
memset(&d, 0, sizeof d);
|
|
rerrstr(e, sizeof e);
|
|
d.name = atom(e);
|
|
d.uid = "";
|
|
d.gid = "";
|
|
insertdb(copyerr, atom(local), &d);
|
|
}
|
|
|
|
void
|
|
delce(char *local)
|
|
{
|
|
removedb(copyerr, local);
|
|
}
|
|
|
|
void
|
|
chat(char *f, ...)
|
|
{
|
|
Fmt fmt;
|
|
char buf[256];
|
|
va_list arg;
|
|
|
|
if(!verbose)
|
|
return;
|
|
|
|
fmtfdinit(&fmt, 1, buf, sizeof buf);
|
|
va_start(arg, f);
|
|
fmtvprint(&fmt, f, arg);
|
|
va_end(arg);
|
|
fmtfdflush(&fmt);
|
|
}
|
|
|
|
void
|
|
usage(void)
|
|
{
|
|
fprint(2, "usage: replica/applylog [-cnSstuv] [-T timefile] clientdb clientroot serverroot [path ...]\n");
|
|
exits("usage");
|
|
}
|
|
|
|
int
|
|
notexists(char *path)
|
|
{
|
|
char buf[ERRMAX];
|
|
|
|
if(access(path, AEXIST) >= 0)
|
|
return 0;
|
|
|
|
rerrstr(buf, sizeof buf);
|
|
if(strstr(buf, "entry not found") || strstr(buf, "not exist"))
|
|
return 1;
|
|
|
|
/* some other error, like network hangup */
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
main(int argc, char **argv)
|
|
{
|
|
char *f[10], *local, *name, *remote, *s, *t, verb;
|
|
int fd, havedb, havelocal, i, k, n, nf, resolve1, skip;
|
|
int checkedmatch1, checkedmatch2,
|
|
checkedmatch3, checkedmatch4;
|
|
ulong now;
|
|
Biobuf bin;
|
|
Dir dbd, ld, nd, rd;
|
|
Entry *e;
|
|
|
|
membogus(argv);
|
|
quotefmtinstall();
|
|
ARGBEGIN{
|
|
case 's':
|
|
case 'c':
|
|
i = ARGC();
|
|
addresolve(i, EARGF(usage()));
|
|
break;
|
|
case 'n':
|
|
donothing = 1;
|
|
verbose = 1;
|
|
break;
|
|
case 'S':
|
|
safeinstall = 0;
|
|
break;
|
|
case 'T':
|
|
timefile = EARGF(usage());
|
|
break;
|
|
case 't':
|
|
tempspool = 0;
|
|
break;
|
|
case 'u':
|
|
douid = 1;
|
|
break;
|
|
case 'v':
|
|
verbose++;
|
|
break;
|
|
default:
|
|
usage();
|
|
}ARGEND
|
|
|
|
if(argc < 3)
|
|
usage();
|
|
|
|
if(timefile)
|
|
readtimefile();
|
|
|
|
lroot = argv[1];
|
|
if(!isdir(lroot))
|
|
sysfatal("bad local root directory");
|
|
rroot = argv[2];
|
|
if(!isdir(rroot))
|
|
sysfatal("bad remote root directory");
|
|
|
|
match = argv+3;
|
|
nmatch = argc-3;
|
|
for(i=0; i<nmatch; i++)
|
|
if(match[i][0] == '/')
|
|
match[i]++;
|
|
|
|
if((clientdb = opendb(argv[0])) == nil)
|
|
sysfatal("opendb %q: %r", argv[2]);
|
|
|
|
copyerr = opendb(nil);
|
|
|
|
skip = 0;
|
|
Binit(&bin, 0, OREAD);
|
|
for(; s=Brdstr(&bin, '\n', 1); free(s)){
|
|
t = estrdup(s);
|
|
nf = tokenize(s, f, nelem(f));
|
|
if(nf != 10 || strlen(f[2]) != 1){
|
|
skip = 1;
|
|
fprint(2, "warning: skipping bad log entry <%s>\n", t);
|
|
free(t);
|
|
continue;
|
|
}
|
|
free(t);
|
|
now = strtoul(f[0], 0, 0);
|
|
n = atoi(f[1]);
|
|
verb = f[2][0];
|
|
name = f[3];
|
|
if(now < maxnow || (now==maxnow && n <= maxn))
|
|
continue;
|
|
local = mkname(localbuf, sizeof localbuf, lroot, name);
|
|
if(strcmp(f[4], "-") == 0)
|
|
f[4] = f[3];
|
|
remote = mkname(remotebuf, sizeof remotebuf, rroot, f[4]);
|
|
rd.name = f[4];
|
|
rd.mode = strtoul(f[5], 0, 8);
|
|
rd.uid = f[6];
|
|
rd.gid = f[7];
|
|
rd.mtime = strtoul(f[8], 0, 10);
|
|
rd.length = strtoll(f[9], 0, 10);
|
|
havedb = finddb(clientdb, name, &dbd)>=0;
|
|
havelocal = localdirstat(local, &ld)>=0;
|
|
|
|
resolve1 = resolve(name);
|
|
|
|
/*
|
|
* if(!ismatch(name)){
|
|
* skip = 1;
|
|
* continue;
|
|
* }
|
|
*
|
|
* This check used to be right here, but we want
|
|
* the time to be able to move forward past entries
|
|
* that don't match and have already been applied.
|
|
* So now every path below must checked !ismatch(name)
|
|
* before making any changes to the local file
|
|
* system. The fake variable checkedmatch
|
|
* tracks whether !ismatch(name) has been checked.
|
|
* If the compiler doesn't produce any used/set
|
|
* warnings, then all the paths should be okay.
|
|
* Even so, we have the asserts to fall back on.
|
|
*/
|
|
switch(verb){
|
|
case 'd': /* delete file */
|
|
delce(local);
|
|
if(!havelocal) /* doesn't exist; who cares? */
|
|
break;
|
|
if(access(remote, AEXIST) >= 0) /* got recreated! */
|
|
break;
|
|
if(!ismatch(name)){
|
|
if(!skip)
|
|
fprint(2, "stopped updating log apply time because of %s\n", name);
|
|
skip = 1;
|
|
continue;
|
|
}
|
|
SET(checkedmatch1);
|
|
if(!havedb){
|
|
if(resolve1 == 's')
|
|
goto DoRemove;
|
|
else if(resolve1 == 'c')
|
|
goto DoRemoveDb;
|
|
conflict(name, "locally created; will not remove");
|
|
skip = 1;
|
|
continue;
|
|
}
|
|
assert(havelocal && havedb);
|
|
if(dbd.mtime > rd.mtime) /* we have a newer file than what was deleted */
|
|
break;
|
|
if(samecontents(local, remote) > 0){ /* going to get recreated */
|
|
chat("= %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime);
|
|
break;
|
|
}
|
|
if(!(dbd.mode&DMDIR) && (dbd.mtime != ld.mtime || dbd.length != ld.length)){ /* locally modified since we downloaded it */
|
|
if(resolve1 == 's')
|
|
goto DoRemove;
|
|
else if(resolve1 == 'c')
|
|
break;
|
|
conflict(name, "locally modified; will not remove");
|
|
skip = 1;
|
|
continue;
|
|
}
|
|
DoRemove:
|
|
USED(checkedmatch1);
|
|
assert(ismatch(name));
|
|
chat("d %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime);
|
|
if(donothing)
|
|
break;
|
|
if(remove(local) < 0){
|
|
error("removing %q: %r", name);
|
|
skip = 1;
|
|
continue;
|
|
}
|
|
DoRemoveDb:
|
|
USED(checkedmatch1);
|
|
assert(ismatch(name));
|
|
removedb(clientdb, name);
|
|
break;
|
|
|
|
case 'a': /* add file */
|
|
if(!havedb){
|
|
if(!ismatch(name)){
|
|
if(!skip)
|
|
fprint(2, "stopped updating log apply time because of %s\n", name);
|
|
skip = 1;
|
|
continue;
|
|
}
|
|
SET(checkedmatch2);
|
|
if(!havelocal)
|
|
goto DoCreate;
|
|
if((ld.mode&DMDIR) && (rd.mode&DMDIR))
|
|
break;
|
|
if(samecontents(local, remote) > 0){
|
|
chat("= %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime);
|
|
goto DoCreateDb;
|
|
}
|
|
if(resolve1 == 's')
|
|
goto DoCreate;
|
|
else if(resolve1 == 'c')
|
|
goto DoCreateDb;
|
|
conflict(name, "locally created; will not overwrite");
|
|
skip = 1;
|
|
continue;
|
|
}
|
|
assert(havedb);
|
|
if(dbd.mtime >= rd.mtime) /* already created this file; ignore */
|
|
break;
|
|
if(havelocal){
|
|
if((ld.mode&DMDIR) && (rd.mode&DMDIR))
|
|
break;
|
|
if(!ismatch(name)){
|
|
if(!skip)
|
|
fprint(2, "stopped updating log apply time because of %s\n", name);
|
|
skip = 1;
|
|
continue;
|
|
}
|
|
SET(checkedmatch2);
|
|
if(samecontents(local, remote) > 0){
|
|
chat("= %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime);
|
|
goto DoCreateDb;
|
|
}
|
|
if(dbd.mtime==ld.mtime && dbd.length==ld.length)
|
|
goto DoCreate;
|
|
if(resolve1=='s')
|
|
goto DoCreate;
|
|
else if(resolve1 == 'c')
|
|
break;
|
|
conflict(name, "locally modified; will not overwrite");
|
|
skip = 1;
|
|
continue;
|
|
}
|
|
if(!ismatch(name)){
|
|
if(!skip)
|
|
fprint(2, "stopped updating log apply time because of %s\n", name);
|
|
skip = 1;
|
|
continue;
|
|
}
|
|
SET(checkedmatch2);
|
|
DoCreate:
|
|
USED(checkedmatch2);
|
|
assert(ismatch(name));
|
|
if(notexists(remote)){
|
|
addce(local);
|
|
/* no skip=1 */
|
|
break;;
|
|
}
|
|
chat("a %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime);
|
|
if(donothing)
|
|
break;
|
|
if(rd.mode&DMDIR){
|
|
fd = create(local, OREAD, DMDIR);
|
|
if(fd < 0 && isdir(local))
|
|
fd = open(local, OREAD);
|
|
if(fd < 0){
|
|
error("mkdir %q: %r", name);
|
|
skip = 1;
|
|
continue;
|
|
}
|
|
nulldir(&nd);
|
|
nd.mode = rd.mode;
|
|
if(dirfwstat(fd, &nd) < 0)
|
|
fprint(2, "warning: cannot set mode on %q\n", local);
|
|
nulldir(&nd);
|
|
nd.gid = rd.gid;
|
|
if(dirfwstat(fd, &nd) < 0)
|
|
fprint(2, "warning: cannot set gid on %q\n", local);
|
|
if(douid){
|
|
nulldir(&nd);
|
|
nd.uid = rd.uid;
|
|
if(dirfwstat(fd, &nd) < 0)
|
|
fprint(2, "warning: cannot set uid on %q\n", local);
|
|
}
|
|
close(fd);
|
|
rd.mtime = now;
|
|
}else{
|
|
if(copyfile(local, remote, name, &rd, 1, &k) < 0){
|
|
if(k)
|
|
addce(local);
|
|
skip = 1;
|
|
continue;
|
|
}
|
|
}
|
|
DoCreateDb:
|
|
USED(checkedmatch2);
|
|
assert(ismatch(name));
|
|
insertdb(clientdb, name, &rd);
|
|
break;
|
|
|
|
case 'c': /* change contents */
|
|
if(!havedb){
|
|
if(notexists(remote)){
|
|
addce(local);
|
|
/* no skip=1 */
|
|
break;
|
|
}
|
|
if(!ismatch(name)){
|
|
if(!skip)
|
|
fprint(2, "stopped updating log apply time because of %s\n", name);
|
|
skip = 1;
|
|
continue;
|
|
}
|
|
SET(checkedmatch3);
|
|
if(resolve1 == 's')
|
|
goto DoCopy;
|
|
else if(resolve1=='c')
|
|
goto DoCopyDb;
|
|
if(samecontents(local, remote) > 0){
|
|
chat("= %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime);
|
|
goto DoCopyDb;
|
|
}
|
|
if(havelocal)
|
|
conflict(name, "locally created; will not update");
|
|
else
|
|
conflict(name, "not replicated; will not update");
|
|
skip = 1;
|
|
continue;
|
|
}
|
|
if(dbd.mtime >= rd.mtime) /* already have/had this version; ignore */
|
|
break;
|
|
if(!ismatch(name)){
|
|
if(!skip)
|
|
fprint(2, "stopped updating log apply time because of %s\n", name);
|
|
skip = 1;
|
|
continue;
|
|
}
|
|
SET(checkedmatch3);
|
|
if(!havelocal){
|
|
if(notexists(remote)){
|
|
addce(local);
|
|
/* no skip=1 */
|
|
break;
|
|
}
|
|
if(resolve1 == 's')
|
|
goto DoCopy;
|
|
else if(resolve1 == 'c')
|
|
break;
|
|
conflict(name, "locally removed; will not update");
|
|
skip = 1;
|
|
continue;
|
|
}
|
|
assert(havedb && havelocal);
|
|
if(dbd.mtime != ld.mtime || dbd.length != ld.length){
|
|
if(notexists(remote)){
|
|
addce(local);
|
|
/* no skip=1 */
|
|
break;
|
|
}
|
|
if(samecontents(local, remote) > 0){
|
|
chat("= %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime);
|
|
goto DoCopyDb;
|
|
}
|
|
if(resolve1 == 's')
|
|
goto DoCopy;
|
|
else if(resolve1 == 'c')
|
|
break;
|
|
conflict(name, "locally modified; will not update [%llud %lud -> %llud %lud]", dbd.length, dbd.mtime, ld.length, ld.mtime);
|
|
skip = 1;
|
|
continue;
|
|
}
|
|
DoCopy:
|
|
USED(checkedmatch3);
|
|
assert(ismatch(name));
|
|
if(notexists(remote)){
|
|
addce(local);
|
|
/* no skip=1 */
|
|
break;
|
|
}
|
|
chat("c %q\n", name);
|
|
if(donothing)
|
|
break;
|
|
if(copyfile(local, remote, name, &rd, 0, &k) < 0){
|
|
if(k)
|
|
addce(local);
|
|
skip = 1;
|
|
continue;
|
|
}
|
|
DoCopyDb:
|
|
USED(checkedmatch3);
|
|
assert(ismatch(name));
|
|
if(!havedb){
|
|
if(havelocal)
|
|
dbd = ld;
|
|
else
|
|
dbd = rd;
|
|
}
|
|
dbd.mtime = rd.mtime;
|
|
dbd.length = rd.length;
|
|
insertdb(clientdb, name, &dbd);
|
|
break;
|
|
|
|
case 'm': /* change metadata */
|
|
if(!havedb){
|
|
if(notexists(remote)){
|
|
addce(local);
|
|
/* no skip=1 */
|
|
break;
|
|
}
|
|
if(!ismatch(name)){
|
|
if(!skip)
|
|
fprint(2, "stopped updating log apply time because of %s\n", name);
|
|
skip = 1;
|
|
continue;
|
|
}
|
|
SET(checkedmatch4);
|
|
if(resolve1 == 's'){
|
|
USED(checkedmatch4);
|
|
SET(checkedmatch2);
|
|
goto DoCreate;
|
|
}
|
|
else if(resolve1 == 'c')
|
|
goto DoMetaDb;
|
|
if(havelocal)
|
|
conflict(name, "locally created; will not update metadata");
|
|
else
|
|
conflict(name, "not replicated; will not update metadata");
|
|
skip = 1;
|
|
continue;
|
|
}
|
|
if(!(dbd.mode&DMDIR) && dbd.mtime > rd.mtime) /* have newer version; ignore */
|
|
break;
|
|
if((dbd.mode&DMDIR) && dbd.mtime > now)
|
|
break;
|
|
if(havelocal && (!douid || strcmp(ld.uid, rd.uid)==0) && strcmp(ld.gid, rd.gid)==0 && ld.mode==rd.mode)
|
|
break;
|
|
if(!havelocal){
|
|
if(notexists(remote)){
|
|
addce(local);
|
|
/* no skip=1 */
|
|
break;
|
|
}
|
|
if(!ismatch(name)){
|
|
if(!skip)
|
|
fprint(2, "stopped updating log apply time because of %s\n", name);
|
|
skip = 1;
|
|
continue;
|
|
}
|
|
SET(checkedmatch4);
|
|
if(resolve1 == 's'){
|
|
USED(checkedmatch4);
|
|
SET(checkedmatch2);
|
|
goto DoCreate;
|
|
}
|
|
else if(resolve1 == 'c')
|
|
break;
|
|
conflict(name, "locally removed; will not update metadata");
|
|
skip = 1;
|
|
continue;
|
|
}
|
|
if(!(dbd.mode&DMDIR) && (dbd.mtime != ld.mtime || dbd.length != ld.length)){ /* this check might be overkill */
|
|
if(notexists(remote)){
|
|
addce(local);
|
|
/* no skip=1 */
|
|
break;
|
|
}
|
|
if(!ismatch(name)){
|
|
if(!skip)
|
|
fprint(2, "stopped updating log apply time because of %s\n", name);
|
|
skip = 1;
|
|
continue;
|
|
}
|
|
SET(checkedmatch4);
|
|
if(resolve1 == 's' || samecontents(local, remote) > 0)
|
|
goto DoMeta;
|
|
else if(resolve1 == 'c')
|
|
break;
|
|
conflict(name, "contents locally modified (%s); will not update metadata to %s %s %luo",
|
|
dbd.mtime != ld.mtime ? "mtime" :
|
|
dbd.length != ld.length ? "length" :
|
|
"unknown",
|
|
rd.uid, rd.gid, rd.mode);
|
|
skip = 1;
|
|
continue;
|
|
}
|
|
if((douid && strcmp(ld.uid, dbd.uid)!=0) || strcmp(ld.gid, dbd.gid)!=0 || ld.mode!=dbd.mode){
|
|
if(notexists(remote)){
|
|
addce(local);
|
|
/* no skip=1 */
|
|
break;
|
|
}
|
|
if(!ismatch(name)){
|
|
if(!skip)
|
|
fprint(2, "stopped updating log apply time because of %s\n", name);
|
|
skip = 1;
|
|
continue;
|
|
}
|
|
SET(checkedmatch4);
|
|
if(resolve1 == 's')
|
|
goto DoMeta;
|
|
else if(resolve1 == 'c')
|
|
break;
|
|
conflict(name, "metadata locally changed; will not update metadata to %s %s %luo", rd.uid, rd.gid, rd.mode);
|
|
skip = 1;
|
|
continue;
|
|
}
|
|
if(!ismatch(name)){
|
|
if(!skip)
|
|
fprint(2, "stopped updating log apply time because of %s\n", name);
|
|
skip = 1;
|
|
continue;
|
|
}
|
|
SET(checkedmatch4);
|
|
DoMeta:
|
|
USED(checkedmatch4);
|
|
assert(ismatch(name));
|
|
if(notexists(remote)){
|
|
addce(local);
|
|
/* no skip=1 */
|
|
break;
|
|
}
|
|
chat("m %q %luo %q %q %lud\n", name, rd.mode, rd.uid, rd.gid, rd.mtime);
|
|
if(donothing)
|
|
break;
|
|
nulldir(&nd);
|
|
nd.gid = rd.gid;
|
|
nd.mode = rd.mode;
|
|
if(douid)
|
|
nd.uid = rd.uid;
|
|
if(dirwstat(local, &nd) < 0){
|
|
error("dirwstat %q: %r", name);
|
|
skip = 1;
|
|
continue;
|
|
}
|
|
DoMetaDb:
|
|
USED(checkedmatch4);
|
|
assert(ismatch(name));
|
|
if(!havedb){
|
|
if(havelocal)
|
|
dbd = ld;
|
|
else
|
|
dbd = rd;
|
|
}
|
|
if(dbd.mode&DMDIR)
|
|
dbd.mtime = now;
|
|
dbd.gid = rd.gid;
|
|
dbd.mode = rd.mode;
|
|
if(douid)
|
|
dbd.uid = rd.uid;
|
|
insertdb(clientdb, name, &dbd);
|
|
break;
|
|
}
|
|
if(!skip && !donothing){
|
|
maxnow = now;
|
|
maxn = n;
|
|
}
|
|
}
|
|
|
|
for(e = (Entry*)avlmin(copyerr->avl); e != nil; e = (Entry*)avlnext(e))
|
|
error("copying %q: %s\n", e->name, e->d.name);
|
|
|
|
if(timefile)
|
|
writetimefile();
|
|
if(nconf)
|
|
exits("conflicts");
|
|
|
|
if(errors)
|
|
exits("errors");
|
|
exits(nil);
|
|
}
|
|
|
|
|
|
char*
|
|
mkname(char *buf, int nbuf, char *a, char *b)
|
|
{
|
|
if(strlen(a)+strlen(b)+2 > nbuf)
|
|
sysfatal("name too long");
|
|
|
|
strcpy(buf, a);
|
|
if(a[strlen(a)-1] != '/')
|
|
strcat(buf, "/");
|
|
strcat(buf, b);
|
|
return buf;
|
|
}
|
|
|
|
int
|
|
isdir(char *s)
|
|
{
|
|
ulong m;
|
|
Dir *d;
|
|
|
|
if((d = dirstat(s)) == nil)
|
|
return 0;
|
|
m = d->mode;
|
|
free(d);
|
|
return (m&DMDIR) != 0;
|
|
}
|
|
|
|
void
|
|
conflict(char *name, char *f, ...)
|
|
{
|
|
char *s;
|
|
va_list arg;
|
|
|
|
va_start(arg, f);
|
|
s = vsmprint(f, arg);
|
|
va_end(arg);
|
|
|
|
fprint(2, "! %s: %s\n", name, s);
|
|
free(s);
|
|
|
|
nconf++;
|
|
}
|
|
|
|
void
|
|
error(char *f, ...)
|
|
{
|
|
char *s;
|
|
va_list arg;
|
|
|
|
va_start(arg, f);
|
|
s = vsmprint(f, arg);
|
|
va_end(arg);
|
|
fprint(2, "error: %s\n", s);
|
|
free(s);
|
|
errors = 1;
|
|
}
|
|
|
|
int
|
|
ismatch(char *s)
|
|
{
|
|
int i, len;
|
|
|
|
if(nmatch == 0)
|
|
return 1;
|
|
for(i=0; i<nmatch; i++){
|
|
len = strlen(match[i]);
|
|
if(len == 0)
|
|
return 1;
|
|
if(strncmp(s, match[i], len) == 0 && (s[len]=='/' || s[len] == 0))
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
localdirstat(char *name, Dir *d)
|
|
{
|
|
static Dir *d2;
|
|
|
|
free(d2);
|
|
if((d2 = dirstat(name)) == nil)
|
|
return -1;
|
|
*d = *d2;
|
|
return 0;
|
|
}
|
|
|
|
enum { DEFB = 8192 };
|
|
|
|
static int
|
|
cmp1(int fd1, int fd2)
|
|
{
|
|
char buf1[DEFB];
|
|
char buf2[DEFB];
|
|
int n1, n2;
|
|
|
|
for(;;){
|
|
n1 = readn(fd1, buf1, DEFB);
|
|
n2 = readn(fd2, buf2, DEFB);
|
|
if(n1 < 0 || n2 < 0)
|
|
return -1;
|
|
if(n1 != n2)
|
|
return 0;
|
|
if(n1 == 0)
|
|
return 1;
|
|
if(memcmp(buf1, buf2, n1) != 0)
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int
|
|
copy1(int fdf, int fdt, char *from, char *to)
|
|
{
|
|
int i, n, rv, pid[Nwork];
|
|
Waitmsg *w;
|
|
|
|
n = 0;
|
|
off = 0;
|
|
for(i=0; i<Nwork; i++){
|
|
switch(pid[n] = rfork(RFPROC|RFMEM)){
|
|
case 0:
|
|
notify(failure);
|
|
worker(fdf, fdt, from, to);
|
|
case -1:
|
|
break;
|
|
default:
|
|
n++;
|
|
break;
|
|
}
|
|
}
|
|
if(n == 0){
|
|
fprint(2, "cp: rfork: %r\n");
|
|
return -1;
|
|
}
|
|
|
|
rv = 0;
|
|
while((w = wait()) != nil){
|
|
if(w->msg[0]){
|
|
rv = -1;
|
|
for(i=0; i<n; i++)
|
|
if(pid[i] > 0)
|
|
postnote(PNPROC, pid[i], "failure");
|
|
}
|
|
free(w);
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
void
|
|
worker(int fdf, int fdt, char *from, char *to)
|
|
{
|
|
char buf[DEFB], *bp;
|
|
long len, n;
|
|
vlong o;
|
|
|
|
len = sizeof(buf);
|
|
bp = buf;
|
|
o = nextoff();
|
|
|
|
while(n = pread(fdf, bp, len, o)){
|
|
if(n < 0){
|
|
fprint(2, "reading %s: %r\n", from);
|
|
_exits("bad");
|
|
}
|
|
if(pwrite(fdt, buf, n, o) != n){
|
|
fprint(2, "writing %s: %r\n", to);
|
|
_exits("bad");
|
|
}
|
|
bp += n;
|
|
o += n;
|
|
len -= n;
|
|
if(len == 0){
|
|
len = sizeof buf;
|
|
bp = buf;
|
|
o = nextoff();
|
|
}
|
|
}
|
|
_exits(nil);
|
|
}
|
|
|
|
vlong
|
|
nextoff(void)
|
|
{
|
|
vlong o;
|
|
|
|
qlock(&lk);
|
|
o = off;
|
|
off += DEFB;
|
|
qunlock(&lk);
|
|
|
|
return o;
|
|
}
|
|
|
|
void
|
|
failure(void*, char *note)
|
|
{
|
|
if(strcmp(note, "failure") == 0)
|
|
_exits(nil);
|
|
noted(NDFLT);
|
|
}
|
|
|
|
|
|
static int
|
|
opentemp(char *template)
|
|
{
|
|
int fd, i;
|
|
char *p;
|
|
|
|
p = estrdup(template);
|
|
fd = -1;
|
|
for(i=0; i<10; i++){
|
|
mktemp(p);
|
|
if((fd=create(p, ORDWR|OEXCL|ORCLOSE, 0000)) >= 0)
|
|
break;
|
|
strcpy(p, template);
|
|
}
|
|
if(fd < 0)
|
|
return -1;
|
|
strcpy(template, p);
|
|
free(p);
|
|
return fd;
|
|
}
|
|
|
|
int
|
|
copyfile(char *local, char *remote, char *name, Dir *d, int dowstat, int *printerror)
|
|
{
|
|
Dir *d0, *d1, *dl;
|
|
Dir nd;
|
|
int rfd, tfd, wfd, didcreate;
|
|
char tmp[32], *p, *safe;
|
|
char err[ERRMAX];
|
|
|
|
Again:
|
|
*printerror = 0;
|
|
if((rfd = open(remote, OREAD)) < 0)
|
|
return -1;
|
|
|
|
d0 = dirfstat(rfd);
|
|
if(d0 == nil){
|
|
close(rfd);
|
|
return -1;
|
|
}
|
|
*printerror = 1;
|
|
if(!tempspool){
|
|
tfd = rfd;
|
|
goto DoCopy;
|
|
}
|
|
strcpy(tmp, "/tmp/replicaXXXXXXXX");
|
|
tfd = opentemp(tmp);
|
|
if(tfd < 0){
|
|
close(rfd);
|
|
free(d0);
|
|
return -1;
|
|
}
|
|
if(copy1(rfd, tfd, remote, tmp) < 0 || (d1 = dirfstat(rfd)) == nil){
|
|
close(rfd);
|
|
close(tfd);
|
|
free(d0);
|
|
return -1;
|
|
}
|
|
close(rfd);
|
|
if(d0->qid.path != d1->qid.path
|
|
|| d0->qid.vers != d1->qid.vers
|
|
|| d0->mtime != d1->mtime
|
|
|| d0->length != d1->length){
|
|
/* file changed underfoot; go around again */
|
|
close(tfd);
|
|
free(d0);
|
|
free(d1);
|
|
goto Again;
|
|
}
|
|
free(d1);
|
|
if(seek(tfd, 0, 0) != 0){
|
|
close(tfd);
|
|
free(d0);
|
|
return -1;
|
|
}
|
|
|
|
DoCopy:
|
|
/*
|
|
* clumsy but important hack to do safeinstall-like installs.
|
|
*/
|
|
p = strchr(name, '/');
|
|
if(safeinstall && p && strncmp(p, "/bin/", 5) == 0 && access(local, AEXIST) >= 0){
|
|
/*
|
|
* remove bin/_targ
|
|
*/
|
|
safe = emalloc(strlen(local)+2);
|
|
strcpy(safe, local);
|
|
p = strrchr(safe, '/')+1;
|
|
memmove(p+1, p, strlen(p)+1);
|
|
p[0] = '_';
|
|
remove(safe); /* ignore failure */
|
|
|
|
/*
|
|
* rename bin/targ to bin/_targ
|
|
*/
|
|
nulldir(&nd);
|
|
nd.name = p;
|
|
if(dirwstat(local, &nd) < 0)
|
|
fprint(2, "warning: rename %s to %s: %r\n", local, p);
|
|
}
|
|
|
|
didcreate = 0;
|
|
if((dl = dirstat(local)) == nil){
|
|
if((wfd = create(local, OWRITE, 0)) >= 0){
|
|
didcreate = 1;
|
|
goto okay;
|
|
}
|
|
goto err;
|
|
}else{
|
|
if((wfd = open(local, OTRUNC|OWRITE)) >= 0)
|
|
goto okay;
|
|
rerrstr(err, sizeof err);
|
|
if(strstr(err, "permission") == nil)
|
|
goto err;
|
|
nulldir(&nd);
|
|
/*
|
|
* Assume the person running pull is in the appropriate
|
|
* groups. We could set 0666 instead, but I'm worried
|
|
* about leaving the file world-readable or world-writable
|
|
* when it shouldn't be.
|
|
*/
|
|
nd.mode = dl->mode | 0660;
|
|
if(nd.mode == dl->mode)
|
|
goto err;
|
|
if(dirwstat(local, &nd) < 0)
|
|
goto err;
|
|
if((wfd = open(local, OTRUNC|OWRITE)) >= 0){
|
|
nd.mode = dl->mode;
|
|
if(dirfwstat(wfd, &nd) < 0)
|
|
fprint(2, "warning: set mode on %s to 0660 to open; cannot set back to %luo: %r\n", local, nd.mode);
|
|
goto okay;
|
|
}
|
|
nd.mode = dl->mode;
|
|
if(dirwstat(local, &nd) < 0)
|
|
fprint(2, "warning: set mode on %s to %luo to open; open failed; cannot set mode back to %luo: %r\n", local, nd.mode|0660, nd.mode);
|
|
goto err;
|
|
}
|
|
|
|
err:
|
|
close(tfd);
|
|
free(d0);
|
|
free(dl);
|
|
return -1;
|
|
|
|
okay:
|
|
free(dl);
|
|
if(copy1(tfd, wfd, tmp, local) < 0){
|
|
close(tfd);
|
|
close(wfd);
|
|
free(d0);
|
|
return -1;
|
|
}
|
|
close(tfd);
|
|
if(didcreate || dowstat){
|
|
nulldir(&nd);
|
|
nd.mode = d->mode;
|
|
if(dirfwstat(wfd, &nd) < 0)
|
|
fprint(2, "warning: cannot set mode on %s\n", local);
|
|
nulldir(&nd);
|
|
nd.gid = d->gid;
|
|
if(dirfwstat(wfd, &nd) < 0)
|
|
fprint(2, "warning: cannot set gid on %s\n", local);
|
|
if(douid){
|
|
nulldir(&nd);
|
|
nd.uid = d->uid;
|
|
if(dirfwstat(wfd, &nd) < 0)
|
|
fprint(2, "warning: cannot set uid on %s\n", local);
|
|
}
|
|
}
|
|
d->mtime = d0->mtime;
|
|
d->length = d0->length;
|
|
nulldir(&nd);
|
|
nd.mtime = d->mtime;
|
|
if(dirfwstat(wfd, &nd) < 0)
|
|
fprint(2, "warning: cannot set mtime on %s\n", local);
|
|
free(d0);
|
|
|
|
close(wfd);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
samecontents(char *local, char *remote)
|
|
{
|
|
Dir *d0, *d1;
|
|
int rfd, tfd, lfd, ret;
|
|
char tmp[32];
|
|
|
|
/* quick check: sizes must match */
|
|
d1 = nil;
|
|
if((d0 = dirstat(local)) == nil || (d1 = dirstat(remote)) == nil){
|
|
free(d0);
|
|
free(d1);
|
|
return -1;
|
|
}
|
|
if(d0->length != d1->length){
|
|
free(d0);
|
|
free(d1);
|
|
return 0;
|
|
}
|
|
|
|
Again:
|
|
if((rfd = open(remote, OREAD)) < 0)
|
|
return -1;
|
|
d0 = dirfstat(rfd);
|
|
if(d0 == nil){
|
|
close(rfd);
|
|
return -1;
|
|
}
|
|
|
|
strcpy(tmp, "/tmp/replicaXXXXXXXX");
|
|
tfd = opentemp(tmp);
|
|
if(tfd < 0){
|
|
close(rfd);
|
|
free(d0);
|
|
return -1;
|
|
}
|
|
if(copy1(rfd, tfd, remote, tmp) < 0 || (d1 = dirfstat(rfd)) == nil){
|
|
close(rfd);
|
|
close(tfd);
|
|
free(d0);
|
|
return -1;
|
|
}
|
|
close(rfd);
|
|
if(d0->qid.path != d1->qid.path
|
|
|| d0->qid.vers != d1->qid.vers
|
|
|| d0->mtime != d1->mtime
|
|
|| d0->length != d1->length){
|
|
/* file changed underfoot; go around again */
|
|
close(tfd);
|
|
free(d0);
|
|
free(d1);
|
|
goto Again;
|
|
}
|
|
free(d1);
|
|
free(d0);
|
|
if(seek(tfd, 0, 0) != 0){
|
|
close(tfd);
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* now compare
|
|
*/
|
|
if((lfd = open(local, OREAD)) < 0){
|
|
close(tfd);
|
|
return -1;
|
|
}
|
|
|
|
ret = cmp1(lfd, tfd);
|
|
close(lfd);
|
|
close(tfd);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Applylog might try to overwrite itself.
|
|
* To avoid problems with this, we copy ourselves
|
|
* into /tmp and then re-exec.
|
|
*/
|
|
char *rmargv0;
|
|
|
|
static void
|
|
rmself(void)
|
|
{
|
|
remove(rmargv0);
|
|
}
|
|
|
|
static int
|
|
genopentemp(char *template, int mode, int perm)
|
|
{
|
|
int fd, i;
|
|
char *p;
|
|
|
|
p = estrdup(template);
|
|
fd = -1;
|
|
for(i=0; i<10; i++){
|
|
mktemp(p);
|
|
if(access(p, 0) < 0 && (fd=create(p, mode, perm)) >= 0)
|
|
break;
|
|
strcpy(p, template);
|
|
}
|
|
if(fd < 0)
|
|
sysfatal("could not create temporary file");
|
|
|
|
strcpy(template, p);
|
|
free(p);
|
|
|
|
return fd;
|
|
}
|
|
|
|
static void
|
|
membogus(char **argv)
|
|
{
|
|
int n, fd, wfd;
|
|
char template[50], buf[1024];
|
|
|
|
if(strncmp(argv[0], "/tmp/_applylog_", 1+3+1+1+8+1)==0) {
|
|
rmargv0 = argv[0];
|
|
atexit(rmself);
|
|
return;
|
|
}
|
|
|
|
if((fd = open(argv[0], OREAD)) < 0)
|
|
return;
|
|
|
|
strcpy(template, "/tmp/_applylog_XXXXXX");
|
|
if((wfd = genopentemp(template, OWRITE, 0700)) < 0)
|
|
return;
|
|
|
|
while((n = read(fd, buf, sizeof buf)) > 0)
|
|
if(write(wfd, buf, n) != n)
|
|
goto Error;
|
|
|
|
if(n != 0)
|
|
goto Error;
|
|
|
|
close(fd);
|
|
close(wfd);
|
|
|
|
argv[0] = template;
|
|
exec(template, argv);
|
|
fprint(2, "exec error %r\n");
|
|
|
|
Error:
|
|
close(fd);
|
|
close(wfd);
|
|
remove(template);
|
|
return;
|
|
}
|