* Copyright (c) 1983 Regents of the University of California.
* Redistribution and use in source and binary forms are permitted
* provided that the above copyright notice and this paragraph are
* duplicated in all such forms and that any documentation,
* advertising materials, and other materials related to such
* distribution and use acknowledge that the software was developed
* by the University of California, Berkeley. The name of the
* University may not be used to endorse or promote products derived
* from this software without specific prior written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
static char sccsid
[] = "@(#)server.c 5.8 (Berkeley) 6/29/88";
#define ack() (void) write(rem, "\0\n", 2)
#define err() (void) write(rem, "\1\n", 2)
struct linkbuf
*ihead
; /* list of files with more than one link */
char buf
[BUFSIZ
]; /* general purpose buffer */
char target
[BUFSIZ
]; /* target/source directory name */
char *tp
; /* pointer to end of target name */
char *Tdest
; /* pointer to last T dest*/
int catname
; /* cat name to target name */
char *stp
[32]; /* stack of saved tp's for directories */
int oumask
; /* old umask for creating files */
extern FILE *lfp
; /* log file for mailing changes */
struct linkbuf
*savelink();
* Server routine to read requests and process them.
* Tname - Transmit file if out of date
* Vname - Verify if file out of date or not
* Qname - Query if file exists. Return mtime & size if it does.
signal(SIGQUIT
, cleanup
);
signal(SIGTERM
, cleanup
);
signal(SIGPIPE
, cleanup
);
(void) sprintf(buf
, "V%d\n", VERSION
);
(void) write(rem
, buf
, strlen(buf
));
if (read(rem
, cp
, 1) <= 0)
error("server: expected control record\n");
if (read(rem
, cp
, 1) != 1)
} while (*cp
++ != '\n' && cp
< &cmdbuf
[BUFSIZ
]);
case 'T': /* init target file/directory name */
catname
= 1; /* target should be directory */
case 't': /* init target file/directory name */
if (exptilde(target
, cp
) == NULL
)
case 'R': /* Transfer a regular file. */
case 'D': /* Transfer a directory. */
case 'K': /* Transfer symbolic link. */
case 'k': /* Transfer hard link. */
case 'E': /* End. (of directory) */
error("server: too many 'E's\n");
case 'C': /* Clean. Cleanup a directory */
case 'Q': /* Query. Does the file/directory exist? */
case 'S': /* Special. Execute commands */
* These entries are reserved but not currently used.
* The intent is to allow remote hosts to have master copies.
* Currently, only the host rdist runs on can have masters.
case 'X': /* start a new list of files to exclude */
case 'x': /* add name to list of files to exclude */
if (exptilde(buf
, cp
) == NULL
)
except
= bp
= expand(makeblock(NAME
, cp
), E_VARS
);
bp
->b_next
= expand(makeblock(NAME
, cp
), E_VARS
);
while (bp
->b_next
!= NULL
)
case 'I': /* Install. Transfer file if out of date. */
while (*cp
>= '0' && *cp
<= '7')
opts
= (opts
<< 3) | (*cp
++ - '0');
error("server: options not delimited\n");
case 'L': /* Log. save message in log file */
error("server: unknown command '%s'\n", cp
);
* 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).
install(src
, dest
, destdir
, opts
)
opts
&= ~WHOLE
; /* WHOLE mode only useful if renaming */
printf("%s%s%s%s%s %s %s\n", opts
& VERIFY
? "verify":"install",
opts
& WHOLE
? " -w" : "",
opts
& YOUNGER
? " -y" : "",
opts
& COMPARE
? " -b" : "",
opts
& REMOVE
? " -R" : "", src
, dest
);
rname
= exptilde(target
, src
);
* 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.
rname
= rindex(target
, '/');
printf("target = %s, rname = %s\n", target
, rname
);
* Pass the destination file/directory name to remote.
(void) sprintf(buf
, "%c%s\n", destdir
? 'T' : 't', dest
);
(void) write(rem
, buf
, strlen(buf
));
#define protoname() (pw ? pw->pw_name : user)
#define protogroup() (gr ? gr->gr_name : group)
* Transfer the file or directory in target[].
* rname is the name of the file on the remote host.
register struct subcmd
*sc
;
extern struct subcmd
*subcmds
;
static char user
[15], group
[15];
printf("sendf(%s, %x)\n", rname
, opts
);
if ((opts
& FOLLOW
? stat(target
, &stb
) : lstat(target
, &stb
)) < 0) {
error("%s: %s\n", target
, sys_errlist
[errno
]);
if ((u
= update(rname
, opts
, &stb
)) == 0) {
if ((stb
.st_mode
& S_IFMT
) == S_IFREG
&& stb
.st_nlink
> 1)
if (pw
== NULL
|| pw
->pw_uid
!= stb
.st_uid
)
if ((pw
= getpwuid(stb
.st_uid
)) == NULL
) {
log(lfp
, "%s: no password entry for uid %d \n",
sprintf(user
, ":%d", stb
.st_uid
);
if (gr
== NULL
|| gr
->gr_gid
!= stb
.st_gid
)
if ((gr
= getgrgid(stb
.st_gid
)) == NULL
) {
log(lfp
, "%s: no name for group %d\n",
sprintf(group
, ":%d", stb
.st_gid
);
log(lfp
, "need to install: %s\n", target
);
log(lfp
, "installing: %s\n", target
);
opts
&= ~(COMPARE
|REMOVE
);
switch (stb
.st_mode
& S_IFMT
) {
if ((d
= opendir(target
)) == NULL
) {
error("%s: %s\n", target
, sys_errlist
[errno
]);
(void) sprintf(buf
, "D%o %04o 0 0 %s %s %s\n", opts
,
stb
.st_mode
& 07777, protoname(), protogroup(), rname
);
(void) write(rem
, buf
, strlen(buf
));
while (dp
= readdir(d
)) {
if (!strcmp(dp
->d_name
, ".") ||
!strcmp(dp
->d_name
, ".."))
if (len
+ 1 + strlen(dp
->d_name
) >= BUFSIZ
- 1) {
error("%s/%s: Name too long\n", target
,
(void) write(rem
, "E\n", 2);
if ((lp
= savelink(&stb
)) != NULL
) {
(void) sprintf(buf
, "k%o %s %s\n", opts
,
(void) sprintf(buf
, "k%o %s/%s %s\n", opts
,
lp
->target
, lp
->pathname
, rname
);
(void) write(rem
, buf
, strlen(buf
));
(void) sprintf(buf
, "K%o %o %ld %ld %s %s %s\n", opts
,
stb
.st_mode
& 07777, stb
.st_size
, stb
.st_mtime
,
protoname(), protogroup(), rname
);
(void) write(rem
, buf
, strlen(buf
));
sizerr
= (readlink(target
, buf
, BUFSIZ
) != stb
.st_size
);
(void) write(rem
, buf
, stb
.st_size
);
printf("readlink = %.*s\n", (int)stb
.st_size
, buf
);
error("%s: not a file or directory\n", target
);
log(lfp
, "need to update: %s\n", target
);
log(lfp
, "updating: %s\n", target
);
if ((lp
= savelink(&stb
)) != NULL
) {
(void) sprintf(buf
, "k%o %s %s\n", opts
,
(void) sprintf(buf
, "k%o %s/%s %s\n", opts
,
lp
->target
, lp
->pathname
, rname
);
(void) write(rem
, buf
, strlen(buf
));
if ((f
= open(target
, 0)) < 0) {
error("%s: %s\n", target
, sys_errlist
[errno
]);
(void) sprintf(buf
, "R%o %o %ld %ld %s %s %s\n", opts
,
stb
.st_mode
& 07777, stb
.st_size
, stb
.st_mtime
,
protoname(), protogroup(), rname
);
(void) write(rem
, buf
, strlen(buf
));
for (i
= 0; i
< stb
.st_size
; i
+= BUFSIZ
) {
if (i
+ amt
> stb
.st_size
)
if (sizerr
== 0 && read(f
, buf
, amt
) != amt
)
(void) write(rem
, buf
, amt
);
error("%s: file changed size\n", target
);
if (f
< 0 || f
== 0 && (opts
& COMPARE
))
for (sc
= subcmds
; sc
!= NULL
; sc
= sc
->sc_next
) {
if (sc
->sc_type
!= SPECIAL
)
if (sc
->sc_args
!= NULL
&& !inlist(sc
->sc_args
, target
))
log(lfp
, "special \"%s\"\n", sc
->sc_name
);
(void) sprintf(buf
, "SFILE=%s;%s\n", target
, sc
->sc_name
);
(void) write(rem
, buf
, strlen(buf
));
for (lp
= ihead
; lp
!= NULL
; lp
= lp
->nextp
)
if (lp
->inum
== stp
->st_ino
&& lp
->devnum
== stp
->st_dev
) {
lp
= (struct linkbuf
*) malloc(sizeof(*lp
));
log(lfp
, "out of memory, link information lost\n");
lp
->devnum
= stp
->st_dev
;
lp
->count
= stp
->st_nlink
- 1;
strcpy(lp
->pathname
, target
);
strcpy(lp
->target
, Tdest
);
* Check to see if file needs to be updated on the remote machine.
* Returns 0 if no update, 1 if remote doesn't exist, 2 if out of date
* and 3 if comparing binaries to determine if out of date.
printf("update(%s, %x, %x)\n", rname
, opts
, stp
);
* Check to see if the file exists on the remote machine.
(void) sprintf(buf
, "Q%s\n", rname
);
(void) write(rem
, buf
, strlen(buf
));
if (read(rem
, cp
, 1) != 1)
} while (*cp
++ != '\n' && cp
< &buf
[BUFSIZ
]);
case 'N': /* file doesn't exist so install it */
(void) write(2, s
, cp
- s
);
(void) fwrite(s
, 1, cp
- s
, lfp
);
log(lfp
, "update: note: %s\n", s
);
error("update: unexpected response '%s'\n", s
);
size
= size
* 10 + (*s
++ - '0');
error("update: size not delimited\n");
mtime
= mtime
* 10 + (*s
++ - '0');
error("update: mtime not delimited\n");
* File needs to be updated?
if (stp
->st_mtime
== mtime
)
if (stp
->st_mtime
< mtime
) {
log(lfp
, "Warning: %s: remote copy is newer\n", target
);
} else if (stp
->st_mtime
== mtime
&& stp
->st_size
== size
)
* Query. Check to see if file exists. Return one of the following:
* Ysize mtime\n - exists and its a regular file (size & mtime of file)
* Y\n - exists and its a directory or symbolic link
(void) sprintf(tp
, "/%s", name
);
if (lstat(target
, &stb
) < 0) {
(void) write(rem
, "N\n", 2);
error("%s:%s: %s\n", host
, target
, sys_errlist
[errno
]);
switch (stb
.st_mode
& S_IFMT
) {
(void) sprintf(buf
, "Y%ld %ld\n", stb
.st_size
, stb
.st_mtime
);
(void) write(rem
, buf
, strlen(buf
));
(void) write(rem
, "Y\n", 2);
error("%s: not a file or directory\n", name
);
int f
, mode
, opts
, wrerr
, olderrno
;
while (*cp
>= '0' && *cp
<= '7')
opts
= (opts
<< 3) | (*cp
++ - '0');
error("recvf: options not delimited\n");
while (*cp
>= '0' && *cp
<= '7')
mode
= (mode
<< 3) | (*cp
++ - '0');
error("recvf: mode not delimited\n");
size
= size
* 10 + (*cp
++ - '0');
error("recvf: size not delimited\n");
mtime
= mtime
* 10 + (*cp
++ - '0');
error("recvf: mtime not delimited\n");
while (*cp
&& *cp
!= ' ')
error("recvf: owner name not delimited\n");
while (*cp
&& *cp
!= ' ')
error("recvf: group name not delimited\n");
if (catname
>= sizeof(stp
)) {
error("%s:%s: too many directory levels\n",
if (lstat(target
, &stb
) == 0) {
if (ISDIR(stb
.st_mode
)) {
if ((stb
.st_mode
& 07777) == mode
) {
"%s: Warning: remote mode %o != local mode %o\n",
target
, stb
.st_mode
& 07777, mode
);
(void) write(rem
, buf
, strlen(buf
+ 1) + 1);
} else if (errno
== ENOENT
&& (mkdir(target
, mode
) == 0 ||
chkparent(target
) == 0 && mkdir(target
, mode
) == 0)) {
if (chog(target
, owner
, group
, mode
) == 0)
error("%s:%s: %s\n", host
, target
, sys_errlist
[errno
]);
(void) sprintf(tp
, "/%s", cp
);
cp
= rindex(target
, '/');
(void) sprintf(new, "/%s", tmpname
);
(void) sprintf(new, "%s/%s", target
, tmpname
);
for (i
= 0; i
< size
; i
+= j
) {
if ((j
= read(rem
, cp
, size
- i
)) <= 0)
if (symlink(buf
, new) < 0) {
if (errno
!= ENOENT
|| chkparent(new) < 0 ||
if ((i
= readlink(target
, tbuf
, BUFSIZ
)) >= 0 &&
i
== size
&& strncmp(buf
, tbuf
, size
) == 0) {
if ((f
= creat(new, mode
)) < 0) {
if (errno
!= ENOENT
|| chkparent(new) < 0 ||
(f
= creat(new, mode
)) < 0)
for (i
= 0; i
< size
; i
+= BUFSIZ
) {
int j
= read(rem
, cp
, amt
);
if (wrerr
== 0 && write(f
, buf
, amt
) != amt
) {
error("%s:%s: %s\n", host
, new, sys_errlist
[olderrno
]);
if ((f1
= fopen(target
, "r")) == NULL
)
if ((f2
= fopen(new, "r")) == NULL
) {
error("%s:%s: %s\n", host
, new, sys_errlist
[errno
]);
while ((c
= getc(f1
)) == getc(f2
))
(void) sprintf(buf
+ 1, "need to update: %s\n",target
);
(void) write(rem
, buf
, strlen(buf
+ 1) + 1);
tvp
[0].tv_sec
= stb
.st_atime
; /* old atime from target */
if (utimes(new, tvp
) < 0) {
note("%s:utimes failed %s: %s\n", host
, new, sys_errlist
[errno
]);
if (chog(new, owner
, group
, mode
) < 0) {
if (rename(new, target
) < 0) {
error("%s:%s: %s\n", host
, target
, sys_errlist
[errno
]);
(void) sprintf(buf
+ 1, "updated %s\n", target
);
(void) write(rem
, buf
, strlen(buf
+ 1) + 1);
* Creat a hard link to existing file.
while (*cp
>= '0' && *cp
<= '7')
opts
= (opts
<< 3) | (*cp
++ - '0');
error("hardlink: options not delimited\n");
while (*cp
&& *cp
!= ' ')
error("hardlink: oldname name not delimited\n");
(void) sprintf(tp
, "/%s", cp
);
if (lstat(target
, &stb
) == 0) {
int mode
= stb
.st_mode
& S_IFMT
;
if (mode
!= S_IFREG
&& mode
!= S_IFLNK
) {
error("%s:%s: not a regular file\n", host
, target
);
if (chkparent(target
) < 0 ) {
error("%s:%s: %s (no parent)\n",
host
, target
, sys_errlist
[errno
]);
if (exists
&& (unlink(target
) < 0)) {
error("%s:%s: %s (unlink)\n",
host
, target
, sys_errlist
[errno
]);
if (link(oldname
, target
) < 0) {
error("%s:can't link %s to %s\n",
* Check to see if parent directory exists and create one if not.
if (cp
== NULL
|| cp
== name
)
if (lstat(name
, &stb
) < 0) {
if (errno
== ENOENT
&& chkparent(name
) >= 0 &&
mkdir(name
, 0777 & ~oumask
) >= 0) {
} else if (ISDIR(stb
.st_mode
)) {
* Change owner, group and mode of file.
chog(file
, owner
, group
, mode
)
char *file
, *owner
, *group
;
} else if (pw
== NULL
|| strcmp(owner
, pw
->pw_name
) != 0) {
if ((pw
= getpwnam(owner
)) == NULL
) {
note("%s:%s: unknown login name, clearing setuid",
} else if ((mode
& 04000) && strcmp(user
, owner
) != 0)
if (gr
== NULL
|| strcmp(group
, gr
->gr_name
) != 0) {
if ((*group
== ':' && (getgrgid(gid
= atoi(group
+ 1)) == NULL
))
|| ((gr
= getgrnam(group
)) == NULL
)) {
note("%s:%s: unknown group", host
, group
);
if (userid
&& gid
>= 0) {
if (gr
) for (i
= 0; gr
->gr_mem
[i
] != NULL
; i
++)
if (!(strcmp(user
, gr
->gr_mem
[i
])))
if (chown(file
, uid
, gid
) < 0 ||
(mode
& 07000) && chmod(file
, mode
) < 0) {
note("%s: chown or chmod failed: file %s: %s",
host
, file
, sys_errlist
[errno
]);
* Check for files on the machine being updated that are not on the master
* machine and remove them.
* Tell the remote to clean the files from the last directory sent.
(void) sprintf(buf
, "C%o\n", opts
& VERIFY
);
(void) write(rem
, buf
, strlen(buf
));
if (read(rem
, cp
, 1) != 1)
} while (*cp
++ != '\n' && cp
< &buf
[BUFSIZ
]);
case 'Q': /* Query if file should be removed */
* Return the following codes to remove query.
* N\n -- file exists - DON'T remove.
* Y\n -- file doesn't exist - REMOVE.
(void) sprintf(tp
, "/%s", s
);
printf("check %s\n", target
);
(void) write(rem
, "N\n", 2);
else if (lstat(target
, &stb
) < 0)
(void) write(rem
, "Y\n", 2);
(void) write(rem
, "N\n", 2);
(void) write(2, s
, cp
- s
);
(void) fwrite(s
, 1, cp
- s
, lfp
);
error("rmchk: unexpected response '%s'\n", buf
);
* Check the current directory (initialized by the 'T' command to server())
* for extraneous files and remove them.
register struct direct
*dp
;
while (*cp
>= '0' && *cp
<= '7')
opts
= (opts
<< 3) | (*cp
++ - '0');
error("clean: options not delimited\n");
if ((d
= opendir(target
)) == NULL
) {
error("%s:%s: %s\n", host
, target
, sys_errlist
[errno
]);
while (dp
= readdir(d
)) {
if (!strcmp(dp
->d_name
, ".") || !strcmp(dp
->d_name
, ".."))
if (len
+ 1 + strlen(dp
->d_name
) >= BUFSIZ
- 1) {
error("%s:%s/%s: Name too long\n",
host
, target
, dp
->d_name
);
if (lstat(target
, &stb
) < 0) {
error("%s:%s: %s\n", host
, target
, sys_errlist
[errno
]);
(void) sprintf(buf
, "Q%s\n", dp
->d_name
);
(void) write(rem
, buf
, strlen(buf
));
if (read(rem
, cp
, 1) != 1)
} while (*cp
++ != '\n' && cp
< &buf
[BUFSIZ
]);
(void) sprintf(cp
, "need to remove: %s\n", target
);
(void) write(rem
, buf
, strlen(cp
) + 1);
(void) write(rem
, "E\n", 2);
* Remove a file or directory (recursively) and send back an acknowledge
switch (stp
->st_mode
& S_IFMT
) {
error("%s:%s: not a plain file\n", host
, target
);
if ((d
= opendir(target
)) == NULL
)
while (dp
= readdir(d
)) {
if (!strcmp(dp
->d_name
, ".") || !strcmp(dp
->d_name
, ".."))
if (len
+ 1 + strlen(dp
->d_name
) >= BUFSIZ
- 1) {
error("%s:%s/%s: Name too long\n",
host
, target
, dp
->d_name
);
if (lstat(target
, &stb
) < 0) {
error("%s:%s: %s\n", host
, target
, sys_errlist
[errno
]);
error("%s:%s: %s\n", host
, target
, sys_errlist
[errno
]);
(void) sprintf(cp
, "removed %s\n", target
);
(void) write(rem
, buf
, strlen(cp
) + 1);
* Execute a shell command to handle special cases.
int fd
[2], status
, pid
, i
;
extern int userid
, groupid
;
error("%s\n", sys_errlist
[errno
]);
if ((pid
= fork()) == 0) {
* Return everything the shell commands print.
(void) open("/dev/null", 0);
execl("/bin/sh", "sh", "-c", cmd
, 0);
while ((i
= read(fd
[0], buf
, sizeof(buf
))) > 0) {
if (s
< &sbuf
[sizeof(sbuf
)-1])
* Throw away blank lines.
(void) write(rem
, sbuf
, s
- sbuf
);
(void) write(rem
, sbuf
, s
- sbuf
);
while ((i
= wait(&status
)) != pid
&& i
!= -1)
error("shell returned %d\n", status
);
/* Print changes locally if not quiet mode */
/* Save changes (for mailing) if really updating files */
if (!(options
& VERIFY
) && fp
!= NULL
)
fprintf(fp
, fmt
, a1
, a2
, a3
);
strcpy(buf
, "\1rdist: ");
(void) sprintf(buf
+8, fmt
, a1
, a2
, a3
);
(void) write(2, buf
+1, strlen(buf
+1));
(void) write(rem
, buf
, strlen(buf
));
(void) fwrite(buf
+1, 1, strlen(buf
+1), lfp
);
strcpy(buf
, "\2rdist: ");
(void) sprintf(buf
+8, fmt
, a1
, a2
, a3
);
(void) write(2, buf
+1, strlen(buf
+1));
(void) write(rem
, buf
, strlen(buf
));
(void) fwrite(buf
+1, 1, strlen(buf
+1), lfp
);
if (read(rem
, cp
, 1) != 1)
} while (*cp
++ != '\n' && cp
< &resp
[BUFSIZ
]);
log(lfp
, "Note: %s\n",s
);
(void) write(2, s
, cp
- s
);
(void) fwrite(s
, 1, cp
- s
, lfp
);
* Remove temporary files and do any cleanup operations before exiting.
sprintf(buf
, fmt
, a1
, a2
, a3
);
write(rem
, s
, strlen(s
));