1250 lines
28 KiB
C
1250 lines
28 KiB
C
/*
|
|
* Copyright (c) 1983 Regents of the University of California.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. All advertising materials mentioning features or use of this software
|
|
* must display the following acknowledgement:
|
|
* This product includes software developed by the University of
|
|
* California, Berkeley and its contributors.
|
|
* 4. Neither the name of the University nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#ifndef lint
|
|
static char RCSid[] =
|
|
"$Id: client.c,v 1.3 1995/09/22 07:37:08 ack Exp $";
|
|
|
|
static char sccsid[] = "@(#)client.c";
|
|
|
|
static char copyright[] =
|
|
"@(#) Copyright (c) 1983 Regents of the University of California.\n\
|
|
All rights reserved.\n";
|
|
#endif /* not lint */
|
|
|
|
/*
|
|
* Routines used in client mode to communicate with remove server.
|
|
*/
|
|
|
|
#include "defs.h"
|
|
#ifdef sgi
|
|
#include <limits.h>
|
|
#endif
|
|
|
|
/*
|
|
* Update status
|
|
*/
|
|
#define US_NOTHING 0 /* No update needed */
|
|
#define US_NOENT 1 /* Entry does not exist */
|
|
#define US_OUTDATE 2 /* Entry is out of date */
|
|
#define US_DOCOMP 3 /* Do a binary comparison */
|
|
#define US_MODE 4 /* Modes of file differ */
|
|
|
|
struct linkbuf *ihead = NULL; /* list of files with more than one link */
|
|
char buf[BUFSIZ]; /* general purpose buffer */
|
|
u_char respbuff[BUFSIZ]; /* Response buffer */
|
|
char target[BUFSIZ]; /* target/source directory name */
|
|
char source[BUFSIZ]; /* source directory name */
|
|
char *ptarget; /* pointer to end of target name */
|
|
char *Tdest; /* pointer to last T dest*/
|
|
struct namelist *updfilelist = NULL; /* List of updated files */
|
|
|
|
static int sendit();
|
|
|
|
/*
|
|
* return remote file pathname (relative from target)
|
|
*/
|
|
char *remfilename(src, dest, path, rname, destdir)
|
|
char *src, *dest, *path, *rname;
|
|
int destdir;
|
|
{
|
|
extern struct namelist *filelist;
|
|
register char *lname, *cp;
|
|
static char buff[BUFSIZ];
|
|
int srclen, pathlen;
|
|
char *p;
|
|
|
|
|
|
debugmsg(DM_MISC,
|
|
"remfilename: src=%s dest=%s path=%s rname=%s destdir=%d\n",
|
|
A(src), A(dest), A(path), A(rname), destdir);
|
|
|
|
if (!dest) {
|
|
debugmsg(DM_MISC, "remfilename: remote filename=%s\n", path);
|
|
return(path);
|
|
}
|
|
|
|
if (!destdir) {
|
|
debugmsg(DM_MISC, "remfilename: remote filename=%s\n", dest);
|
|
return(dest);
|
|
}
|
|
|
|
buff[0] = CNULL;
|
|
lname = buff;
|
|
if (path && *path) {
|
|
cp = strrchr(path, '/');
|
|
if (cp == NULL)
|
|
(void) sprintf(buff, "%s/%s", dest, path);
|
|
else {
|
|
srclen = strlen(src);
|
|
pathlen = strlen(path);
|
|
if (srclen >= pathlen)
|
|
cp++; /* basename(path) */
|
|
else {
|
|
if (filelist && filelist->n_next == NULL)
|
|
/* path relative to src */
|
|
cp = path + srclen;
|
|
else {
|
|
if ((p = strrchr(src, '/')))
|
|
cp = path + srclen - strlen(p);
|
|
else
|
|
cp = path;
|
|
}
|
|
}
|
|
if ((*cp != '/') && *cp)
|
|
(void) sprintf(buff, "%s/%s", dest, cp);
|
|
else
|
|
(void) sprintf(buff, "%s%s", dest, cp);
|
|
}
|
|
} else
|
|
strcpy(lname, dest);
|
|
|
|
debugmsg(DM_MISC, "remfilename: remote filename=%s\n", lname);
|
|
|
|
return(lname);
|
|
}
|
|
|
|
/*
|
|
* Return true if name is in the list.
|
|
*/
|
|
int inlist(list, file)
|
|
struct namelist *list;
|
|
char *file;
|
|
{
|
|
register struct namelist *nl;
|
|
|
|
for (nl = list; nl != NULL; nl = nl->n_next)
|
|
if (strcmp(file, nl->n_name) == 0)
|
|
return(1);
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
* Run any special commands for this file
|
|
*/
|
|
static void runspecial(starget, opts, rname, destdir)
|
|
char *starget;
|
|
opt_t opts;
|
|
char *rname;
|
|
int destdir;
|
|
{
|
|
register struct subcmd *sc;
|
|
extern struct subcmd *subcmds;
|
|
char *rfile;
|
|
|
|
rfile = remfilename(source, Tdest, target, rname, destdir);
|
|
|
|
for (sc = subcmds; sc != NULL; sc = sc->sc_next) {
|
|
if (sc->sc_type != SPECIAL)
|
|
continue;
|
|
if (sc->sc_args != NULL && !inlist(sc->sc_args, starget))
|
|
continue;
|
|
message(MT_CHANGE, "special \"%s\"", sc->sc_name);
|
|
if (IS_ON(opts, DO_VERIFY))
|
|
continue;
|
|
(void) sendcmd(C_SPECIAL,
|
|
"%s=%s;%s=%s;%s=%s;export %s %s %s;%s",
|
|
E_LOCFILE, starget,
|
|
E_REMFILE, rfile,
|
|
E_BASEFILE, basename(rfile),
|
|
E_LOCFILE, E_REMFILE, E_BASEFILE,
|
|
sc->sc_name);
|
|
while (response() > 0)
|
|
;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If we're doing a target with a "cmdspecial" in it, then
|
|
* save the name of the file being updated for use with "cmdspecial".
|
|
*/
|
|
static void addcmdspecialfile(starget, rname, destdir)
|
|
char *starget;
|
|
char *rname;
|
|
int destdir;
|
|
{
|
|
char *rfile;
|
|
struct namelist *new;
|
|
register struct subcmd *sc;
|
|
extern struct subcmd *subcmds;
|
|
int isokay = 0;
|
|
|
|
rfile = remfilename(source, Tdest, target, rname, destdir);
|
|
|
|
for (sc = subcmds; sc != NULL && !isokay; sc = sc->sc_next) {
|
|
if (sc->sc_type != CMDSPECIAL)
|
|
continue;
|
|
if (sc->sc_args != NULL && !inlist(sc->sc_args, starget))
|
|
continue;
|
|
isokay = TRUE;
|
|
}
|
|
|
|
if (isokay) {
|
|
new = (struct namelist *) xmalloc(sizeof(struct namelist));
|
|
new->n_name = strdup(rfile);
|
|
new->n_next = updfilelist;
|
|
updfilelist = new;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Free the file list
|
|
*/
|
|
static void freecmdspecialfiles()
|
|
{
|
|
register struct namelist *ptr, *save;
|
|
|
|
for (ptr = updfilelist; ptr; ) {
|
|
if (ptr->n_name) (void) free(ptr->n_name);
|
|
save = ptr->n_next;
|
|
(void) free(ptr);
|
|
if (save)
|
|
ptr = save->n_next;
|
|
else
|
|
ptr = NULL;
|
|
}
|
|
updfilelist = NULL;
|
|
}
|
|
|
|
/*
|
|
* Run commands for an entire cmd
|
|
*/
|
|
extern void runcmdspecial(cmd, filev, opts)
|
|
struct cmd *cmd;
|
|
char **filev;
|
|
opt_t opts;
|
|
{
|
|
register struct subcmd *sc;
|
|
register struct namelist *f;
|
|
register char **cpp;
|
|
char *file;
|
|
int first = TRUE;
|
|
|
|
for (sc = cmd->c_cmds; sc != NULL; sc = sc->sc_next) {
|
|
if (sc->sc_type != CMDSPECIAL)
|
|
continue;
|
|
message(MT_CHANGE, "cmdspecial \"%s\"", sc->sc_name);
|
|
if (IS_ON(opts, DO_VERIFY))
|
|
continue;
|
|
/* Send all the file names */
|
|
for (f = updfilelist; f != NULL; f = f->n_next) {
|
|
if (first) {
|
|
(void) sendcmd(C_CMDSPECIAL, NULL);
|
|
if (response() < 0)
|
|
return;
|
|
first = FALSE;
|
|
}
|
|
(void) sendcmd(RC_FILE, f->n_name);
|
|
if (response() < 0)
|
|
return;
|
|
}
|
|
if (first) {
|
|
(void) sendcmd(C_CMDSPECIAL, NULL);
|
|
if (response() < 0)
|
|
return;
|
|
first = FALSE;
|
|
}
|
|
/* Send command to run and wait for it to complete */
|
|
(void) sendcmd(RC_COMMAND, sc->sc_name);
|
|
while (response() > 0)
|
|
;
|
|
first = TRUE; /* Reset in case there are more CMDSPECIAL's */
|
|
}
|
|
freecmdspecialfiles();
|
|
}
|
|
|
|
/*
|
|
* For security, reject filenames that contains a newline
|
|
*/
|
|
int checkfilename(name)
|
|
char *name;
|
|
{
|
|
register char *cp;
|
|
|
|
if (strchr(name, '\n')) {
|
|
for (cp = name; *cp; cp++)
|
|
if (*cp == '\n')
|
|
*cp = '?';
|
|
message(MT_NERROR,
|
|
"Refuse to handle filename containing newline: %s",
|
|
name);
|
|
return(-1);
|
|
}
|
|
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
* Save and retrieve hard link info
|
|
*/
|
|
static struct linkbuf *linkinfo(statp)
|
|
struct stat *statp;
|
|
{
|
|
struct linkbuf *lp;
|
|
|
|
for (lp = ihead; lp != NULL; lp = lp->nextp)
|
|
if (lp->inum == statp->st_ino && lp->devnum == statp->st_dev) {
|
|
lp->count--;
|
|
return(lp);
|
|
}
|
|
|
|
lp = (struct linkbuf *) xmalloc(sizeof(*lp));
|
|
lp->nextp = ihead;
|
|
ihead = lp;
|
|
lp->inum = statp->st_ino;
|
|
lp->devnum = statp->st_dev;
|
|
lp->count = statp->st_nlink - 1;
|
|
(void) strcpy(lp->pathname, target);
|
|
(void) strcpy(lp->src, source);
|
|
if (Tdest)
|
|
(void) strcpy(lp->target, Tdest);
|
|
else
|
|
*lp->target = CNULL;
|
|
|
|
return((struct linkbuf *) NULL);
|
|
}
|
|
|
|
/*
|
|
* Send a hardlink
|
|
*/
|
|
static int sendhardlink(opts, lp, rname, destdir)
|
|
opt_t opts;
|
|
struct linkbuf *lp;
|
|
char *rname;
|
|
int destdir;
|
|
{
|
|
static char buff[MAXPATHLEN];
|
|
char *lname; /* name of file to link to */
|
|
|
|
debugmsg(DM_MISC,
|
|
"sendhardlink: rname='%s' pathname='%s' src='%s' target='%s'\n",
|
|
rname, lp->pathname, lp->src, lp->target);
|
|
|
|
if (*lp->target == CNULL)
|
|
(void) sendcmd(C_RECVHARDLINK, "%o %s %s",
|
|
opts, lp->pathname, rname);
|
|
else {
|
|
lname = buff;
|
|
strcpy(lname, remfilename(lp->src, lp->target,
|
|
lp->pathname, rname,
|
|
destdir));
|
|
debugmsg(DM_MISC, "sendhardlink: lname=%s\n", lname);
|
|
(void) sendcmd(C_RECVHARDLINK, "%o %s %s",
|
|
opts, lname, rname);
|
|
}
|
|
|
|
return(response());
|
|
}
|
|
|
|
/*
|
|
* Send a file
|
|
*/
|
|
static int sendfile(rname, opts, stb, user, group, destdir)
|
|
char *rname;
|
|
opt_t opts;
|
|
struct stat *stb;
|
|
char *user, *group;
|
|
int destdir;
|
|
{
|
|
int goterr, f;
|
|
off_t i;
|
|
|
|
if (stb->st_nlink > 1) {
|
|
struct linkbuf *lp;
|
|
|
|
if ((lp = linkinfo(stb)) != NULL)
|
|
return(sendhardlink(opts, lp, rname, destdir));
|
|
}
|
|
|
|
if ((f = open(target, O_RDONLY)) < 0) {
|
|
error("%s: open for read failed: %s", target, SYSERR);
|
|
return(-1);
|
|
}
|
|
|
|
#ifdef sgi
|
|
if (stb->st_size > INT_MAX) {
|
|
error("%s: file too big to transfer (> 2Gb-1)", target);
|
|
return(-1);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Send file info
|
|
*/
|
|
(void) sendcmd(C_RECVREG, "%o %04o %ld %ld %ld %s %s %s",
|
|
opts, stb->st_mode & 07777, stb->st_size,
|
|
stb->st_mtime, stb->st_atime,
|
|
user, group, rname);
|
|
if (response() < 0) {
|
|
(void) close(f);
|
|
return(-1);
|
|
}
|
|
|
|
debugmsg(DM_MISC, "Send file '%s' %d bytes\n", rname, stb->st_size);
|
|
|
|
/*
|
|
* Set remote time out alarm handler.
|
|
*/
|
|
(void) signal(SIGALRM, sighandler);
|
|
|
|
/*
|
|
* Actually transfer the file
|
|
*/
|
|
goterr = 0;
|
|
for (i = 0; i < stb->st_size; i += BUFSIZ) {
|
|
int amt = BUFSIZ;
|
|
|
|
(void) alarm(rtimeout);
|
|
if (i + amt > stb->st_size)
|
|
amt = stb->st_size - i;
|
|
if (read(f, buf, amt) != amt) {
|
|
error("%s: File changed size", target);
|
|
err();
|
|
++goterr;
|
|
/*
|
|
* XXX - We have to keep going because the
|
|
* server expects to receive a fixed number
|
|
* of bytes that we specified as the file size.
|
|
* We need Out Of Band communication to handle
|
|
* this situation gracefully.
|
|
*/
|
|
}
|
|
if (write(rem, buf, amt) == -1) {
|
|
error("%s: Error writing to client: %s",
|
|
target, SYSERR);
|
|
err();
|
|
++goterr;
|
|
break;
|
|
}
|
|
(void) alarm(0);
|
|
}
|
|
|
|
(void) alarm(0); /* Insure alarm is off */
|
|
(void) close(f);
|
|
|
|
debugmsg(DM_MISC, "Send file '%s' %s.\n",
|
|
(goterr) ? "failed" : "complete", rname);
|
|
|
|
/*
|
|
* Check for errors and end send
|
|
*/
|
|
if (goterr)
|
|
return(-1);
|
|
else {
|
|
ack();
|
|
f = response();
|
|
if (f < 0)
|
|
return(-1);
|
|
else if (f == 0 && IS_ON(opts, DO_COMPARE))
|
|
return(0);
|
|
|
|
runspecial(target, opts, rname, destdir);
|
|
addcmdspecialfile(target, rname, destdir);
|
|
|
|
return(0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check for files on the machine being updated that are not on the master
|
|
* machine and remove them.
|
|
*
|
|
* Return < 0 on error.
|
|
* Return 0 if nothing happened.
|
|
* Return > 0 if anything is updated.
|
|
*/
|
|
static int rmchk(opts)
|
|
opt_t opts;
|
|
{
|
|
register u_char *s;
|
|
struct stat stb;
|
|
int didupdate = 0;
|
|
int n;
|
|
|
|
debugmsg(DM_CALL, "rmchk()\n");
|
|
|
|
/*
|
|
* Tell the remote to clean the files from the last directory sent.
|
|
*/
|
|
(void) sendcmd(C_CLEAN, "%o", IS_ON(opts, DO_VERIFY));
|
|
if (response() < 0)
|
|
return(-1);
|
|
|
|
for ( ; ; ) {
|
|
n = remline(s = respbuff, sizeof(respbuff), TRUE);
|
|
if (n <= 0) {
|
|
error("rmchk: unexpected control record");
|
|
return(didupdate);
|
|
}
|
|
|
|
switch (*s++) {
|
|
case CC_QUERY: /* Query if file should be removed */
|
|
/*
|
|
* Return the following codes to remove query.
|
|
* CC_NO -- file exists - DON'T remove.
|
|
* CC_YES -- file doesn't exist - REMOVE.
|
|
*/
|
|
(void) sprintf(ptarget, "%s%s",
|
|
(ptarget[-1] == '/' ? "" : "/"), s);
|
|
debugmsg(DM_MISC, "check %s\n", target);
|
|
if (except(target))
|
|
(void) sendcmd(CC_NO, NULL);
|
|
else if (lstat(target, &stb) < 0) {
|
|
if (sendcmd(CC_YES, NULL) == 0)
|
|
didupdate = 1;
|
|
} else
|
|
(void) sendcmd(CC_NO, NULL);
|
|
break;
|
|
|
|
case CC_END:
|
|
*ptarget = CNULL;
|
|
ack();
|
|
return(didupdate);
|
|
|
|
case C_LOGMSG:
|
|
if (n > 0)
|
|
message(MT_INFO, "%s", s);
|
|
break;
|
|
|
|
case C_ERRMSG:
|
|
message(MT_NERROR, "%s", s);
|
|
return(didupdate);
|
|
|
|
case C_FERRMSG:
|
|
message(MT_FERROR, "%s", s);
|
|
finish();
|
|
|
|
default:
|
|
error("rmchk: unexpected response '%s'", respbuff);
|
|
err();
|
|
}
|
|
}
|
|
/*NOTREACHED*/
|
|
}
|
|
|
|
/*
|
|
* Send a directory
|
|
*
|
|
* Return < 0 on error.
|
|
* Return 0 if nothing happened.
|
|
* Return > 0 if anything is updated.
|
|
*/
|
|
static int senddir(rname, opts, stb, user, group, destdir)
|
|
char *rname;
|
|
opt_t opts;
|
|
struct stat *stb;
|
|
char *user, *group;
|
|
int destdir;
|
|
{
|
|
DIRENTRY *dp;
|
|
DIR *d;
|
|
char *optarget, *cp;
|
|
int len;
|
|
int didupdate = 0;
|
|
|
|
/*
|
|
* Send recvdir command in recvit() format.
|
|
*/
|
|
(void) sendcmd(C_RECVDIR, "%o %04o 0 0 0 %s %s %s",
|
|
opts, stb->st_mode & 07777, user, group, rname);
|
|
if (response() < 0)
|
|
return(-1);
|
|
|
|
/*
|
|
* Don't descend into directory
|
|
*/
|
|
if (IS_ON(opts, DO_NODESCEND))
|
|
return(0);
|
|
|
|
if (IS_ON(opts, DO_REMOVE))
|
|
if (rmchk(opts) > 0)
|
|
++didupdate;
|
|
|
|
if ((d = opendir(target)) == NULL) {
|
|
error("%s: opendir failed: %s", target, SYSERR);
|
|
return(-1);
|
|
}
|
|
|
|
optarget = ptarget;
|
|
len = ptarget - target;
|
|
while (dp = readdir(d)) {
|
|
if (!strcmp(dp->d_name, ".") ||
|
|
!strcmp(dp->d_name, ".."))
|
|
continue;
|
|
if (len + 1 + (int) strlen(dp->d_name) >= MAXPATHLEN - 1) {
|
|
error("%s/%s: Name too long", target,
|
|
dp->d_name);
|
|
continue;
|
|
}
|
|
ptarget = optarget;
|
|
if (ptarget[-1] != '/')
|
|
*ptarget++ = '/';
|
|
cp = dp->d_name;
|
|
while (*ptarget++ = *cp++)
|
|
;
|
|
ptarget--;
|
|
if (sendit(dp->d_name, opts, destdir) > 0)
|
|
didupdate = 1;
|
|
}
|
|
(void) closedir(d);
|
|
|
|
(void) sendcmd(C_END, NULL);
|
|
(void) response();
|
|
|
|
ptarget = optarget;
|
|
*ptarget = CNULL;
|
|
|
|
return(didupdate);
|
|
}
|
|
|
|
/*
|
|
* Send a link
|
|
*/
|
|
static int sendlink(rname, opts, stb, user, group, destdir)
|
|
char *rname;
|
|
opt_t opts;
|
|
struct stat *stb;
|
|
char *user;
|
|
char *group;
|
|
int destdir;
|
|
{
|
|
int sizerr, f, n;
|
|
static char tbuf[BUFSIZ];
|
|
char lbuf[MAXPATHLEN];
|
|
u_char *s;
|
|
|
|
debugmsg(DM_CALL, "sendlink(%s, %x, stb, %d)\n", rname, opts, destdir);
|
|
|
|
if (stb->st_nlink > 1) {
|
|
struct linkbuf *lp;
|
|
|
|
if ((lp = linkinfo(stb)) != NULL)
|
|
return(sendhardlink(opts, lp, rname, destdir));
|
|
}
|
|
|
|
/*
|
|
* Gather and send basic link info
|
|
*/
|
|
(void) sendcmd(C_RECVSYMLINK, "%o %04o %ld %ld %ld %s %s %s",
|
|
opts, stb->st_mode & 07777, stb->st_size,
|
|
stb->st_mtime, stb->st_atime,
|
|
user, group, rname);
|
|
if (response() < 0)
|
|
return(-1);
|
|
|
|
/*
|
|
* Gather and send additional link info
|
|
*/
|
|
sizerr = (readlink(target, lbuf, sizeof(lbuf)) != stb->st_size);
|
|
(void) sprintf(tbuf, "%.*s", stb->st_size, lbuf);
|
|
(void) sendcmd(C_NONE, "%s\n", tbuf);
|
|
|
|
if (sizerr) {
|
|
error("%s: file changed size", target);
|
|
err();
|
|
} else
|
|
ack();
|
|
|
|
/*
|
|
* Check response
|
|
*/
|
|
f = response();
|
|
if (f < 0)
|
|
return(-1);
|
|
else if (f == 0 && IS_ON(opts, DO_COMPARE))
|
|
return(0);
|
|
|
|
/*
|
|
* Read and process responses from server.
|
|
* The server may send multiple messages regarding
|
|
* file deletes if the remote target is a directory.
|
|
*/
|
|
for (;;) {
|
|
n = remline(s = respbuff, sizeof(respbuff), TRUE);
|
|
if (n == -1) /* normal EOF */
|
|
return(0);
|
|
if (n == 0) {
|
|
error("expected control record");
|
|
continue;
|
|
}
|
|
|
|
switch (*s++) {
|
|
case C_END: /* End of send operation */
|
|
*ptarget = CNULL;
|
|
ack();
|
|
runspecial(target, opts, rname, destdir);
|
|
addcmdspecialfile(target, rname, destdir);
|
|
return(0);
|
|
|
|
case C_LOGMSG:
|
|
if (n > 0)
|
|
message(MT_INFO, "%s", s);
|
|
break;
|
|
|
|
case C_ERRMSG:
|
|
message(MT_NERROR, "%s", s);
|
|
return(-1);
|
|
|
|
case C_FERRMSG:
|
|
message(MT_FERROR, "%s", s);
|
|
finish();
|
|
|
|
default:
|
|
error("install link: unexpected response '%s'",
|
|
respbuff);
|
|
err();
|
|
}
|
|
}
|
|
/*NOTREACHED*/
|
|
}
|
|
|
|
/*
|
|
* Check to see if file needs to be updated on the remote machine.
|
|
* Returns:
|
|
* US_NOTHING - no update
|
|
* US_NOENT - remote doesn't exist
|
|
* US_OUTDATE - out of date
|
|
* US_DOCOMP - comparing binaries to determine if out of date
|
|
* US_MODE - File modes do not match
|
|
*/
|
|
static int update(rname, opts, statp)
|
|
char *rname;
|
|
opt_t opts;
|
|
struct stat *statp;
|
|
{
|
|
register off_t size;
|
|
register time_t mtime;
|
|
unsigned short lmode;
|
|
unsigned short rmode;
|
|
char *owner = NULL, *group = NULL;
|
|
int done, n;
|
|
u_char *cp;
|
|
|
|
debugmsg(DM_CALL, "update(%s, 0x%x, 0x%x)\n", rname, opts, statp);
|
|
|
|
if (IS_ON(opts, DO_NOEXEC))
|
|
if (isexec(target, statp)) {
|
|
debugmsg(DM_MISC, "%s is an executable\n", target);
|
|
return(US_NOTHING);
|
|
}
|
|
|
|
/*
|
|
* Check to see if the file exists on the remote machine.
|
|
*/
|
|
(void) sendcmd(C_QUERY, "%s", rname);
|
|
|
|
for (done = 0; !done;) {
|
|
n = remline(cp = respbuff, sizeof(respbuff), TRUE);
|
|
if (n <= 0) {
|
|
error("update: unexpected control record in response to query");
|
|
return(US_NOTHING);
|
|
}
|
|
|
|
switch (*cp++) {
|
|
case QC_ONNFS: /* Resides on a NFS */
|
|
debugmsg(DM_MISC,
|
|
"update: %s is on a NFS. Skipping...\n",
|
|
rname);
|
|
return(US_NOTHING);
|
|
|
|
case QC_SYM: /* Is a symbolic link */
|
|
debugmsg(DM_MISC,
|
|
"update: %s is a symlink. Skipping...\n",
|
|
rname);
|
|
return(US_NOTHING);
|
|
|
|
case QC_ONRO: /* Resides on a Read-Only fs */
|
|
debugmsg(DM_MISC,
|
|
"update: %s is on a RO fs. Skipping...\n",
|
|
rname);
|
|
return(US_NOTHING);
|
|
|
|
case QC_YES:
|
|
done = 1;
|
|
break;
|
|
|
|
case QC_NO: /* file doesn't exist so install it */
|
|
return(US_NOENT);
|
|
|
|
case C_ERRMSG:
|
|
if (cp)
|
|
message(MT_NERROR, "%s", cp);
|
|
return(US_NOTHING);
|
|
|
|
case C_FERRMSG:
|
|
if (cp)
|
|
message(MT_FERROR, "%s", cp);
|
|
finish();
|
|
|
|
case C_NOTEMSG:
|
|
if (cp)
|
|
message(MT_NOTICE, "%s", cp);
|
|
break;
|
|
/* Goto top of loop */
|
|
|
|
default:
|
|
error("update: unexpected response to query '%s'", cp);
|
|
return(US_NOTHING);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Target exists, but no other info passed
|
|
*/
|
|
if (n <= 1 || !S_ISREG(statp->st_mode))
|
|
return(US_OUTDATE);
|
|
|
|
if (IS_ON(opts, DO_COMPARE))
|
|
return(US_DOCOMP);
|
|
|
|
/*
|
|
* Parse size
|
|
*/
|
|
size = strtol(cp, &cp, 10);
|
|
if (*cp++ != ' ') {
|
|
error("update: size not delimited");
|
|
return(US_NOTHING);
|
|
}
|
|
|
|
/*
|
|
* Parse mtime
|
|
*/
|
|
mtime = strtol(cp, &cp, 10);
|
|
if (*cp++ != ' ') {
|
|
error("update: mtime not delimited");
|
|
return(US_NOTHING);
|
|
}
|
|
|
|
/*
|
|
* Parse remote file mode
|
|
*/
|
|
rmode = strtol(cp, &cp, 8);
|
|
if (cp && *cp)
|
|
++cp;
|
|
|
|
/*
|
|
* Be backwards compatible
|
|
*/
|
|
if (cp && *cp != CNULL) {
|
|
/*
|
|
* Parse remote file owner
|
|
*/
|
|
owner = strtok((char *)cp, " ");
|
|
if (owner == NULL) {
|
|
error("update: owner not delimited");
|
|
return(US_NOTHING);
|
|
}
|
|
|
|
/*
|
|
* Parse remote file group
|
|
*/
|
|
group = strtok((char *) NULL, " ");
|
|
if (group == NULL) {
|
|
error("update: group not delimited");
|
|
return(US_NOTHING);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* File needs to be updated?
|
|
*/
|
|
lmode = statp->st_mode & 07777;
|
|
|
|
debugmsg(DM_MISC, "update(%s,) local mode %04o remote mode %04o\n",
|
|
rname, lmode, rmode);
|
|
debugmsg(DM_MISC, "update(%s,) size %d mtime %d owner '%s' grp '%s'\n",
|
|
rname, size, mtime, owner, group);
|
|
|
|
if (statp->st_mtime != mtime) {
|
|
if (statp->st_mtime < mtime && IS_ON(opts, DO_YOUNGER)) {
|
|
message(MT_WARNING,
|
|
"%s: Warning: remote copy is newer",
|
|
target);
|
|
return(US_NOTHING);
|
|
}
|
|
return(US_OUTDATE);
|
|
}
|
|
|
|
/*
|
|
* If the mode of a file does not match the local mode, the
|
|
* whole file is updated. This is done both to insure that
|
|
* a bogus version of the file has not been installed and to
|
|
* avoid having to handle weird cases of chmod'ing symlinks
|
|
* and such.
|
|
*/
|
|
if (!IS_ON(opts, DO_NOCHKMODE) && lmode != rmode) {
|
|
debugmsg(DM_MISC, "modes do not match (%04o != %04o).\n",
|
|
lmode, rmode);
|
|
return(US_OUTDATE);
|
|
}
|
|
|
|
if (statp->st_size != size) {
|
|
debugmsg(DM_MISC, "size does not match (%d != %d).\n",
|
|
statp->st_size, size);
|
|
return(US_OUTDATE);
|
|
}
|
|
|
|
/*
|
|
* Check ownership
|
|
*/
|
|
if (!IS_ON(opts, DO_NOCHKOWNER) && owner) {
|
|
if (!IS_ON(opts, DO_NUMCHKOWNER)) {
|
|
/* Check by string compare */
|
|
if (strcmp(owner, getusername(statp->st_uid,
|
|
target, opts)) != 0) {
|
|
debugmsg(DM_MISC,
|
|
"owner does not match (%s != %s).\n",
|
|
getusername(statp->st_uid,
|
|
target, opts), owner);
|
|
return(US_OUTDATE);
|
|
}
|
|
} else {
|
|
/*
|
|
* Check numerically.
|
|
* Allow negative numbers.
|
|
*/
|
|
while (*owner && !isdigit(*owner) && (*owner != '-'))
|
|
++owner;
|
|
if (owner && atoi(owner) != statp->st_uid) {
|
|
debugmsg(DM_MISC,
|
|
"owner does not match (%d != %s).\n",
|
|
statp->st_uid, owner);
|
|
return(US_OUTDATE);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!IS_ON(opts, DO_NOCHKGROUP) && group) {
|
|
if (!IS_ON(opts, DO_NUMCHKGROUP)) {
|
|
/* Check by string compare */
|
|
if (strcmp(group, getgroupname(statp->st_gid,
|
|
target, opts)) != 0) {
|
|
debugmsg(DM_MISC,
|
|
"group does not match (%s != %s).\n",
|
|
getgroupname(statp->st_gid,
|
|
target, opts), group);
|
|
return(US_OUTDATE);
|
|
}
|
|
} else {
|
|
/* Check numerically */
|
|
/* Allow negative gid */
|
|
while (*group && !isdigit(*group) && (*group != '-'))
|
|
++group;
|
|
if (group && atoi(group) != statp->st_gid) {
|
|
debugmsg(DM_MISC,
|
|
"group does not match (%d != %s).\n",
|
|
statp->st_gid, group);
|
|
return(US_OUTDATE);
|
|
}
|
|
}
|
|
}
|
|
|
|
return(US_NOTHING);
|
|
}
|
|
|
|
/*
|
|
* Stat a file
|
|
*/
|
|
static int dostat(file, statbuf, opts)
|
|
char *file;
|
|
struct stat *statbuf;
|
|
opt_t opts;
|
|
{
|
|
int s;
|
|
|
|
if (IS_ON(opts, DO_FOLLOW))
|
|
s = stat(file, statbuf);
|
|
else
|
|
s = lstat(file, statbuf);
|
|
|
|
if (s < 0)
|
|
error("%s: %s failed: %s", file,
|
|
IS_ON(opts, DO_FOLLOW) ? "stat" : "lstat", SYSERR);
|
|
return(s);
|
|
}
|
|
|
|
/*
|
|
* Transfer the file or directory in target[].
|
|
* rname is the name of the file on the remote host.
|
|
*
|
|
* Return < 0 on error.
|
|
* Return 0 if nothing happened.
|
|
* Return > 0 if anything is updated.
|
|
*/
|
|
static int sendit(rname, opts, destdir)
|
|
char *rname;
|
|
opt_t opts;
|
|
int destdir;
|
|
{
|
|
static struct stat stb;
|
|
extern struct subcmd *subcmds;
|
|
char *user, *group;
|
|
int u, len;
|
|
int didupdate = 0;
|
|
|
|
/*
|
|
* Remove possible accidental newline
|
|
*/
|
|
len = strlen(rname);
|
|
if (len > 0 && rname[len-1] == '\n')
|
|
rname[len-1] = CNULL;
|
|
|
|
if (checkfilename(rname) != 0)
|
|
return(-1);
|
|
|
|
debugmsg(DM_CALL, "sendit(%s, 0x%x) called\n", rname, opts);
|
|
|
|
if (except(target))
|
|
return(0);
|
|
|
|
if (dostat(target, &stb, opts) < 0)
|
|
return(-1);
|
|
|
|
/*
|
|
* Does rname need updating?
|
|
*/
|
|
u = update(rname, opts, &stb);
|
|
debugmsg(DM_MISC, "sendit(%s, 0x%x): update status of %s is %d\n",
|
|
rname, opts, target, u);
|
|
|
|
/*
|
|
* Don't need to update the file, but we may need to save hardlink
|
|
* info.
|
|
*/
|
|
if (u == US_NOTHING) {
|
|
if (S_ISREG(stb.st_mode) && stb.st_nlink > 1)
|
|
(void) linkinfo(&stb);
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
* File mode needs changing
|
|
*/
|
|
if (u == US_MODE) {
|
|
if (IS_ON(opts, DO_VERIFY)) {
|
|
message(MT_INFO, "%s: need to chmod to %04o",
|
|
target, stb.st_mode & 07777);
|
|
runspecial(target, opts, rname, destdir);
|
|
return(1);
|
|
}
|
|
message(MT_CHANGE, "%s: chmod to %04o",
|
|
target, stb.st_mode & 07777);
|
|
(void) sendcmd(C_CHMOD, "%o %04o %s",
|
|
opts, stb.st_mode & 07777, rname);
|
|
(void) response();
|
|
return(1);
|
|
}
|
|
|
|
user = getusername(stb.st_uid, target, opts);
|
|
group = getgroupname(stb.st_gid, target, opts);
|
|
|
|
/*
|
|
* No entry - need to install
|
|
*/
|
|
if (u == US_NOENT) {
|
|
if (IS_ON(opts, DO_VERIFY)) {
|
|
message(MT_INFO, "%s: need to install", target);
|
|
runspecial(target, opts, rname, destdir);
|
|
return(1);
|
|
}
|
|
if (!IS_ON(opts, DO_QUIET))
|
|
message(MT_CHANGE, "%s: installing", target);
|
|
FLAG_OFF(opts, (DO_COMPARE|DO_REMOVE));
|
|
}
|
|
|
|
/*
|
|
* Handle special file types, including directories and symlinks
|
|
*/
|
|
if (S_ISDIR(stb.st_mode)) {
|
|
if (senddir(rname, opts, &stb, user, group, destdir) > 0)
|
|
didupdate = 1;
|
|
} else if (S_ISLNK(stb.st_mode)) {
|
|
if (u != US_NOENT)
|
|
FLAG_ON(opts, DO_COMPARE);
|
|
/*
|
|
* Since we always send link info to the server
|
|
* so the server can determine if the remote link
|
|
* is correct, we never get any acknowledge meant
|
|
* from the server whether the link was really
|
|
* updated or not.
|
|
*/
|
|
(void) sendlink(rname, opts, &stb, user, group, destdir);
|
|
} else if (S_ISREG(stb.st_mode)) {
|
|
if (u == US_OUTDATE) {
|
|
if (IS_ON(opts, DO_VERIFY)) {
|
|
message(MT_INFO, "%s: need to update", target);
|
|
runspecial(target, opts, rname, destdir);
|
|
return(1);
|
|
}
|
|
if (!IS_ON(opts, DO_QUIET))
|
|
message(MT_CHANGE, "%s: updating", target);
|
|
}
|
|
if (sendfile(rname, opts, &stb, user, group, destdir) == 0)
|
|
didupdate = 1;
|
|
#ifdef sgi
|
|
else
|
|
didupdate = -1;
|
|
#endif
|
|
} else
|
|
error("%s: unknown file type", target);
|
|
|
|
return(didupdate);
|
|
}
|
|
|
|
/*
|
|
* Remove temporary files and do any cleanup operations before exiting.
|
|
*/
|
|
extern void cleanup()
|
|
{
|
|
char *file;
|
|
#ifdef USE_STATDB
|
|
extern char statfile[];
|
|
|
|
(void) unlink(statfile);
|
|
#endif
|
|
|
|
if (file = getnotifyfile())
|
|
(void) unlink(file);
|
|
}
|
|
|
|
/*
|
|
* Update the file(s) if they are different.
|
|
* destdir = 1 if destination should be a directory
|
|
* (i.e., more than one source is being copied to the same destination).
|
|
*
|
|
* Return < 0 on error.
|
|
* Return 0 if nothing updated.
|
|
* Return > 0 if something was updated.
|
|
*/
|
|
extern int install(src, dest, ddir, destdir, opts)
|
|
char *src, *dest;
|
|
int ddir, destdir;
|
|
opt_t opts;
|
|
{
|
|
static char destcopy[MAXPATHLEN];
|
|
char *rname;
|
|
int didupdate = 0;
|
|
|
|
debugmsg(DM_CALL,
|
|
"install(src=%s,dest=%s,ddir=%d,destdir=%d,opts=%d) start\n",
|
|
(src?src:"NULL"), (dest?dest:"NULL"), ddir, destdir, opts);
|
|
/*
|
|
* Save source name
|
|
*/
|
|
if (IS_ON(opts, DO_WHOLE))
|
|
source[0] = CNULL;
|
|
else
|
|
(void) strcpy(source, src);
|
|
|
|
if (dest == NULL) {
|
|
FLAG_OFF(opts, DO_WHOLE); /* WHOLE only useful if renaming */
|
|
dest = src;
|
|
}
|
|
|
|
if (checkfilename(dest) != 0)
|
|
return(-1);
|
|
|
|
if (nflag || debug) {
|
|
static char buff[BUFSIZ];
|
|
char *cp;
|
|
|
|
cp = getondistoptlist(opts);
|
|
(void) sprintf(buff, "%s%s%s %s %s",
|
|
IS_ON(opts, DO_VERIFY) ? "verify" : "install",
|
|
(cp) ? " -o" : "", (cp) ? cp : "",
|
|
src, dest);
|
|
if (nflag) {
|
|
printf("%s\n", buff);
|
|
return(0);
|
|
} else
|
|
debugmsg(DM_MISC, "%s\n", buff);
|
|
}
|
|
|
|
rname = exptilde(target, src);
|
|
if (rname == NULL)
|
|
return(-1);
|
|
ptarget = target;
|
|
while (*ptarget)
|
|
ptarget++;
|
|
/*
|
|
* If we are renaming a directory and we want to preserve
|
|
* the directory heirarchy (-w), we must strip off the leading
|
|
* directory name and preserve the rest.
|
|
*/
|
|
if (IS_ON(opts, DO_WHOLE)) {
|
|
while (*rname == '/')
|
|
rname++;
|
|
ddir = 1;
|
|
destdir = 1;
|
|
} else {
|
|
rname = strrchr(target, '/');
|
|
/* Check if no '/' or target ends in '/' */
|
|
if (rname == NULL ||
|
|
rname+1 == NULL ||
|
|
*(rname+1) == CNULL)
|
|
rname = target;
|
|
else
|
|
rname++;
|
|
}
|
|
|
|
debugmsg(DM_MISC,
|
|
"install: target=%s src=%s rname=%s dest='%s' destdir=%d, ddir=%d\n",
|
|
target, source, rname, dest, destdir, ddir);
|
|
|
|
/*
|
|
* Pass the destination file/directory name to remote.
|
|
*/
|
|
if (ddir)
|
|
(void) sendcmd(C_DIRTARGET, "%o %s", opts, dest);
|
|
else
|
|
(void) sendcmd(C_TARGET, "%o %s", opts, dest);
|
|
if (response() < 0)
|
|
return(-1);
|
|
|
|
/*
|
|
* Save the name of the remote target destination if we are
|
|
* in WHOLE mode (destdir > 0) or if the source and destination
|
|
* are not the same. This info will be used later for maintaining
|
|
* hardlink info.
|
|
*/
|
|
if (destdir || (src && dest && strcmp(src, dest))) {
|
|
(void) strcpy(destcopy, dest);
|
|
Tdest = destcopy;
|
|
}
|
|
|
|
didupdate = sendit(rname, opts, destdir);
|
|
Tdest = 0;
|
|
|
|
return(didupdate);
|
|
}
|