diff options
author | peter <peter@FreeBSD.org> | 1996-08-20 23:46:10 +0000 |
---|---|---|
committer | peter <peter@FreeBSD.org> | 1996-08-20 23:46:10 +0000 |
commit | 8982e501c77217c860f79bba431f46a62b607a21 (patch) | |
tree | 70187fdf5be4cbefd0baf46bddac7e5e32c13c24 /contrib/cvs/src/server.c | |
parent | 01ee40fd6a76f6ff7ef247fc1b2cf6e337f216c5 (diff) | |
download | FreeBSD-src-8982e501c77217c860f79bba431f46a62b607a21.zip FreeBSD-src-8982e501c77217c860f79bba431f46a62b607a21.tar.gz |
Import of slightly trimmed cvs-1.8 distribution. Generated files
and non-unix code has been left out.
Diffstat (limited to 'contrib/cvs/src/server.c')
-rw-r--r-- | contrib/cvs/src/server.c | 4642 |
1 files changed, 4642 insertions, 0 deletions
diff --git a/contrib/cvs/src/server.c b/contrib/cvs/src/server.c new file mode 100644 index 0000000..e92445b --- /dev/null +++ b/contrib/cvs/src/server.c @@ -0,0 +1,4642 @@ +#include <assert.h> +#include "cvs.h" +#include "watch.h" +#include "edit.h" +#include "fileattr.h" + +#ifdef SERVER_SUPPORT + +/* for select */ +#include <sys/types.h> +#ifdef HAVE_SYS_BSDTYPES_H +#include <sys/bsdtypes.h> +#endif +#include <sys/time.h> + +#if HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif + +#if HAVE_FCNTL_H +#include <fcntl.h> +#endif + +#ifndef O_NONBLOCK +#define O_NONBLOCK O_NDELAY +#endif + +#ifdef AUTH_SERVER_SUPPORT +/* For initgroups(). */ +#if HAVE_INITGROUPS +#include <grp.h> +#endif /* HAVE_INITGROUPS */ +#endif /* AUTH_SERVER_SUPPORT */ + + +/* Functions which the server calls. */ +int add PROTO((int argc, char **argv)); +int admin PROTO((int argc, char **argv)); +int checkout PROTO((int argc, char **argv)); +int commit PROTO((int argc, char **argv)); +int diff PROTO((int argc, char **argv)); +int history PROTO((int argc, char **argv)); +int import PROTO((int argc, char **argv)); +int cvslog PROTO((int argc, char **argv)); +int patch PROTO((int argc, char **argv)); +int release PROTO((int argc, char **argv)); +int cvsremove PROTO((int argc, char **argv)); +int rtag PROTO((int argc, char **argv)); +int status PROTO((int argc, char **argv)); +int tag PROTO((int argc, char **argv)); +int update PROTO((int argc, char **argv)); + + +/* + * This is where we stash stuff we are going to use. Format string + * which expects a single directory within it, starting with a slash. + */ +static char *server_temp_dir; + +/* Nonzero if we should keep the temp directory around after we exit. */ +static int dont_delete_temp; + +static char no_mem_error; +#define NO_MEM_ERROR (&no_mem_error) + +static void server_write_entries PROTO((void)); + +/* + * Read a line from the stream "instream" without command line editing. + * + * Action is compatible with "readline", e.g. space for the result is + * malloc'd and should be freed by the caller. + * + * A NULL return means end of file. A return of NO_MEM_ERROR means + * that we are out of memory. + */ +static char *read_line PROTO((FILE *)); + +static char * +read_line (stream) + FILE *stream; +{ + int c; + char *result; + int input_index = 0; + int result_size = 80; + + fflush (stdout); + result = (char *) malloc (result_size); + if (result == NULL) + return NO_MEM_ERROR; + + while (1) + { + c = fgetc (stream); + + if (c == EOF) + { + free (result); + return NULL; + } + + if (c == '\n') + break; + + result[input_index++] = c; + while (input_index >= result_size) + { + result_size *= 2; + result = (char *) realloc (result, result_size); + if (result == NULL) + return NO_MEM_ERROR; + } + } + + result[input_index++] = '\0'; + return result; +} + +/* + * Make directory DIR, including all intermediate directories if necessary. + * Returns 0 for success or errno code. + */ +static int mkdir_p PROTO((char *)); + +static int +mkdir_p (dir) + char *dir; +{ + char *p; + char *q = malloc (strlen (dir) + 1); + int retval; + + if (q == NULL) + return ENOMEM; + + /* + * Skip over leading slash if present. We won't bother to try to + * make '/'. + */ + p = dir + 1; + while (1) + { + while (*p != '/' && *p != '\0') + ++p; + if (*p == '/') + { + strncpy (q, dir, p - dir); + q[p - dir] = '\0'; + if (CVS_MKDIR (q, 0777) < 0) + { + if (errno != EEXIST + && (errno != EACCES || !isdir(q))) + { + retval = errno; + goto done; + } + } + ++p; + } + else + { + if (CVS_MKDIR (dir, 0777) < 0) + retval = errno; + else + retval = 0; + goto done; + } + } + done: + free (q); + return retval; +} + +/* + * Print the error response for error code STATUS. The caller is + * reponsible for making sure we get back to the command loop without + * any further output occuring. + */ +static void +print_error (status) + int status; +{ + char *msg; + printf ("error "); + msg = strerror (status); + if (msg) + printf ("%s", msg); + printf ("\n"); +} + +static int pending_error; +/* + * Malloc'd text for pending error. Each line must start with "E ". The + * last line should not end with a newline. + */ +static char *pending_error_text; + +/* If an error is pending, print it and return 1. If not, return 0. */ +static int +print_pending_error () +{ + if (pending_error_text) + { + printf ("%s\n", pending_error_text); + if (pending_error) + print_error (pending_error); + else + printf ("error \n"); + pending_error = 0; + free (pending_error_text); + pending_error_text = NULL; + return 1; + } + else if (pending_error) + { + print_error (pending_error); + pending_error = 0; + return 1; + } + else + return 0; +} + +/* Is an error pending? */ +#define error_pending() (pending_error || pending_error_text) + +int +supported_response (name) + char *name; +{ + struct response *rs; + + for (rs = responses; rs->name != NULL; ++rs) + if (strcmp (rs->name, name) == 0) + return rs->status == rs_supported; + error (1, 0, "internal error: testing support for unknown response?"); + /* NOTREACHED */ + return 0; +} + +static void +serve_valid_responses (arg) + char *arg; +{ + char *p = arg; + char *q; + struct response *rs; + do + { + q = strchr (p, ' '); + if (q != NULL) + *q++ = '\0'; + for (rs = responses; rs->name != NULL; ++rs) + { + if (strcmp (rs->name, p) == 0) + break; + } + if (rs->name == NULL) + /* + * It is a response we have never heard of (and thus never + * will want to use). So don't worry about it. + */ + ; + else + rs->status = rs_supported; + p = q; + } while (q != NULL); + for (rs = responses; rs->name != NULL; ++rs) + { + if (rs->status == rs_essential) + { + printf ("E response `%s' not supported by client\nerror \n", + rs->name); + exit (EXIT_FAILURE); + } + else if (rs->status == rs_optional) + rs->status = rs_not_supported; + } +} + +static int use_dir_and_repos = 0; + +static void +serve_root (arg) + char *arg; +{ + char *env; + extern char *CVSroot; + char path[PATH_MAX]; + int save_errno; + + if (error_pending()) return; + + (void) sprintf (path, "%s/%s", arg, CVSROOTADM); + if (!isaccessible (path, R_OK | X_OK)) + { + save_errno = errno; + pending_error_text = malloc (80 + strlen (path)); + if (pending_error_text != NULL) + sprintf (pending_error_text, "E Cannot access %s", path); + pending_error = save_errno; + } + (void) strcat (path, "/"); + (void) strcat (path, CVSROOTADM_HISTORY); + if (isfile (path) && !isaccessible (path, R_OK | W_OK)) + { + save_errno = errno; + pending_error_text = malloc (80 + strlen (path)); + if (pending_error_text != NULL) + sprintf (pending_error_text, "E \ +Sorry, you don't have read/write access to the history file %s", path); + pending_error = save_errno; + } + + CVSroot = malloc (strlen (arg) + 1); + if (CVSroot == NULL) + { + pending_error = ENOMEM; + return; + } + strcpy (CVSroot, arg); +#ifdef HAVE_PUTENV + env = malloc (strlen (CVSROOT_ENV) + strlen (CVSroot) + 1 + 1); + if (env == NULL) + { + pending_error = ENOMEM; + return; + } + (void) sprintf (env, "%s=%s", CVSROOT_ENV, arg); + (void) putenv (env); + /* do not free env, as putenv has control of it */ +#endif +} + +/* + * Add as many directories to the temp directory as the client tells us it + * will use "..", so we never try to access something outside the temp + * directory via "..". + */ +static void +serve_max_dotdot (arg) + char *arg; +{ + int lim = atoi (arg); + int i; + char *p; + + if (lim < 0) + return; + p = malloc (strlen (server_temp_dir) + 2 * lim + 10); + if (p == NULL) + { + pending_error = ENOMEM; + return; + } + strcpy (p, server_temp_dir); + for (i = 0; i < lim; ++i) + strcat (p, "/d"); + free (server_temp_dir); + server_temp_dir = p; +} + +static char *dir_name; + +static void +dirswitch (dir, repos) + char *dir; + char *repos; +{ + int status; + FILE *f; + + server_write_entries (); + + if (error_pending()) return; + + if (dir_name != NULL) + free (dir_name); + + dir_name = malloc (strlen (server_temp_dir) + strlen (dir) + 40); + if (dir_name == NULL) + { + pending_error = ENOMEM; + return; + } + + strcpy (dir_name, server_temp_dir); + strcat (dir_name, "/"); + strcat (dir_name, dir); + + status = mkdir_p (dir_name); + if (status != 0 + && status != EEXIST) + { + pending_error = status; + pending_error_text = malloc (80 + strlen(dir_name)); + sprintf(pending_error_text, "E cannot mkdir %s", dir_name); + return; + } + if (chdir (dir_name) < 0) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(dir_name)); + sprintf(pending_error_text, "E cannot change to %s", dir_name); + return; + } + /* + * This is pretty much like calling Create_Admin, but Create_Admin doesn't + * report errors in the right way for us. + */ + if (CVS_MKDIR (CVSADM, 0777) < 0) + { + if (errno == EEXIST) + /* Don't create the files again. */ + return; + pending_error = errno; + return; + } + f = fopen (CVSADM_REP, "w"); + if (f == NULL) + { + pending_error = errno; + return; + } + if (fprintf (f, "%s\n", repos) < 0) + { + pending_error = errno; + fclose (f); + return; + } + if (fclose (f) == EOF) + { + pending_error = errno; + return; + } + f = fopen (CVSADM_ENT, "w+"); + if (f == NULL) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_ENT)); + sprintf(pending_error_text, "E cannot open %s", CVSADM_ENT); + return; + } + if (fclose (f) == EOF) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_ENT)); + sprintf(pending_error_text, "E cannot close %s", CVSADM_ENT); + return; + } +} + +static void +serve_repository (arg) + char *arg; +{ + dirswitch (arg + 1, arg); +} + +static void +serve_directory (arg) + char *arg; +{ + char *repos; + use_dir_and_repos = 1; + repos = read_line (stdin); + if (repos == NULL) + { + pending_error_text = malloc (80 + strlen (arg)); + if (pending_error_text) + { + if (feof (stdin)) + sprintf (pending_error_text, + "E end of file reading mode for %s", arg); + else + { + sprintf (pending_error_text, + "E error reading mode for %s", arg); + pending_error = errno; + } + } + else + pending_error = ENOMEM; + } + else if (repos == NO_MEM_ERROR) + { + pending_error = ENOMEM; + } + else + { + dirswitch (arg, repos); + free (repos); + } +} + +static void +serve_static_directory (arg) + char *arg; +{ + FILE *f; + f = fopen (CVSADM_ENTSTAT, "w+"); + if (f == NULL) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_ENTSTAT)); + sprintf(pending_error_text, "E cannot open %s", CVSADM_ENTSTAT); + return; + } + if (fclose (f) == EOF) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_ENTSTAT)); + sprintf(pending_error_text, "E cannot close %s", CVSADM_ENTSTAT); + return; + } +} + +static void +serve_sticky (arg) + char *arg; +{ + FILE *f; + f = fopen (CVSADM_TAG, "w+"); + if (f == NULL) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_TAG)); + sprintf(pending_error_text, "E cannot open %s", CVSADM_TAG); + return; + } + if (fprintf (f, "%s\n", arg) < 0) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_TAG)); + sprintf(pending_error_text, "E cannot write to %s", CVSADM_TAG); + return; + } + if (fclose (f) == EOF) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_TAG)); + sprintf(pending_error_text, "E cannot close %s", CVSADM_TAG); + return; + } +} + +/* + * Read SIZE bytes from stdin, write them to FILE. + * + * Currently this isn't really used for receiving parts of a file -- + * the file is still sent over in one chunk. But if/when we get + * spiffy in-process gzip support working, perhaps the compressed + * pieces could be sent over as they're ready, if the network is fast + * enough. Or something. + */ +static void +receive_partial_file (size, file) + int size; + int file; +{ + char buf[16*1024], *bufp; + int toread, nread, nwrote; + while (size > 0) + { + toread = sizeof (buf); + if (toread > size) + toread = size; + + nread = fread (buf, 1, toread, stdin); + if (nread <= 0) + { + if (feof (stdin)) + { + pending_error_text = malloc (80); + if (pending_error_text) + { + sprintf (pending_error_text, + "E premature end of file from client"); + pending_error = 0; + } + else + pending_error = ENOMEM; + } + else if (ferror (stdin)) + { + pending_error_text = malloc (40); + if (pending_error_text) + sprintf (pending_error_text, + "E error reading from client"); + pending_error = errno; + } + else + { + pending_error_text = malloc (40); + if (pending_error_text) + sprintf (pending_error_text, + "E short read from client"); + pending_error = 0; + } + return; + } + size -= nread; + bufp = buf; + while (nread) + { + nwrote = write (file, bufp, nread); + if (nwrote < 0) + { + pending_error_text = malloc (40); + if (pending_error_text) + sprintf (pending_error_text, "E unable to write"); + pending_error = errno; + return; + } + nread -= nwrote; + bufp += nwrote; + } + } +} + +/* Receive SIZE bytes, write to filename FILE. */ +static void +receive_file (size, file, gzipped) + int size; + char *file; + int gzipped; +{ + int fd; + char *arg = file; + pid_t gzip_pid = 0; + int gzip_status; + + /* Write the file. */ + fd = open (arg, O_WRONLY | O_CREAT | O_TRUNC, 0600); + if (fd < 0) + { + pending_error_text = malloc (40 + strlen (arg)); + if (pending_error_text) + sprintf (pending_error_text, "E cannot open %s", arg); + pending_error = errno; + return; + } + + /* + * FIXME: This doesn't do anything reasonable with gunzip's stderr, which + * means that if gunzip writes to stderr, it will cause all manner of + * protocol violations. + */ + if (gzipped) + fd = filter_through_gunzip (fd, 0, &gzip_pid); + + receive_partial_file (size, fd); + + if (pending_error_text) + { + char *p = realloc (pending_error_text, + strlen (pending_error_text) + strlen (arg) + 30); + if (p) + { + pending_error_text = p; + sprintf (p + strlen (p), ", file %s", arg); + } + /* else original string is supposed to be unchanged */ + } + + if (close (fd) < 0 && !error_pending ()) + { + pending_error_text = malloc (40 + strlen (arg)); + if (pending_error_text) + sprintf (pending_error_text, "E cannot close %s", arg); + pending_error = errno; + if (gzip_pid) + waitpid (gzip_pid, (int *) 0, 0); + return; + } + + if (gzip_pid) + { + if (waitpid (gzip_pid, &gzip_status, 0) != gzip_pid) + error (1, errno, "waiting for gunzip process %ld", + (long) gzip_pid); + else if (gzip_status != 0) + error (1, 0, "gunzip exited %d", gzip_status); + } +} + +static void +serve_modified (arg) + char *arg; +{ + int size; + char *size_text; + char *mode_text; + + int gzipped = 0; + + if (error_pending ()) return; + + mode_text = read_line (stdin); + if (mode_text == NULL) + { + pending_error_text = malloc (80 + strlen (arg)); + if (pending_error_text) + { + if (feof (stdin)) + sprintf (pending_error_text, + "E end of file reading mode for %s", arg); + else + { + sprintf (pending_error_text, + "E error reading mode for %s", arg); + pending_error = errno; + } + } + else + pending_error = ENOMEM; + return; + } + else if (mode_text == NO_MEM_ERROR) + { + pending_error = ENOMEM; + return; + } + size_text = read_line (stdin); + if (size_text == NULL) + { + pending_error_text = malloc (80 + strlen (arg)); + if (pending_error_text) + { + if (feof (stdin)) + sprintf (pending_error_text, + "E end of file reading size for %s", arg); + else + { + sprintf (pending_error_text, + "E error reading size for %s", arg); + pending_error = errno; + } + } + else + pending_error = ENOMEM; + return; + } + else if (size_text == NO_MEM_ERROR) + { + pending_error = ENOMEM; + return; + } + if (size_text[0] == 'z') + { + gzipped = 1; + size = atoi (size_text + 1); + } + else + size = atoi (size_text); + free (size_text); + + if (size >= 0) + { + receive_file (size, arg, gzipped); + if (error_pending ()) return; + } + + { + int status = change_mode (arg, mode_text); + free (mode_text); + if (status) + { + pending_error_text = malloc (40 + strlen (arg)); + if (pending_error_text) + sprintf (pending_error_text, + "E cannot change mode for %s", arg); + pending_error = status; + return; + } + } +} + +#endif /* SERVER_SUPPORT */ + +#if defined(SERVER_SUPPORT) || defined(CLIENT_SUPPORT) + +int use_unchanged = 0; + +#endif +#ifdef SERVER_SUPPORT + +static void +serve_enable_unchanged (arg) + char *arg; +{ + use_unchanged = 1; +} + +static void +serve_lost (arg) + char *arg; +{ + if (use_unchanged) + { + /* A missing file already indicates it is nonexistent. */ + return; + } + else + { + struct utimbuf ut; + int fd = open (arg, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (fd < 0 || close (fd) < 0) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(arg)); + sprintf(pending_error_text, "E cannot open %s", arg); + return; + } + /* + * Set the times to the beginning of the epoch to tell time_stamp() + * that the file was lost. + */ + ut.actime = 0; + ut.modtime = 0; + if (utime (arg, &ut) < 0) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(arg)); + sprintf(pending_error_text, "E cannot utime %s", arg); + return; + } + } +} + +struct an_entry { + struct an_entry *next; + char *entry; +}; + +static struct an_entry *entries; + +static void +serve_unchanged (arg) + char *arg; +{ + if (error_pending ()) + return; + if (!use_unchanged) + { + /* A missing file already indicates it is unchanged. */ + return; + } + else + { + struct an_entry *p; + char *name; + char *cp; + char *timefield; + + /* Rewrite entries file to have `=' in timestamp field. */ + for (p = entries; p != NULL; p = p->next) + { + name = p->entry + 1; + cp = strchr (name, '/'); + if (cp != NULL + && strlen (arg) == cp - name + && strncmp (arg, name, cp - name) == 0) + { + timefield = strchr (cp + 1, '/') + 1; + if (*timefield != '=') + { + cp = timefield + strlen (timefield); + cp[1] = '\0'; + while (cp > timefield) + { + *cp = cp[-1]; + --cp; + } + *timefield = '='; + } + break; + } + } + } +} + +static void +serve_entry (arg) + char *arg; +{ + struct an_entry *p; + char *cp; + if (error_pending()) return; + p = (struct an_entry *) malloc (sizeof (struct an_entry)); + if (p == NULL) + { + pending_error = ENOMEM; + return; + } + /* Leave space for serve_unchanged to write '=' if it wants. */ + cp = malloc (strlen (arg) + 2); + if (cp == NULL) + { + pending_error = ENOMEM; + return; + } + strcpy (cp, arg); + p->next = entries; + p->entry = cp; + entries = p; +} + +static void +server_write_entries () +{ + FILE *f; + struct an_entry *p; + struct an_entry *q; + + if (entries == NULL) + return; + + f = NULL; + /* Note that we free all the entries regardless of errors. */ + if (!error_pending ()) + { + f = fopen (CVSADM_ENT, "w"); + if (f == NULL) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_ENT)); + sprintf(pending_error_text, "E cannot open %s", CVSADM_ENT); + } + } + for (p = entries; p != NULL;) + { + if (!error_pending ()) + { + if (fprintf (f, "%s\n", p->entry) < 0) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_ENT)); + sprintf(pending_error_text, "E cannot write to %s", CVSADM_ENT); + } + } + free (p->entry); + q = p->next; + free (p); + p = q; + } + entries = NULL; + if (f != NULL && fclose (f) == EOF && !error_pending ()) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_ENT)); + sprintf(pending_error_text, "E cannot close %s", CVSADM_ENT); + } +} + +struct notify_note { + /* Directory in which this notification happens. malloc'd*/ + char *dir; + + /* malloc'd. */ + char *filename; + + /* The following three all in one malloc'd block, pointed to by TYPE. + Each '\0' terminated. */ + /* "E" or "U". */ + char *type; + /* time+host+dir */ + char *val; + char *watches; + + struct notify_note *next; +}; + +static struct notify_note *notify_list; +/* Used while building list, to point to the last node that already exists. */ +static struct notify_note *last_node; + +static void serve_notify PROTO ((char *)); + +static void +serve_notify (arg) + char *arg; +{ + struct notify_note *new; + char *data; + + if (error_pending ()) return; + + new = (struct notify_note *) malloc (sizeof (struct notify_note)); + if (new == NULL) + { + pending_error = ENOMEM; + return; + } + if (dir_name == NULL) + goto error; + new->dir = malloc (strlen (dir_name) + 1); + if (new->dir == NULL) + { + pending_error = ENOMEM; + return; + } + strcpy (new->dir, dir_name); + new->filename = malloc (strlen (arg) + 1); + if (new->filename == NULL) + { + pending_error = ENOMEM; + return; + } + strcpy (new->filename, arg); + + data = read_line (stdin); + if (data == NULL) + { + pending_error_text = malloc (80 + strlen (arg)); + if (pending_error_text) + { + if (feof (stdin)) + sprintf (pending_error_text, + "E end of file reading mode for %s", arg); + else + { + sprintf (pending_error_text, + "E error reading mode for %s", arg); + pending_error = errno; + } + } + else + pending_error = ENOMEM; + } + else if (data == NO_MEM_ERROR) + { + pending_error = ENOMEM; + } + else + { + char *cp; + + new->type = data; + if (data[1] != '\t') + goto error; + data[1] = '\0'; + cp = data + 2; + new->val = cp; + cp = strchr (cp, '\t'); + if (cp == NULL) + goto error; + *cp++ = '+'; + cp = strchr (cp, '\t'); + if (cp == NULL) + goto error; + *cp++ = '+'; + cp = strchr (cp, '\t'); + if (cp == NULL) + goto error; + *cp++ = '\0'; + new->watches = cp; + /* If there is another tab, ignore everything after it, + for future expansion. */ + cp = strchr (cp, '\t'); + if (cp != NULL) + { + *cp = '\0'; + } + + new->next = NULL; + + if (last_node == NULL) + { + notify_list = new; + } + else + last_node->next = new; + last_node = new; + } + return; + error: + pending_error_text = malloc (40); + if (pending_error_text) + strcpy (pending_error_text, + "E Protocol error; misformed Notify request"); + pending_error = 0; + return; +} + +/* Process all the Notify requests that we have stored up. Returns 0 + if successful, if not prints error message (via error()) and + returns negative value. */ +static int +server_notify () +{ + struct notify_note *p; + char *repos; + List *list; + Node *node; + int status; + + while (notify_list != NULL) + { + if (chdir (notify_list->dir) < 0) + { + error (0, errno, "cannot change to %s", notify_list->dir); + return -1; + } + repos = Name_Repository (NULL, NULL); + + /* Now writelock. */ + list = getlist (); + node = getnode (); + node->type = LOCK; + node->key = xstrdup (repos); + status = addnode (list, node); + assert (status == 0); + Writer_Lock (list); + + fileattr_startdir (repos); + + notify_do (*notify_list->type, notify_list->filename, getcaller(), + notify_list->val, notify_list->watches, repos); + + printf ("Notified "); + if (use_dir_and_repos) + { + char *dir = notify_list->dir + strlen (server_temp_dir) + 1; + if (dir[0] == '\0') + fputs (".", stdout); + else + fputs (dir, stdout); + fputs ("/\n", stdout); + } + fputs (repos, stdout); + fputs ("/", stdout); + fputs (notify_list->filename, stdout); + fputs ("\n", stdout); + + p = notify_list->next; + free (notify_list->filename); + free (notify_list->dir); + free (notify_list->type); + free (notify_list); + notify_list = p; + + fileattr_write (); + fileattr_free (); + + /* Remove the writelock. */ + Lock_Cleanup (); + dellist (&list); + } + /* do_cvs_command writes to stdout via write(), not stdio, so better + flush out the buffer. */ + fflush (stdout); + return 0; +} + +static int argument_count; +static char **argument_vector; +static int argument_vector_size; + +static void +serve_argument (arg) + char *arg; +{ + char *p; + + if (error_pending()) return; + + if (argument_vector_size <= argument_count) + { + argument_vector_size *= 2; + argument_vector = + (char **) realloc ((char *)argument_vector, + argument_vector_size * sizeof (char *)); + if (argument_vector == NULL) + { + pending_error = ENOMEM; + return; + } + } + p = malloc (strlen (arg) + 1); + if (p == NULL) + { + pending_error = ENOMEM; + return; + } + strcpy (p, arg); + argument_vector[argument_count++] = p; +} + +static void +serve_argumentx (arg) + char *arg; +{ + char *p; + + if (error_pending()) return; + + p = argument_vector[argument_count - 1]; + p = realloc (p, strlen (p) + 1 + strlen (arg) + 1); + if (p == NULL) + { + pending_error = ENOMEM; + return; + } + strcat (p, "\n"); + strcat (p, arg); + argument_vector[argument_count - 1] = p; +} + +static void +serve_global_option (arg) + char *arg; +{ + if (arg[0] != '-' || arg[1] == '\0' || arg[2] != '\0') + { + error_return: + pending_error_text = malloc (strlen (arg) + 80); + sprintf (pending_error_text, "E Protocol error: bad global option %s", + arg); + return; + } + switch (arg[1]) + { + case 'n': + noexec = 1; + break; + case 'q': + quiet = 1; + break; + case 'r': + cvswrite = 0; + break; + case 'Q': + really_quiet = 1; + break; + case 'l': + logoff = 1; + break; + case 't': + trace = 1; + break; + default: + goto error_return; + } +} + +static void +serve_set (arg) + char *arg; +{ + /* FIXME: This sends errors immediately (I think); they should be + put into pending_error. */ + variable_set (arg); +} + +/* + * We must read data from a child process and send it across the + * network. We do not want to block on writing to the network, so we + * store the data from the child process in memory. A BUFFER + * structure holds the status of one communication, and uses a linked + * list of buffer_data structures to hold data. + */ + +struct buffer +{ + /* Data. */ + struct buffer_data *data; + + /* Last buffer on data chain. */ + struct buffer_data *last; + + /* File descriptor to write to or read from. */ + int fd; + + /* Nonzero if this is an output buffer (sanity check). */ + int output; + + /* Nonzero if the file descriptor is in nonblocking mode. */ + int nonblocking; + + /* Function to call if we can't allocate memory. */ + void (*memory_error) PROTO((struct buffer *)); +}; + +/* Data is stored in lists of these structures. */ + +struct buffer_data +{ + /* Next buffer in linked list. */ + struct buffer_data *next; + + /* + * A pointer into the data area pointed to by the text field. This + * is where to find data that has not yet been written out. + */ + char *bufp; + + /* The number of data bytes found at BUFP. */ + int size; + + /* + * Actual buffer. This never changes after the structure is + * allocated. The buffer is BUFFER_DATA_SIZE bytes. + */ + char *text; +}; + +/* The size we allocate for each buffer_data structure. */ +#define BUFFER_DATA_SIZE (4096) + +#ifdef SERVER_FLOWCONTROL +/* The maximum we'll queue to the remote client before blocking. */ +# ifndef SERVER_HI_WATER +# define SERVER_HI_WATER (2 * 1024 * 1024) +# endif /* SERVER_HI_WATER */ +/* When the buffer drops to this, we restart the child */ +# ifndef SERVER_LO_WATER +# define SERVER_LO_WATER (1 * 1024 * 1024) +# endif /* SERVER_LO_WATER */ +#endif /* SERVER_FLOWCONTROL */ + +/* Linked list of available buffer_data structures. */ +static struct buffer_data *free_buffer_data; + +static void allocate_buffer_datas PROTO((void)); +static inline struct buffer_data *get_buffer_data PROTO((void)); +static int buf_empty_p PROTO((struct buffer *)); +static void buf_output PROTO((struct buffer *, const char *, int)); +static void buf_output0 PROTO((struct buffer *, const char *)); +static inline void buf_append_char PROTO((struct buffer *, int)); +static int buf_send_output PROTO((struct buffer *)); +static int set_nonblock PROTO((struct buffer *)); +static int set_block PROTO((struct buffer *)); +static int buf_send_counted PROTO((struct buffer *)); +static inline void buf_append_data PROTO((struct buffer *, + struct buffer_data *, + struct buffer_data *)); +static int buf_read_file PROTO((FILE *, long, struct buffer_data **, + struct buffer_data **)); +static int buf_input_data PROTO((struct buffer *, int *)); +static void buf_copy_lines PROTO((struct buffer *, struct buffer *, int)); +static int buf_copy_counted PROTO((struct buffer *, struct buffer *)); + +#ifdef SERVER_FLOWCONTROL +static int buf_count_mem PROTO((struct buffer *)); +static int set_nonblock_fd PROTO((int)); +#endif /* SERVER_FLOWCONTROL */ + +/* Allocate more buffer_data structures. */ + +static void +allocate_buffer_datas () +{ + struct buffer_data *alc; + char *space; + int i; + + /* Allocate buffer_data structures in blocks of 16. */ +#define ALLOC_COUNT (16) + + alc = ((struct buffer_data *) + malloc (ALLOC_COUNT * sizeof (struct buffer_data))); + space = (char *) valloc (ALLOC_COUNT * BUFFER_DATA_SIZE); + if (alc == NULL || space == NULL) + return; + for (i = 0; i < ALLOC_COUNT; i++, alc++, space += BUFFER_DATA_SIZE) + { + alc->next = free_buffer_data; + free_buffer_data = alc; + alc->text = space; + } +} + +/* Get a new buffer_data structure. */ + +static inline struct buffer_data * +get_buffer_data () +{ + struct buffer_data *ret; + + if (free_buffer_data == NULL) + { + allocate_buffer_datas (); + if (free_buffer_data == NULL) + return NULL; + } + + ret = free_buffer_data; + free_buffer_data = ret->next; + return ret; +} + +/* See whether a buffer is empty. */ + +static int +buf_empty_p (buf) + struct buffer *buf; +{ + struct buffer_data *data; + + for (data = buf->data; data != NULL; data = data->next) + if (data->size > 0) + return 0; + return 1; +} + +#ifdef SERVER_FLOWCONTROL +/* + * Count how much data is stored in the buffer.. + * Note that each buffer is a malloc'ed chunk BUFFER_DATA_SIZE. + */ + +static int +buf_count_mem (buf) + struct buffer *buf; +{ + struct buffer_data *data; + int mem = 0; + + for (data = buf->data; data != NULL; data = data->next) + mem += BUFFER_DATA_SIZE; + + return mem; +} +#endif /* SERVER_FLOWCONTROL */ + +/* Add data DATA of length LEN to BUF. */ + +static void +buf_output (buf, data, len) + struct buffer *buf; + const char *data; + int len; +{ + if (buf->data != NULL + && (((buf->last->text + BUFFER_DATA_SIZE) + - (buf->last->bufp + buf->last->size)) + >= len)) + { + memcpy (buf->last->bufp + buf->last->size, data, len); + buf->last->size += len; + return; + } + + while (1) + { + struct buffer_data *newdata; + + newdata = get_buffer_data (); + if (newdata == NULL) + { + (*buf->memory_error) (buf); + return; + } + + if (buf->data == NULL) + buf->data = newdata; + else + buf->last->next = newdata; + newdata->next = NULL; + buf->last = newdata; + + newdata->bufp = newdata->text; + + if (len <= BUFFER_DATA_SIZE) + { + newdata->size = len; + memcpy (newdata->text, data, len); + return; + } + + newdata->size = BUFFER_DATA_SIZE; + memcpy (newdata->text, data, BUFFER_DATA_SIZE); + + data += BUFFER_DATA_SIZE; + len -= BUFFER_DATA_SIZE; + } + + /*NOTREACHED*/ +} + +/* Add a '\0' terminated string to BUF. */ + +static void +buf_output0 (buf, string) + struct buffer *buf; + const char *string; +{ + buf_output (buf, string, strlen (string)); +} + +/* Add a single character to BUF. */ + +static inline void +buf_append_char (buf, ch) + struct buffer *buf; + int ch; +{ + if (buf->data != NULL + && (buf->last->text + BUFFER_DATA_SIZE + != buf->last->bufp + buf->last->size)) + { + *(buf->last->bufp + buf->last->size) = ch; + ++buf->last->size; + } + else + { + char b; + + b = ch; + buf_output (buf, &b, 1); + } +} + +/* + * Send all the output we've been saving up. Returns 0 for success or + * errno code. If the buffer has been set to be nonblocking, this + * will just write until the write would block. + */ + +static int +buf_send_output (buf) + struct buffer *buf; +{ + if (! buf->output) + abort (); + + while (buf->data != NULL) + { + struct buffer_data *data; + + data = buf->data; + while (data->size > 0) + { + int nbytes; + + nbytes = write (buf->fd, data->bufp, data->size); + if (nbytes <= 0) + { + int status; + + if (buf->nonblocking + && (nbytes == 0 +#ifdef EWOULDBLOCK + || errno == EWOULDBLOCK +#endif + || errno == EAGAIN)) + { + /* + * A nonblocking write failed to write any data. + * Just return. + */ + return 0; + } + + /* + * An error, or EOF. Throw away all the data and + * return. + */ + if (nbytes == 0) + status = EIO; + else + status = errno; + + buf->last->next = free_buffer_data; + free_buffer_data = buf->data; + buf->data = NULL; + buf->last = NULL; + + return status; + } + + data->size -= nbytes; + data->bufp += nbytes; + } + + buf->data = data->next; + data->next = free_buffer_data; + free_buffer_data = data; + } + + buf->last = NULL; + + return 0; +} + +#ifdef SERVER_FLOWCONTROL +/* + * Set buffer BUF to non-blocking I/O. Returns 0 for success or errno + * code. + */ + +static int +set_nonblock_fd (fd) + int fd; +{ + int flags; + + flags = fcntl (fd, F_GETFL, 0); + if (flags < 0) + return errno; + if (fcntl (fd, F_SETFL, flags | O_NONBLOCK) < 0) + return errno; + return 0; +} +#endif /* SERVER_FLOWCONTROL */ + +static int +set_nonblock (buf) + struct buffer *buf; +{ + int flags; + + if (buf->nonblocking) + return 0; + flags = fcntl (buf->fd, F_GETFL, 0); + if (flags < 0) + return errno; + if (fcntl (buf->fd, F_SETFL, flags | O_NONBLOCK) < 0) + return errno; + buf->nonblocking = 1; + return 0; +} + +/* + * Set buffer BUF to blocking I/O. Returns 0 for success or errno + * code. + */ + +static int +set_block (buf) + struct buffer *buf; +{ + int flags; + + if (! buf->nonblocking) + return 0; + flags = fcntl (buf->fd, F_GETFL, 0); + if (flags < 0) + return errno; + if (fcntl (buf->fd, F_SETFL, flags & ~O_NONBLOCK) < 0) + return errno; + buf->nonblocking = 0; + return 0; +} + +/* + * Send a character count and some output. Returns errno code or 0 for + * success. + * + * Sending the count in binary is OK since this is only used on a pipe + * within the same system. + */ + +static int +buf_send_counted (buf) + struct buffer *buf; +{ + int size; + struct buffer_data *data; + + if (! buf->output) + abort (); + + size = 0; + for (data = buf->data; data != NULL; data = data->next) + size += data->size; + + data = get_buffer_data (); + if (data == NULL) + { + (*buf->memory_error) (buf); + return ENOMEM; + } + + data->next = buf->data; + buf->data = data; + if (buf->last == NULL) + buf->last = data; + + data->bufp = data->text; + data->size = sizeof (int); + + *((int *) data->text) = size; + + return buf_send_output (buf); +} + +/* Append a list of buffer_data structures to an buffer. */ + +static inline void +buf_append_data (buf, data, last) + struct buffer *buf; + struct buffer_data *data; + struct buffer_data *last; +{ + if (data != NULL) + { + if (buf->data == NULL) + buf->data = data; + else + buf->last->next = data; + buf->last = last; + } +} + +/* + * Copy the contents of file F into buffer_data structures. We can't + * copy directly into an buffer, because we want to handle failure and + * succeess differently. Returns 0 on success, or -2 if out of + * memory, or a status code on error. Since the caller happens to + * know the size of the file, it is passed in as SIZE. On success, + * this function sets *RETP and *LASTP, which may be passed to + * buf_append_data. + */ + +static int +buf_read_file (f, size, retp, lastp) + FILE *f; + long size; + struct buffer_data **retp; + struct buffer_data **lastp; +{ + int status; + + *retp = NULL; + *lastp = NULL; + + while (size > 0) + { + struct buffer_data *data; + int get; + + data = get_buffer_data (); + if (data == NULL) + { + status = -2; + goto error_return; + } + + if (*retp == NULL) + *retp = data; + else + (*lastp)->next = data; + data->next = NULL; + *lastp = data; + + data->bufp = data->text; + data->size = 0; + + if (size > BUFFER_DATA_SIZE) + get = BUFFER_DATA_SIZE; + else + get = size; + + errno = EIO; + if (fread (data->text, get, 1, f) != 1) + { + status = errno; + goto error_return; + } + + data->size += get; + size -= get; + } + + return 0; + + error_return: + if (*retp != NULL) + { + (*lastp)->next = free_buffer_data; + free_buffer_data = *retp; + } + return status; +} + +static int +buf_read_file_to_eof (f, retp, lastp) + FILE *f; + struct buffer_data **retp; + struct buffer_data **lastp; +{ + int status; + + *retp = NULL; + *lastp = NULL; + + while (!feof (f)) + { + struct buffer_data *data; + int get, nread; + + data = get_buffer_data (); + if (data == NULL) + { + status = -2; + goto error_return; + } + + if (*retp == NULL) + *retp = data; + else + (*lastp)->next = data; + data->next = NULL; + *lastp = data; + + data->bufp = data->text; + data->size = 0; + + get = BUFFER_DATA_SIZE; + + errno = EIO; + nread = fread (data->text, 1, get, f); + if (nread == 0 && !feof (f)) + { + status = errno; + goto error_return; + } + + data->size = nread; + } + + return 0; + + error_return: + if (*retp != NULL) + { + (*lastp)->next = free_buffer_data; + free_buffer_data = *retp; + } + return status; +} + +static int +buf_chain_length (buf) + struct buffer_data *buf; +{ + int size = 0; + while (buf) + { + size += buf->size; + buf = buf->next; + } + return size; +} + +/* + * Read an arbitrary amount of data from a file descriptor into an + * input buffer. The file descriptor will be in nonblocking mode, and + * we just grab what we can. Return 0 on success, or -1 on end of + * file, or -2 if out of memory, or an error code. If COUNTP is not + * NULL, *COUNTP is set to the number of bytes read. + */ + +static int +buf_input_data (buf, countp) + struct buffer *buf; + int *countp; +{ + if (buf->output) + abort (); + + if (countp != NULL) + *countp = 0; + + while (1) + { + int get; + int nbytes; + + if (buf->data == NULL + || (buf->last->bufp + buf->last->size + == buf->last->text + BUFFER_DATA_SIZE)) + { + struct buffer_data *data; + + data = get_buffer_data (); + if (data == NULL) + { + (*buf->memory_error) (buf); + return -2; + } + + if (buf->data == NULL) + buf->data = data; + else + buf->last->next = data; + data->next = NULL; + buf->last = data; + + data->bufp = data->text; + data->size = 0; + } + + get = ((buf->last->text + BUFFER_DATA_SIZE) + - (buf->last->bufp + buf->last->size)); + nbytes = read (buf->fd, buf->last->bufp + buf->last->size, get); + if (nbytes <= 0) + { + if (nbytes == 0) + { + /* + * This assumes that we are using POSIX or BSD style + * nonblocking I/O. On System V we will get a zero + * return if there is no data, even when not at EOF. + */ + return -1; + } + + if (errno == EAGAIN +#ifdef EWOULDBLOCK + || errno == EWOULDBLOCK +#endif + ) + return 0; + + return errno; + } + + buf->last->size += nbytes; + if (countp != NULL) + *countp += nbytes; + } + + /*NOTREACHED*/ +} + +/* + * Copy lines from an input buffer to an output buffer. This copies + * all complete lines (characters up to a newline) from INBUF to + * OUTBUF. Each line in OUTBUF is preceded by the character COMMAND + * and a space. + */ + +static void +buf_copy_lines (outbuf, inbuf, command) + struct buffer *outbuf; + struct buffer *inbuf; + int command; +{ + if (! outbuf->output || inbuf->output) + abort (); + + while (1) + { + struct buffer_data *data; + struct buffer_data *nldata; + char *nl; + int len; + + /* See if there is a newline in INBUF. */ + nldata = NULL; + nl = NULL; + for (data = inbuf->data; data != NULL; data = data->next) + { + nl = memchr (data->bufp, '\n', data->size); + if (nl != NULL) + { + nldata = data; + break; + } + } + + if (nldata == NULL) + { + /* There are no more lines in INBUF. */ + return; + } + + /* Put in the command. */ + buf_append_char (outbuf, command); + buf_append_char (outbuf, ' '); + + if (inbuf->data != nldata) + { + /* + * Simply move over all the buffers up to the one containing + * the newline. + */ + for (data = inbuf->data; data->next != nldata; data = data->next) + ; + data->next = NULL; + buf_append_data (outbuf, inbuf->data, data); + inbuf->data = nldata; + } + + /* + * If the newline is at the very end of the buffer, just move + * the buffer onto OUTBUF. Otherwise we must copy the data. + */ + len = nl + 1 - nldata->bufp; + if (len == nldata->size) + { + inbuf->data = nldata->next; + if (inbuf->data == NULL) + inbuf->last = NULL; + + nldata->next = NULL; + buf_append_data (outbuf, nldata, nldata); + } + else + { + buf_output (outbuf, nldata->bufp, len); + nldata->bufp += len; + nldata->size -= len; + } + } +} + +/* + * Copy counted data from one buffer to another. The count is an + * integer, host size, host byte order (it is only used across a + * pipe). If there is enough data, it should be moved over. If there + * is not enough data, it should remain on the original buffer. This + * returns the number of bytes it needs to see in order to actually + * copy something over. + */ + +static int +buf_copy_counted (outbuf, inbuf) + struct buffer *outbuf; + struct buffer *inbuf; +{ + if (! outbuf->output || inbuf->output) + abort (); + + while (1) + { + struct buffer_data *data; + int need; + union + { + char intbuf[sizeof (int)]; + int i; + } u; + char *intp; + int count; + struct buffer_data *start; + int startoff; + struct buffer_data *stop; + int stopwant; + + /* See if we have enough bytes to figure out the count. */ + need = sizeof (int); + intp = u.intbuf; + for (data = inbuf->data; data != NULL; data = data->next) + { + if (data->size >= need) + { + memcpy (intp, data->bufp, need); + break; + } + memcpy (intp, data->bufp, data->size); + intp += data->size; + need -= data->size; + } + if (data == NULL) + { + /* We don't have enough bytes to form an integer. */ + return need; + } + + count = u.i; + start = data; + startoff = need; + + /* + * We have an integer in COUNT. We have gotten all the data + * from INBUF in all buffers before START, and we have gotten + * STARTOFF bytes from START. See if we have enough bytes + * remaining in INBUF. + */ + need = count - (start->size - startoff); + if (need <= 0) + { + stop = start; + stopwant = count; + } + else + { + for (data = start->next; data != NULL; data = data->next) + { + if (need <= data->size) + break; + need -= data->size; + } + if (data == NULL) + { + /* We don't have enough bytes. */ + return need; + } + stop = data; + stopwant = need; + } + + /* + * We have enough bytes. Free any buffers in INBUF before + * START, and remove STARTOFF bytes from START, so that we can + * forget about STARTOFF. + */ + start->bufp += startoff; + start->size -= startoff; + + if (start->size == 0) + start = start->next; + + if (stop->size == stopwant) + { + stop = stop->next; + stopwant = 0; + } + + while (inbuf->data != start) + { + data = inbuf->data; + inbuf->data = data->next; + data->next = free_buffer_data; + free_buffer_data = data; + } + + /* + * We want to copy over the bytes from START through STOP. We + * only want STOPWANT bytes from STOP. + */ + + if (start != stop) + { + /* Attach the buffers from START through STOP to OUTBUF. */ + for (data = start; data->next != stop; data = data->next) + ; + inbuf->data = stop; + data->next = NULL; + buf_append_data (outbuf, start, data); + } + + if (stopwant > 0) + { + buf_output (outbuf, stop->bufp, stopwant); + stop->bufp += stopwant; + stop->size -= stopwant; + } + } + + /*NOTREACHED*/ +} + +/* While processing requests, this buffer accumulates data to be sent to + the client, and then once we are in do_cvs_command, we use it + for all the data to be sent. */ +static struct buffer buf_to_net; + +static void serve_questionable PROTO((char *)); + +static void +serve_questionable (arg) + char *arg; +{ + static int initted; + + if (!initted) + { + /* Pick up ignores from CVSROOTADM_IGNORE, $HOME/.cvsignore on server, + and CVSIGNORE on server. */ + ign_setup (); + initted = 1; + } + + if (dir_name == NULL) + { + buf_output0 (&buf_to_net, "E Protocol error: 'Directory' missing"); + return; + } + + if (!ign_name (arg)) + { + char *update_dir; + + buf_output (&buf_to_net, "M ? ", 4); + update_dir = dir_name + strlen (server_temp_dir) + 1; + if (!(update_dir[0] == '.' && update_dir[1] == '\0')) + { + buf_output0 (&buf_to_net, update_dir); + buf_output (&buf_to_net, "/", 1); + } + buf_output0 (&buf_to_net, arg); + buf_output (&buf_to_net, "\n", 1); + } +} + +static void serve_case PROTO ((char *)); + +static void +serve_case (arg) + char *arg; +{ + ign_case = 1; +} + +static struct buffer protocol; + +/* This is the output which we are saving up to send to the server, in the + child process. We will push it through, via the `protocol' buffer, when + we have a complete line. */ +static struct buffer saved_output; +/* Likewise, but stuff which will go to stderr. */ +static struct buffer saved_outerr; + +static void +protocol_memory_error (buf) + struct buffer *buf; +{ + error (1, ENOMEM, "Virtual memory exhausted"); +} + +/* + * Process IDs of the subprocess, or negative if that subprocess + * does not exist. + */ +static pid_t command_pid; + +static void +outbuf_memory_error (buf) + struct buffer *buf; +{ + static const char msg[] = "E Fatal server error\n\ +error ENOMEM Virtual memory exhausted.\n"; + if (command_pid > 0) + kill (command_pid, SIGTERM); + + /* + * We have arranged things so that printing this now either will + * be legal, or the "E fatal error" line will get glommed onto the + * end of an existing "E" or "M" response. + */ + + /* If this gives an error, not much we could do. syslog() it? */ + write (STDOUT_FILENO, msg, sizeof (msg) - 1); + server_cleanup (0); + exit (EXIT_FAILURE); +} + +static void +input_memory_error (buf) + struct buffer *buf; +{ + outbuf_memory_error (buf); +} + +/* Execute COMMAND in a subprocess with the approriate funky things done. */ + +static struct fd_set_wrapper { fd_set fds; } command_fds_to_drain; +static int max_command_fd; + +#ifdef SERVER_FLOWCONTROL +static int flowcontrol_pipe[2]; +#endif /* SERVER_FLOWCONTROL */ + +static void +do_cvs_command (command) + int (*command) PROTO((int argc, char **argv)); +{ + /* + * The following file descriptors are set to -1 if that file is not + * currently open. + */ + + /* Data on these pipes is a series of '\n'-terminated lines. */ + int stdout_pipe[2]; + int stderr_pipe[2]; + + /* + * Data on this pipe is a series of counted (see buf_send_counted) + * packets. Each packet must be processed atomically (i.e. not + * interleaved with data from stdout_pipe or stderr_pipe). + */ + int protocol_pipe[2]; + + int dev_null_fd = -1; + + int errs; + + command_pid = -1; + stdout_pipe[0] = -1; + stdout_pipe[1] = -1; + stderr_pipe[0] = -1; + stderr_pipe[1] = -1; + protocol_pipe[0] = -1; + protocol_pipe[1] = -1; + + server_write_entries (); + + if (print_pending_error ()) + goto free_args_and_return; + + (void) server_notify (); + + /* + * We use a child process which actually does the operation. This + * is so we can intercept its standard output. Even if all of CVS + * were written to go to some special routine instead of writing + * to stdout or stderr, we would still need to do the same thing + * for the RCS commands. + */ + + if (pipe (stdout_pipe) < 0) + { + print_error (errno); + goto error_exit; + } + if (pipe (stderr_pipe) < 0) + { + print_error (errno); + goto error_exit; + } + if (pipe (protocol_pipe) < 0) + { + print_error (errno); + goto error_exit; + } +#ifdef SERVER_FLOWCONTROL + if (pipe (flowcontrol_pipe) < 0) + { + print_error (errno); + goto error_exit; + } + set_nonblock_fd (flowcontrol_pipe[0]); + set_nonblock_fd (flowcontrol_pipe[1]); +#endif /* SERVER_FLOWCONTROL */ + + dev_null_fd = open ("/dev/null", O_RDONLY); + if (dev_null_fd < 0) + { + print_error (errno); + goto error_exit; + } + + /* Don't use vfork; we're not going to exec(). */ + command_pid = fork (); + if (command_pid < 0) + { + print_error (errno); + goto error_exit; + } + if (command_pid == 0) + { + int exitstatus; + + /* Since we're in the child, and the parent is going to take + care of packaging up our error messages, we can clear this + flag. */ + error_use_protocol = 0; + + protocol.data = protocol.last = NULL; + protocol.fd = protocol_pipe[1]; + protocol.output = 1; + protocol.nonblocking = 0; + protocol.memory_error = protocol_memory_error; + + saved_output.data = saved_output.last = NULL; + saved_output.fd = -1; + saved_output.output = 0; + saved_output.nonblocking = 0; + saved_output.memory_error = protocol_memory_error; + saved_outerr = saved_output; + + if (dup2 (dev_null_fd, STDIN_FILENO) < 0) + error (1, errno, "can't set up pipes"); + if (dup2 (stdout_pipe[1], STDOUT_FILENO) < 0) + error (1, errno, "can't set up pipes"); + if (dup2 (stderr_pipe[1], STDERR_FILENO) < 0) + error (1, errno, "can't set up pipes"); + close (stdout_pipe[0]); + close (stderr_pipe[0]); + close (protocol_pipe[0]); +#ifdef SERVER_FLOWCONTROL + close (flowcontrol_pipe[1]); +#endif /* SERVER_FLOWCONTROL */ + + /* + * Set this in .bashrc if you want to give yourself time to attach + * to the subprocess with a debugger. + */ + if (getenv ("CVS_SERVER_SLEEP")) + { + int secs = atoi (getenv ("CVS_SERVER_SLEEP")); + sleep (secs); + } + + exitstatus = (*command) (argument_count, argument_vector); + + /* + * When we exit, that will close the pipes, giving an EOF to + * the parent. + */ + exit (exitstatus); + } + + /* OK, sit around getting all the input from the child. */ + { + struct buffer stdoutbuf; + struct buffer stderrbuf; + struct buffer protocol_inbuf; + /* Number of file descriptors to check in select (). */ + int num_to_check; + int count_needed = 0; +#ifdef SERVER_FLOWCONTROL + int have_flowcontrolled = 0; +#endif /* SERVER_FLOWCONTROL */ + + FD_ZERO (&command_fds_to_drain.fds); + num_to_check = stdout_pipe[0]; + FD_SET (stdout_pipe[0], &command_fds_to_drain.fds); + if (stderr_pipe[0] > num_to_check) + num_to_check = stderr_pipe[0]; + FD_SET (stderr_pipe[0], &command_fds_to_drain.fds); + if (protocol_pipe[0] > num_to_check) + num_to_check = protocol_pipe[0]; + FD_SET (protocol_pipe[0], &command_fds_to_drain.fds); + if (STDOUT_FILENO > num_to_check) + num_to_check = STDOUT_FILENO; + max_command_fd = num_to_check; + /* + * File descriptors are numbered from 0, so num_to_check needs to + * be one larger than the largest descriptor. + */ + ++num_to_check; + if (num_to_check > FD_SETSIZE) + { + printf ("E internal error: FD_SETSIZE not big enough.\nerror \n"); + goto error_exit; + } + + stdoutbuf.data = stdoutbuf.last = NULL; + stdoutbuf.fd = stdout_pipe[0]; + stdoutbuf.output = 0; + stdoutbuf.nonblocking = 0; + stdoutbuf.memory_error = input_memory_error; + + stderrbuf.data = stderrbuf.last = NULL; + stderrbuf.fd = stderr_pipe[0]; + stderrbuf.output = 0; + stderrbuf.nonblocking = 0; + stderrbuf.memory_error = input_memory_error; + + protocol_inbuf.data = protocol_inbuf.last = NULL; + protocol_inbuf.fd = protocol_pipe[0]; + protocol_inbuf.output = 0; + protocol_inbuf.nonblocking = 0; + protocol_inbuf.memory_error = input_memory_error; + + set_nonblock (&buf_to_net); + set_nonblock (&stdoutbuf); + set_nonblock (&stderrbuf); + set_nonblock (&protocol_inbuf); + + if (close (stdout_pipe[1]) < 0) + { + print_error (errno); + goto error_exit; + } + stdout_pipe[1] = -1; + + if (close (stderr_pipe[1]) < 0) + { + print_error (errno); + goto error_exit; + } + stderr_pipe[1] = -1; + + if (close (protocol_pipe[1]) < 0) + { + print_error (errno); + goto error_exit; + } + protocol_pipe[1] = -1; + +#ifdef SERVER_FLOWCONTROL + if (close (flowcontrol_pipe[0]) < 0) + { + print_error (errno); + goto error_exit; + } + flowcontrol_pipe[0] = -1; +#endif /* SERVER_FLOWCONTROL */ + + if (close (dev_null_fd) < 0) + { + print_error (errno); + goto error_exit; + } + dev_null_fd = -1; + + while (stdout_pipe[0] >= 0 + || stderr_pipe[0] >= 0 + || protocol_pipe[0] >= 0) + { + fd_set readfds; + fd_set writefds; + int numfds; +#ifdef SERVER_FLOWCONTROL + int bufmemsize; + + /* + * See if we are swamping the remote client and filling our VM. + * Tell child to hold off if we do. + */ + bufmemsize = buf_count_mem (&buf_to_net); + if (!have_flowcontrolled && (bufmemsize > SERVER_HI_WATER)) + { + if (write(flowcontrol_pipe[1], "S", 1) == 1) + have_flowcontrolled = 1; + } + else if (have_flowcontrolled && (bufmemsize < SERVER_LO_WATER)) + { + if (write(flowcontrol_pipe[1], "G", 1) == 1) + have_flowcontrolled = 0; + } +#endif /* SERVER_FLOWCONTROL */ + + FD_ZERO (&readfds); + FD_ZERO (&writefds); + if (! buf_empty_p (&buf_to_net)) + FD_SET (STDOUT_FILENO, &writefds); + + if (stdout_pipe[0] >= 0) + { + FD_SET (stdout_pipe[0], &readfds); + } + if (stderr_pipe[0] >= 0) + { + FD_SET (stderr_pipe[0], &readfds); + } + if (protocol_pipe[0] >= 0) + { + FD_SET (protocol_pipe[0], &readfds); + } + + do { + /* This used to select on exceptions too, but as far + as I know there was never any reason to do that and + SCO doesn't let you select on exceptions on pipes. */ + numfds = select (num_to_check, &readfds, &writefds, + (fd_set *)0, (struct timeval *)NULL); + if (numfds < 0 + && errno != EINTR) + { + print_error (errno); + goto error_exit; + } + } while (numfds < 0); + + if (FD_ISSET (STDOUT_FILENO, &writefds)) + { + /* What should we do with errors? syslog() them? */ + buf_send_output (&buf_to_net); + } + + if (stdout_pipe[0] >= 0 + && (FD_ISSET (stdout_pipe[0], &readfds))) + { + int status; + + status = buf_input_data (&stdoutbuf, (int *) NULL); + + buf_copy_lines (&buf_to_net, &stdoutbuf, 'M'); + + if (status == -1) + stdout_pipe[0] = -1; + else if (status > 0) + { + print_error (status); + goto error_exit; + } + + /* What should we do with errors? syslog() them? */ + buf_send_output (&buf_to_net); + } + + if (stderr_pipe[0] >= 0 + && (FD_ISSET (stderr_pipe[0], &readfds))) + { + int status; + + status = buf_input_data (&stderrbuf, (int *) NULL); + + buf_copy_lines (&buf_to_net, &stderrbuf, 'E'); + + if (status == -1) + stderr_pipe[0] = -1; + else if (status > 0) + { + print_error (status); + goto error_exit; + } + + /* What should we do with errors? syslog() them? */ + buf_send_output (&buf_to_net); + } + + if (protocol_pipe[0] >= 0 + && (FD_ISSET (protocol_pipe[0], &readfds))) + { + int status; + int count_read; + + status = buf_input_data (&protocol_inbuf, &count_read); + + /* + * We only call buf_copy_counted if we have read + * enough bytes to make it worthwhile. This saves us + * from continually recounting the amount of data we + * have. + */ + count_needed -= count_read; + if (count_needed <= 0) + count_needed = buf_copy_counted (&buf_to_net, + &protocol_inbuf); + + if (status == -1) + protocol_pipe[0] = -1; + else if (status > 0) + { + print_error (status); + goto error_exit; + } + + /* What should we do with errors? syslog() them? */ + buf_send_output (&buf_to_net); + } + } + + /* + * OK, we've gotten EOF on all the pipes. If there is + * anything left on stdoutbuf or stderrbuf (this could only + * happen if there was no trailing newline), send it over. + */ + if (! buf_empty_p (&stdoutbuf)) + { + buf_append_char (&stdoutbuf, '\n'); + buf_copy_lines (&buf_to_net, &stdoutbuf, 'M'); + } + if (! buf_empty_p (&stderrbuf)) + { + buf_append_char (&stderrbuf, '\n'); + buf_copy_lines (&buf_to_net, &stderrbuf, 'E'); + } + if (! buf_empty_p (&protocol_inbuf)) + buf_output0 (&buf_to_net, + "E Protocol error: uncounted data discarded\n"); + + errs = 0; + + while (command_pid > 0) + { + int status; + pid_t waited_pid; + waited_pid = waitpid (command_pid, &status, 0); + if (waited_pid < 0) + { + /* + * Intentionally ignoring EINTR. Other errors + * "can't happen". + */ + continue; + } + + if (WIFEXITED (status)) + errs += WEXITSTATUS (status); + else + { + int sig = WTERMSIG (status); + /* + * This is really evil, because signals might be numbered + * differently on the two systems. We should be using + * signal names (either of the "Terminated" or the "SIGTERM" + * variety). But cvs doesn't currently use libiberty...we + * could roll our own.... FIXME. + */ + printf ("E Terminated with fatal signal %d\n", sig); + + /* Test for a core dump. Is this portable? */ + if (status & 0x80) + { + printf ("E Core dumped; preserving %s on server.\n\ +E CVS locks may need cleaning up.\n", + server_temp_dir); + dont_delete_temp = 1; + } + ++errs; + } + if (waited_pid == command_pid) + command_pid = -1; + } + + /* + * OK, we've waited for the child. By now all CVS locks are free + * and it's OK to block on the network. + */ + set_block (&buf_to_net); + buf_send_output (&buf_to_net); + } + + if (errs) + /* We will have printed an error message already. */ + printf ("error \n"); + else + printf ("ok\n"); + goto free_args_and_return; + + error_exit: + if (command_pid > 0) + kill (command_pid, SIGTERM); + + while (command_pid > 0) + { + pid_t waited_pid; + waited_pid = waitpid (command_pid, (int *) 0, 0); + if (waited_pid < 0 && errno == EINTR) + continue; + if (waited_pid == command_pid) + command_pid = -1; + } + + close (dev_null_fd); + close (protocol_pipe[0]); + close (protocol_pipe[1]); + close (stderr_pipe[0]); + close (stderr_pipe[1]); + close (stdout_pipe[0]); + close (stdout_pipe[1]); + + free_args_and_return: + /* Now free the arguments. */ + { + /* argument_vector[0] is a dummy argument, we don't mess with it. */ + char **cp; + for (cp = argument_vector + 1; + cp < argument_vector + argument_count; + ++cp) + free (*cp); + + argument_count = 1; + } + return; +} + +#ifdef SERVER_FLOWCONTROL +/* + * Called by the child at convenient points in the server's execution for + * the server child to block.. ie: when it has no locks active. + */ +void +server_pause_check() +{ + int paused = 0; + char buf[1]; + + while (read (flowcontrol_pipe[0], buf, 1) == 1) + { + if (*buf == 'S') /* Stop */ + paused = 1; + else if (*buf == 'G') /* Go */ + paused = 0; + else + return; /* ??? */ + } + while (paused) { + int numfds, numtocheck; + fd_set fds; + + FD_ZERO (&fds); + FD_SET (flowcontrol_pipe[0], &fds); + numtocheck = flowcontrol_pipe[0] + 1; + + do { + numfds = select (numtocheck, &fds, (fd_set *)0, + (fd_set *)0, (struct timeval *)NULL); + if (numfds < 0 + && errno != EINTR) + { + print_error (errno); + return; + } + } while (numfds < 0); + + if (FD_ISSET (flowcontrol_pipe[0], &fds)) + { + while (read (flowcontrol_pipe[0], buf, 1) == 1) + { + if (*buf == 'S') /* Stop */ + paused = 1; + else if (*buf == 'G') /* Go */ + paused = 0; + else + return; /* ??? */ + } + } + } +} +#endif /* SERVER_FLOWCONTROL */ + +static void output_dir PROTO((char *, char *)); + +static void +output_dir (update_dir, repository) + char *update_dir; + char *repository; +{ + if (use_dir_and_repos) + { + if (update_dir[0] == '\0') + buf_output0 (&protocol, "."); + else + buf_output0 (&protocol, update_dir); + buf_output0 (&protocol, "/\n"); + } + buf_output0 (&protocol, repository); + buf_output0 (&protocol, "/"); +} + +/* + * Entries line that we are squirreling away to send to the client when + * we are ready. + */ +static char *entries_line; + +/* + * File which has been Scratch_File'd, we are squirreling away that fact + * to inform the client when we are ready. + */ +static char *scratched_file; + +/* + * The scratched_file will need to be removed as well as having its entry + * removed. + */ +static int kill_scratched_file; + +void +server_register (name, version, timestamp, options, tag, date, conflict) + char *name; + char *version; + char *timestamp; + char *options; + char *tag; + char *date; + char *conflict; +{ + int len; + + if (options == NULL) + options = ""; + + if (trace) + { + (void) fprintf (stderr, + "%c-> server_register(%s, %s, %s, %s, %s, %s, %s)\n", + (server_active) ? 'S' : ' ', /* silly */ + name, version, timestamp, options, tag ? tag : "", + date ? date : "", conflict ? conflict : ""); + } + + if (entries_line != NULL) + { + /* + * If CVS decides to Register it more than once (which happens + * on "cvs update foo/foo.c" where foo and foo.c are already + * checked out), use the last of the entries lines Register'd. + */ + free (entries_line); + } + + /* + * I have reports of Scratch_Entry and Register both happening, in + * two different cases. Using the last one which happens is almost + * surely correct; I haven't tracked down why they both happen (or + * even verified that they are for the same file). + */ + if (scratched_file != NULL) + { + free (scratched_file); + scratched_file = NULL; + } + + len = (strlen (name) + strlen (version) + strlen (options) + 80); + if (tag) + len += strlen (tag); + if (date) + len += strlen (date); + + entries_line = xmalloc (len); + sprintf (entries_line, "/%s/%s/", name, version); + if (conflict != NULL) + { + strcat (entries_line, "+="); + } + strcat (entries_line, "/"); + strcat (entries_line, options); + strcat (entries_line, "/"); + if (tag != NULL) + { + strcat (entries_line, "T"); + strcat (entries_line, tag); + } + else if (date != NULL) + { + strcat (entries_line, "D"); + strcat (entries_line, date); + } +} + +void +server_scratch (fname) + char *fname; +{ + /* + * I have reports of Scratch_Entry and Register both happening, in + * two different cases. Using the last one which happens is almost + * surely correct; I haven't tracked down why they both happen (or + * even verified that they are for the same file). + */ + if (entries_line != NULL) + { + free (entries_line); + entries_line = NULL; + } + + if (scratched_file != NULL) + { + buf_output0 (&protocol, + "E CVS server internal error: duplicate Scratch_Entry\n"); + buf_send_counted (&protocol); + return; + } + scratched_file = xstrdup (fname); + kill_scratched_file = 1; +} + +void +server_scratch_entry_only () +{ + kill_scratched_file = 0; +} + +/* Print a new entries line, from a previous server_register. */ +static void +new_entries_line () +{ + if (entries_line) + { + buf_output0 (&protocol, entries_line); + buf_output (&protocol, "\n", 1); + } + else + /* Return the error message as the Entries line. */ + buf_output0 (&protocol, + "CVS server internal error: Register missing\n"); + free (entries_line); + entries_line = NULL; +} + +static void +serve_ci (arg) + char *arg; +{ + do_cvs_command (commit); +} + +static void +checked_in_response (file, update_dir, repository) + char *file; + char *update_dir; + char *repository; +{ + if (supported_response ("Mode")) + { + struct stat sb; + char *mode_string; + + if (stat (file, &sb) < 0) + { + /* Not clear to me why the file would fail to exist, but it + was happening somewhere in the testsuite. */ + if (!existence_error (errno)) + error (0, errno, "cannot stat %s", file); + } + else + { + buf_output0 (&protocol, "Mode "); + mode_string = mode_to_string (sb.st_mode); + buf_output0 (&protocol, mode_string); + buf_output0 (&protocol, "\n"); + free (mode_string); + } + } + + buf_output0 (&protocol, "Checked-in "); + output_dir (update_dir, repository); + buf_output0 (&protocol, file); + buf_output (&protocol, "\n", 1); + new_entries_line (); +} + +void +server_checked_in (file, update_dir, repository) + char *file; + char *update_dir; + char *repository; +{ + if (noexec) + return; + if (scratched_file != NULL && entries_line == NULL) + { + /* + * This happens if we are now doing a "cvs remove" after a previous + * "cvs add" (without a "cvs ci" in between). + */ + buf_output0 (&protocol, "Remove-entry "); + output_dir (update_dir, repository); + buf_output0 (&protocol, file); + buf_output (&protocol, "\n", 1); + free (scratched_file); + scratched_file = NULL; + } + else + { + checked_in_response (file, update_dir, repository); + } + buf_send_counted (&protocol); +} + +void +server_update_entries (file, update_dir, repository, updated) + char *file; + char *update_dir; + char *repository; + enum server_updated_arg4 updated; +{ + if (noexec) + return; + if (updated == SERVER_UPDATED) + checked_in_response (file, update_dir, repository); + else + { + if (!supported_response ("New-entry")) + return; + buf_output0 (&protocol, "New-entry "); + output_dir (update_dir, repository); + buf_output0 (&protocol, file); + buf_output (&protocol, "\n", 1); + new_entries_line (); + } + + buf_send_counted (&protocol); +} + +static void +serve_update (arg) + char *arg; +{ + do_cvs_command (update); +} + +static void +serve_diff (arg) + char *arg; +{ + do_cvs_command (diff); +} + +static void +serve_log (arg) + char *arg; +{ + do_cvs_command (cvslog); +} + +static void +serve_add (arg) + char *arg; +{ + do_cvs_command (add); +} + +static void +serve_remove (arg) + char *arg; +{ + do_cvs_command (cvsremove); +} + +static void +serve_status (arg) + char *arg; +{ + do_cvs_command (status); +} + +static void +serve_rdiff (arg) + char *arg; +{ + do_cvs_command (patch); +} + +static void +serve_tag (arg) + char *arg; +{ + do_cvs_command (tag); +} + +static void +serve_rtag (arg) + char *arg; +{ + do_cvs_command (rtag); +} + +static void +serve_import (arg) + char *arg; +{ + do_cvs_command (import); +} + +static void +serve_admin (arg) + char *arg; +{ + do_cvs_command (admin); +} + +static void +serve_history (arg) + char *arg; +{ + do_cvs_command (history); +} + +static void +serve_release (arg) + char *arg; +{ + do_cvs_command (release); +} + +static void serve_watch_on PROTO ((char *)); + +static void +serve_watch_on (arg) + char *arg; +{ + do_cvs_command (watch_on); +} + +static void serve_watch_off PROTO ((char *)); + +static void +serve_watch_off (arg) + char *arg; +{ + do_cvs_command (watch_off); +} + +static void serve_watch_add PROTO ((char *)); + +static void +serve_watch_add (arg) + char *arg; +{ + do_cvs_command (watch_add); +} + +static void serve_watch_remove PROTO ((char *)); + +static void +serve_watch_remove (arg) + char *arg; +{ + do_cvs_command (watch_remove); +} + +static void serve_watchers PROTO ((char *)); + +static void +serve_watchers (arg) + char *arg; +{ + do_cvs_command (watchers); +} + +static void serve_editors PROTO ((char *)); + +static void +serve_editors (arg) + char *arg; +{ + do_cvs_command (editors); +} + +static int noop PROTO ((int, char **)); + +static int +noop (argc, argv) + int argc; + char **argv; +{ + return 0; +} + +static void serve_noop PROTO ((char *)); + +static void +serve_noop (arg) + char *arg; +{ + do_cvs_command (noop); +} + +static void serve_init PROTO ((char *)); + +static void +serve_init (arg) + char *arg; +{ + CVSroot = malloc (strlen (arg) + 1); + if (CVSroot == NULL) + { + pending_error = ENOMEM; + return; + } + strcpy (CVSroot, arg); + + do_cvs_command (init); +} + +static void serve_annotate PROTO ((char *)); + +static void +serve_annotate (arg) + char *arg; +{ + do_cvs_command (annotate); +} + +static void +serve_co (arg) + char *arg; +{ + char *tempdir; + int status; + + if (print_pending_error ()) + return; + + if (!isdir (CVSADM)) + { + /* + * The client has not sent a "Repository" line. Check out + * into a pristine directory. + */ + tempdir = malloc (strlen (server_temp_dir) + 80); + if (tempdir == NULL) + { + printf ("E Out of memory\n"); + return; + } + strcpy (tempdir, server_temp_dir); + strcat (tempdir, "/checkout-dir"); + status = mkdir_p (tempdir); + if (status != 0 && status != EEXIST) + { + printf ("E Cannot create %s\n", tempdir); + print_error (errno); + free (tempdir); + return; + } + + if (chdir (tempdir) < 0) + { + printf ("E Cannot change to directory %s\n", tempdir); + print_error (errno); + free (tempdir); + return; + } + free (tempdir); + } + do_cvs_command (checkout); +} + +static void +serve_export (arg) + char *arg; +{ + /* Tell checkout() to behave like export not checkout. */ + command_name = "export"; + serve_co (arg); +} + +void +server_copy_file (file, update_dir, repository, newfile) + char *file; + char *update_dir; + char *repository; + char *newfile; +{ + if (!supported_response ("Copy-file")) + return; + buf_output0 (&protocol, "Copy-file "); + output_dir (update_dir, repository); + buf_output0 (&protocol, file); + buf_output0 (&protocol, "\n"); + buf_output0 (&protocol, newfile); + buf_output0 (&protocol, "\n"); +} + +void +server_updated (file, update_dir, repository, updated, file_info, checksum) + char *file; + char *update_dir; + char *repository; + enum server_updated_arg4 updated; + struct stat *file_info; + unsigned char *checksum; +{ + char *short_pathname; + + if (noexec) + return; + + short_pathname = xmalloc (strlen (update_dir) + strlen (file) + 10); + if (update_dir[0] == '\0') + strcpy (short_pathname, file); + else + sprintf (short_pathname, "%s/%s", update_dir, file); + + if (entries_line != NULL && scratched_file == NULL) + { + FILE *f; + struct stat sb; + struct buffer_data *list, *last; + unsigned long size; + char size_text[80]; + + if (stat (file, &sb) < 0) + { + if (existence_error (errno)) + { + /* + * If we have a sticky tag for a branch on which the + * file is dead, and cvs update the directory, it gets + * a T_CHECKOUT but no file. So in this case just + * forget the whole thing. + */ + free (entries_line); + entries_line = NULL; + goto done; + } + error (1, errno, "reading %s", short_pathname); + } + + if (checksum != NULL) + { + static int checksum_supported = -1; + + if (checksum_supported == -1) + { + checksum_supported = supported_response ("Checksum"); + } + + if (checksum_supported) + { + int i; + char buf[3]; + + buf_output0 (&protocol, "Checksum "); + for (i = 0; i < 16; i++) + { + sprintf (buf, "%02x", (unsigned int) checksum[i]); + buf_output0 (&protocol, buf); + } + buf_append_char (&protocol, '\n'); + } + } + + if (updated == SERVER_UPDATED) + buf_output0 (&protocol, "Updated "); + else if (updated == SERVER_MERGED) + buf_output0 (&protocol, "Merged "); + else if (updated == SERVER_PATCHED) + buf_output0 (&protocol, "Patched "); + else + abort (); + output_dir (update_dir, repository); + buf_output0 (&protocol, file); + buf_output (&protocol, "\n", 1); + + new_entries_line (); + + { + char *mode_string; + + /* FIXME: When we check out files the umask of the server + (set in .bashrc if rsh is in use, or set in main.c in + the kerberos case, I think) affects what mode we send, + and it shouldn't. */ + if (file_info != NULL) + mode_string = mode_to_string (file_info->st_mode); + else + mode_string = mode_to_string (sb.st_mode); + buf_output0 (&protocol, mode_string); + buf_output0 (&protocol, "\n"); + free (mode_string); + } + + list = last = NULL; + size = 0; + if (sb.st_size > 0) + { + /* Throughout this section we use binary mode to read the + file we are sending. The client handles any line ending + translation if necessary. */ + + if (gzip_level + /* + * For really tiny files, the gzip process startup + * time will outweigh the compression savings. This + * might be computable somehow; using 100 here is just + * a first approximation. + */ + && sb.st_size > 100) + { + int status, fd, gzip_status; + pid_t gzip_pid; + + fd = open (file, O_RDONLY | OPEN_BINARY, 0); + if (fd < 0) + error (1, errno, "reading %s", short_pathname); + fd = filter_through_gzip (fd, 1, gzip_level, &gzip_pid); + f = fdopen (fd, "rb"); + status = buf_read_file_to_eof (f, &list, &last); + size = buf_chain_length (list); + if (status == -2) + (*protocol.memory_error) (&protocol); + else if (status != 0) + error (1, ferror (f) ? errno : 0, "reading %s", + short_pathname); + if (fclose (f) == EOF) + error (1, errno, "reading %s", short_pathname); + if (waitpid (gzip_pid, &gzip_status, 0) == -1) + error (1, errno, "waiting for gzip process %ld", + (long) gzip_pid); + else if (gzip_status != 0) + error (1, 0, "gzip exited %d", gzip_status); + /* Prepending length with "z" is flag for using gzip here. */ + buf_output0 (&protocol, "z"); + } + else + { + long status; + + size = sb.st_size; + f = fopen (file, "rb"); + if (f == NULL) + error (1, errno, "reading %s", short_pathname); + status = buf_read_file (f, sb.st_size, &list, &last); + if (status == -2) + (*protocol.memory_error) (&protocol); + else if (status != 0) + error (1, ferror (f) ? errno : 0, "reading %s", + short_pathname); + if (fclose (f) == EOF) + error (1, errno, "reading %s", short_pathname); + } + } + + sprintf (size_text, "%lu\n", size); + buf_output0 (&protocol, size_text); + + buf_append_data (&protocol, list, last); + /* Note we only send a newline here if the file ended with one. */ + + /* + * Avoid using up too much disk space for temporary files. + * A file which does not exist indicates that the file is up-to-date, + * which is now the case. If this is SERVER_MERGED, the file is + * not up-to-date, and we indicate that by leaving the file there. + * I'm thinking of cases like "cvs update foo/foo.c foo". + */ + if ((updated == SERVER_UPDATED || updated == SERVER_PATCHED) + /* But if we are joining, we'll need the file when we call + join_file. */ + && !joining ()) + unlink (file); + } + else if (scratched_file != NULL && entries_line == NULL) + { + if (strcmp (scratched_file, file) != 0) + error (1, 0, + "CVS server internal error: `%s' vs. `%s' scratched", + scratched_file, + file); + free (scratched_file); + scratched_file = NULL; + + if (kill_scratched_file) + buf_output0 (&protocol, "Removed "); + else + buf_output0 (&protocol, "Remove-entry "); + output_dir (update_dir, repository); + buf_output0 (&protocol, file); + buf_output (&protocol, "\n", 1); + } + else if (scratched_file == NULL && entries_line == NULL) + { + /* + * This can happen with death support if we were processing + * a dead file in a checkout. + */ + } + else + error (1, 0, + "CVS server internal error: Register *and* Scratch_Entry.\n"); + buf_send_counted (&protocol); + done: + free (short_pathname); +} + +void +server_set_entstat (update_dir, repository) + char *update_dir; + char *repository; +{ + static int set_static_supported = -1; + if (set_static_supported == -1) + set_static_supported = supported_response ("Set-static-directory"); + if (!set_static_supported) return; + + buf_output0 (&protocol, "Set-static-directory "); + output_dir (update_dir, repository); + buf_output0 (&protocol, "\n"); + buf_send_counted (&protocol); +} + +void +server_clear_entstat (update_dir, repository) + char *update_dir; + char *repository; +{ + static int clear_static_supported = -1; + if (clear_static_supported == -1) + clear_static_supported = supported_response ("Clear-static-directory"); + if (!clear_static_supported) return; + + if (noexec) + return; + + buf_output0 (&protocol, "Clear-static-directory "); + output_dir (update_dir, repository); + buf_output0 (&protocol, "\n"); + buf_send_counted (&protocol); +} + +void +server_set_sticky (update_dir, repository, tag, date) + char *update_dir; + char *repository; + char *tag; + char *date; +{ + static int set_sticky_supported = -1; + if (set_sticky_supported == -1) + set_sticky_supported = supported_response ("Set-sticky"); + if (!set_sticky_supported) return; + + if (noexec) + return; + + if (tag == NULL && date == NULL) + { + buf_output0 (&protocol, "Clear-sticky "); + output_dir (update_dir, repository); + buf_output0 (&protocol, "\n"); + } + else + { + buf_output0 (&protocol, "Set-sticky "); + output_dir (update_dir, repository); + buf_output0 (&protocol, "\n"); + if (tag != NULL) + { + buf_output0 (&protocol, "T"); + buf_output0 (&protocol, tag); + } + else + { + buf_output0 (&protocol, "D"); + buf_output0 (&protocol, date); + } + buf_output0 (&protocol, "\n"); + } + buf_send_counted (&protocol); +} + +struct template_proc_data +{ + char *update_dir; + char *repository; +}; + +/* Here as a static until we get around to fixing Parse_Info to pass along + a void * for it. */ +static struct template_proc_data *tpd; + +static int +template_proc (repository, template) + char *repository; + char *template; +{ + FILE *fp; + char buf[1024]; + size_t n; + struct stat sb; + struct template_proc_data *data = tpd; + + if (!supported_response ("Template")) + /* Might want to warn the user that the rcsinfo feature won't work. */ + return 0; + buf_output0 (&protocol, "Template "); + output_dir (data->update_dir, data->repository); + buf_output0 (&protocol, "\n"); + + fp = fopen (template, "rb"); + if (fp == NULL) + { + error (0, errno, "Couldn't open rcsinfo template file %s", template); + return 1; + } + if (fstat (fileno (fp), &sb) < 0) + { + error (0, errno, "cannot stat rcsinfo template file %s", template); + return 1; + } + sprintf (buf, "%ld\n", (long) sb.st_size); + buf_output0 (&protocol, buf); + while (!feof (fp)) + { + n = fread (buf, 1, sizeof buf, fp); + buf_output (&protocol, buf, n); + if (ferror (fp)) + { + error (0, errno, "cannot read rcsinfo template file %s", template); + (void) fclose (fp); + return 1; + } + } + if (fclose (fp) < 0) + error (0, errno, "cannot close rcsinfo template file %s", template); + return 0; +} + +void +server_template (update_dir, repository) + char *update_dir; + char *repository; +{ + struct template_proc_data data; + data.update_dir = update_dir; + data.repository = repository; + tpd = &data; + (void) Parse_Info (CVSROOTADM_RCSINFO, repository, template_proc, 1); +} + +static void +serve_gzip_contents (arg) + char *arg; +{ + int level; + level = atoi (arg); + if (level == 0) + level = 6; + gzip_level = level; +} + +static void +serve_ignore (arg) + char *arg; +{ + /* + * Just ignore this command. This is used to support the + * update-patches command, which is not a real command, but a signal + * to the client that update will accept the -u argument. + */ +} + +static int +expand_proc (pargc, argv, where, mwhere, mfile, shorten, + local_specified, omodule, msg) + int *pargc; + char **argv; + char *where; + char *mwhere; + char *mfile; + int shorten; + int local_specified; + char *omodule; + char *msg; +{ + int i; + char *dir = argv[0]; + + /* If mwhere has been specified, the thing we're expanding is a + module -- just return its name so the client will ask for the + right thing later. If it is an alias or a real directory, + mwhere will not be set, so send out the appropriate + expansion. */ + + if (mwhere != NULL) + { + printf ("Module-expansion %s", mwhere); + if (mfile != NULL) + { + printf ("/%s", mfile); + } + printf ("\n"); + } + else + { + /* We may not need to do this anymore -- check the definition + of aliases before removing */ + if (*pargc == 1) + printf ("Module-expansion %s\n", dir); + else + for (i = 1; i < *pargc; ++i) + printf ("Module-expansion %s/%s\n", dir, argv[i]); + } + return 0; +} + +static void +serve_expand_modules (arg) + char *arg; +{ + int i; + int err; + DBM *db; + err = 0; + + /* + * FIXME: error handling is bogus; do_module can write to stdout and/or + * stderr and we're not using do_cvs_command. + */ + + server_expanding = 1; + db = open_module (); + for (i = 1; i < argument_count; i++) + err += do_module (db, argument_vector[i], + CHECKOUT, "Updating", expand_proc, + NULL, 0, 0, 0, + (char *) NULL); + close_module (db); + server_expanding = 0; + { + /* argument_vector[0] is a dummy argument, we don't mess with it. */ + char **cp; + for (cp = argument_vector + 1; + cp < argument_vector + argument_count; + ++cp) + free (*cp); + + argument_count = 1; + } + if (err) + /* We will have printed an error message already. */ + printf ("error \n"); + else + printf ("ok\n"); +} + +void +server_prog (dir, name, which) + char *dir; + char *name; + enum progs which; +{ + if (!supported_response ("Set-checkin-prog")) + { + printf ("E \ +warning: this client does not support -i or -u flags in the modules file.\n"); + return; + } + switch (which) + { + case PROG_CHECKIN: + printf ("Set-checkin-prog "); + break; + case PROG_UPDATE: + printf ("Set-update-prog "); + break; + } + printf ("%s\n%s\n", dir, name); +} + +static void +serve_checkin_prog (arg) + char *arg; +{ + FILE *f; + f = fopen (CVSADM_CIPROG, "w+"); + if (f == NULL) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_CIPROG)); + sprintf(pending_error_text, "E cannot open %s", CVSADM_CIPROG); + return; + } + if (fprintf (f, "%s\n", arg) < 0) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_CIPROG)); + sprintf(pending_error_text, "E cannot write to %s", CVSADM_CIPROG); + return; + } + if (fclose (f) == EOF) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_CIPROG)); + sprintf(pending_error_text, "E cannot close %s", CVSADM_CIPROG); + return; + } +} + +static void +serve_update_prog (arg) + char *arg; +{ + FILE *f; + f = fopen (CVSADM_UPROG, "w+"); + if (f == NULL) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_UPROG)); + sprintf(pending_error_text, "E cannot open %s", CVSADM_UPROG); + return; + } + if (fprintf (f, "%s\n", arg) < 0) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_UPROG)); + sprintf(pending_error_text, "E cannot write to %s", CVSADM_UPROG); + return; + } + if (fclose (f) == EOF) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_UPROG)); + sprintf(pending_error_text, "E cannot close %s", CVSADM_UPROG); + return; + } +} + +static void serve_valid_requests PROTO((char *arg)); + +#endif /* SERVER_SUPPORT */ +#if defined(SERVER_SUPPORT) || defined(CLIENT_SUPPORT) + +/* + * Parts of this table are shared with the client code, + * but the client doesn't need to know about the handler + * functions. + */ + +struct request requests[] = +{ +#ifdef SERVER_SUPPORT +#define REQ_LINE(n, f, s) {n, f, s} +#else +#define REQ_LINE(n, f, s) {n, s} +#endif + + REQ_LINE("Root", serve_root, rq_essential), + REQ_LINE("Valid-responses", serve_valid_responses, rq_essential), + REQ_LINE("valid-requests", serve_valid_requests, rq_essential), + REQ_LINE("Repository", serve_repository, rq_essential), + REQ_LINE("Directory", serve_directory, rq_optional), + REQ_LINE("Max-dotdot", serve_max_dotdot, rq_optional), + REQ_LINE("Static-directory", serve_static_directory, rq_optional), + REQ_LINE("Sticky", serve_sticky, rq_optional), + REQ_LINE("Checkin-prog", serve_checkin_prog, rq_optional), + REQ_LINE("Update-prog", serve_update_prog, rq_optional), + REQ_LINE("Entry", serve_entry, rq_essential), + REQ_LINE("Modified", serve_modified, rq_essential), + REQ_LINE("Lost", serve_lost, rq_optional), + REQ_LINE("UseUnchanged", serve_enable_unchanged, rq_enableme), + REQ_LINE("Unchanged", serve_unchanged, rq_optional), + REQ_LINE("Notify", serve_notify, rq_optional), + REQ_LINE("Questionable", serve_questionable, rq_optional), + REQ_LINE("Case", serve_case, rq_optional), + REQ_LINE("Argument", serve_argument, rq_essential), + REQ_LINE("Argumentx", serve_argumentx, rq_essential), + REQ_LINE("Global_option", serve_global_option, rq_optional), + REQ_LINE("Set", serve_set, rq_optional), + REQ_LINE("expand-modules", serve_expand_modules, rq_optional), + REQ_LINE("ci", serve_ci, rq_essential), + REQ_LINE("co", serve_co, rq_essential), + REQ_LINE("update", serve_update, rq_essential), + REQ_LINE("diff", serve_diff, rq_optional), + REQ_LINE("log", serve_log, rq_optional), + REQ_LINE("add", serve_add, rq_optional), + REQ_LINE("remove", serve_remove, rq_optional), + REQ_LINE("update-patches", serve_ignore, rq_optional), + REQ_LINE("gzip-file-contents", serve_gzip_contents, rq_optional), + REQ_LINE("status", serve_status, rq_optional), + REQ_LINE("rdiff", serve_rdiff, rq_optional), + REQ_LINE("tag", serve_tag, rq_optional), + REQ_LINE("rtag", serve_rtag, rq_optional), + REQ_LINE("import", serve_import, rq_optional), + REQ_LINE("admin", serve_admin, rq_optional), + REQ_LINE("export", serve_export, rq_optional), + REQ_LINE("history", serve_history, rq_optional), + REQ_LINE("release", serve_release, rq_optional), + REQ_LINE("watch-on", serve_watch_on, rq_optional), + REQ_LINE("watch-off", serve_watch_off, rq_optional), + REQ_LINE("watch-add", serve_watch_add, rq_optional), + REQ_LINE("watch-remove", serve_watch_remove, rq_optional), + REQ_LINE("watchers", serve_watchers, rq_optional), + REQ_LINE("editors", serve_editors, rq_optional), + REQ_LINE("init", serve_init, rq_optional), + REQ_LINE("annotate", serve_annotate, rq_optional), + REQ_LINE("noop", serve_noop, rq_optional), + REQ_LINE(NULL, NULL, rq_optional) + +#undef REQ_LINE +}; + +#endif /* SERVER_SUPPORT or CLIENT_SUPPORT */ +#ifdef SERVER_SUPPORT + +static void +serve_valid_requests (arg) + char *arg; +{ + struct request *rq; + if (print_pending_error ()) + return; + printf ("Valid-requests"); + for (rq = requests; rq->name != NULL; rq++) + if (rq->func != NULL) + printf (" %s", rq->name); + printf ("\nok\n"); +} + +#ifdef sun +/* + * Delete temporary files. SIG is the signal making this happen, or + * 0 if not called as a result of a signal. + */ +static int command_pid_is_dead; +static void wait_sig (sig) + int sig; +{ + int status; + pid_t r = wait (&status); + if (r == command_pid) + command_pid_is_dead++; +} +#endif + +void +server_cleanup (sig) + int sig; +{ + /* Do "rm -rf" on the temp directory. */ + int len; + char *cmd; + char *temp_dir; + + if (dont_delete_temp) + return; + + /* What a bogus kludge. This disgusting code makes all kinds of + assumptions about SunOS, and is only for a bug in that system. + So only enable it on Suns. */ +#ifdef sun + if (command_pid > 0) { + /* To avoid crashes on SunOS due to bugs in SunOS tmpfs + triggered by the use of rename() in RCS, wait for the + subprocess to die. Unfortunately, this means draining output + while waiting for it to unblock the signal we sent it. Yuck! */ + int status; + pid_t r; + + signal (SIGCHLD, wait_sig); + if (sig) + /* Perhaps SIGTERM would be more correct. But the child + process will delay the SIGINT delivery until its own + children have exited. */ + kill (command_pid, SIGINT); + /* The caller may also have sent a signal to command_pid, so + always try waiting. First, though, check and see if it's still + there.... */ + do_waitpid: + r = waitpid (command_pid, &status, WNOHANG); + if (r == 0) + ; + else if (r == command_pid) + command_pid_is_dead++; + else if (r == -1) + switch (errno) { + case ECHILD: + command_pid_is_dead++; + break; + case EINTR: + goto do_waitpid; + } + else + /* waitpid should always return one of the above values */ + abort (); + while (!command_pid_is_dead) { + struct timeval timeout; + struct fd_set_wrapper readfds; + char buf[100]; + int i; + + /* Use a non-zero timeout to avoid eating up CPU cycles. */ + timeout.tv_sec = 2; + timeout.tv_usec = 0; + readfds = command_fds_to_drain; + switch (select (max_command_fd + 1, &readfds.fds, + (fd_set *)0, (fd_set *)0, + &timeout)) { + case -1: + if (errno != EINTR) + abort (); + case 0: + /* timeout */ + break; + case 1: + for (i = 0; i <= max_command_fd; i++) + { + if (!FD_ISSET (i, &readfds.fds)) + continue; + /* this fd is non-blocking */ + while (read (i, buf, sizeof (buf)) >= 1) + ; + } + break; + default: + abort (); + } + } + } +#endif + + /* This might be set by the user in ~/.bashrc, ~/.cshrc, etc. */ + temp_dir = getenv ("TMPDIR"); + if (temp_dir == NULL || temp_dir[0] == '\0') + temp_dir = "/tmp"; + chdir(temp_dir); + + len = strlen (server_temp_dir) + 80; + cmd = malloc (len); + if (cmd == NULL) + { + printf ("E Cannot delete %s on server; out of memory\n", + server_temp_dir); + return; + } + sprintf (cmd, "rm -rf %s", server_temp_dir); + system (cmd); + free (cmd); +} + +int server_active = 0; +int server_expanding = 0; + +int +server (argc, argv) + int argc; + char **argv; +{ + if (argc == -1) + { + static const char *const msg[] = + { + "Usage: %s %s\n", + " Normally invoked by a cvs client on a remote machine.\n", + NULL + }; + usage (msg); + } + /* Ignore argc and argv. They might be from .cvsrc. */ + + /* Since we're in the server parent process, error should use the + protocol to report error messages. */ + error_use_protocol = 1; + + /* + * Put Rcsbin at the start of PATH, so that rcs programs can find + * themselves. + */ +#ifdef HAVE_PUTENV + if (Rcsbin != NULL && *Rcsbin) + { + char *p; + char *env; + + p = getenv ("PATH"); + if (p != NULL) + { + env = malloc (strlen (Rcsbin) + strlen (p) + sizeof "PATH=:"); + if (env != NULL) + sprintf (env, "PATH=%s:%s", Rcsbin, p); + } + else + { + env = malloc (strlen (Rcsbin) + sizeof "PATH="); + if (env != NULL) + sprintf (env, "PATH=%s", Rcsbin); + } + if (env == NULL) + { + printf ("E Fatal server error, aborting.\n\ +error ENOMEM Virtual memory exhausted.\n"); + exit (EXIT_FAILURE); + } + putenv (env); + } +#endif + + /* OK, now figure out where we stash our temporary files. */ + { + char *p; + + /* This might be set by the user in ~/.bashrc, ~/.cshrc, etc. */ + char *temp_dir = getenv ("TMPDIR"); + if (temp_dir == NULL || temp_dir[0] == '\0') + temp_dir = "/tmp"; + + server_temp_dir = malloc (strlen (temp_dir) + 80); + if (server_temp_dir == NULL) + { + /* + * Strictly speaking, we're not supposed to output anything + * now. But we're about to exit(), give it a try. + */ + printf ("E Fatal server error, aborting.\n\ +error ENOMEM Virtual memory exhausted.\n"); + exit (EXIT_FAILURE); + } + strcpy (server_temp_dir, temp_dir); + + /* Remove a trailing slash from TMPDIR if present. */ + p = server_temp_dir + strlen (server_temp_dir) - 1; + if (*p == '/') + *p = '\0'; + + /* + * I wanted to use cvs-serv/PID, but then you have to worry about + * the permissions on the cvs-serv directory being right. So + * use cvs-servPID. + */ + strcat (server_temp_dir, "/cvs-serv"); + + p = server_temp_dir + strlen (server_temp_dir); + sprintf (p, "%ld", (long) getpid ()); + } + + (void) SIG_register (SIGHUP, server_cleanup); + (void) SIG_register (SIGINT, server_cleanup); + (void) SIG_register (SIGQUIT, server_cleanup); + (void) SIG_register (SIGPIPE, server_cleanup); + (void) SIG_register (SIGTERM, server_cleanup); + + /* Now initialize our argument vector (for arguments from the client). */ + + /* Small for testing. */ + argument_vector_size = 1; + argument_vector = + (char **) malloc (argument_vector_size * sizeof (char *)); + if (argument_vector == NULL) + { + /* + * Strictly speaking, we're not supposed to output anything + * now. But we're about to exit(), give it a try. + */ + printf ("E Fatal server error, aborting.\n\ +error ENOMEM Virtual memory exhausted.\n"); + exit (EXIT_FAILURE); + } + + argument_count = 1; + argument_vector[0] = "Dummy argument 0"; + + buf_to_net.data = buf_to_net.last = NULL; + buf_to_net.fd = STDOUT_FILENO; + buf_to_net.output = 1; + buf_to_net.nonblocking = 0; + buf_to_net.memory_error = outbuf_memory_error; + + server_active = 1; + + while (1) + { + char *cmd, *orig_cmd; + struct request *rq; + + orig_cmd = cmd = read_line (stdin); + if (cmd == NULL) + break; + if (cmd == NO_MEM_ERROR) + { + printf ("E Fatal server error, aborting.\n\ +error ENOMEM Virtual memory exhausted.\n"); + break; + } + for (rq = requests; rq->name != NULL; ++rq) + if (strncmp (cmd, rq->name, strlen (rq->name)) == 0) + { + int len = strlen (rq->name); + if (cmd[len] == '\0') + cmd += len; + else if (cmd[len] == ' ') + cmd += len + 1; + else + /* + * The first len characters match, but it's a different + * command. e.g. the command is "cooperate" but we matched + * "co". + */ + continue; + (*rq->func) (cmd); + break; + } + if (rq->name == NULL) + { + if (!print_pending_error ()) + printf ("error unrecognized request `%s'\n", cmd); + } + free (orig_cmd); + } + server_cleanup (0); + return 0; +} + + +#ifdef AUTH_SERVER_SUPPORT + +extern char *crypt PROTO((const char *, const char *)); + +/* This was test code, which we may need again. */ +#if 0 + /* If we were invoked this way, then stdin comes from the + client and stdout/stderr writes to it. */ + int c; + while ((c = getc (stdin)) != EOF && c != '*') + { + printf ("%c", toupper (c)); + fflush (stdout); + } + exit (0); +#endif /* 1/0 */ + + +/* + * 0 means no entry found for this user. + * 1 means entry found and password matches. + * 2 means entry found, but password does not match. + */ +int +check_repository_password (username, password, repository, host_user_ptr) + char *username, *password, *repository, **host_user_ptr; +{ + int retval = 0; + FILE *fp; + char *filename; + char *linebuf; + int found_it = 0; + int namelen; + + filename = xmalloc (strlen (repository) + + 1 + + strlen ("CVSROOT") + + 1 + + strlen ("passwd") + + 1); + + strcpy (filename, repository); + strcat (filename, "/CVSROOT"); + strcat (filename, "/passwd"); + + fp = fopen (filename, "r"); + if (fp == NULL) + { + if (!existence_error (errno)) + error (0, errno, "cannot open %s", filename); + return 0; + } + + /* Look for a relevant line -- one with this user's name. */ + namelen = strlen (username); + while (1) + { + linebuf = read_line(fp); + if (linebuf == NULL) + { + free (linebuf); + break; + } + if (linebuf == NO_MEM_ERROR) + { + error (0, errno, "out of memory"); + break; + } + if ((strncmp (linebuf, username, namelen) == 0) + && (linebuf[namelen] == ':')) + { + found_it = 1; + break; + } + free (linebuf); + + } + if (ferror (fp)) + error (0, errno, "cannot read %s", filename); + if (fclose (fp) < 0) + error (0, errno, "cannot close %s", filename); + + /* If found_it != 0, then linebuf contains the information we need. */ + if (found_it) + { + char *found_password; + + strtok (linebuf, ":"); + found_password = strtok (NULL, ": \n"); + *host_user_ptr = strtok (NULL, ": \n"); + if (*host_user_ptr == NULL) *host_user_ptr = username; + if (strcmp (found_password, crypt (password, found_password)) == 0) + retval = 1; + else + retval = 2; + } + else + { + *host_user_ptr = NULL; + retval = 0; + } + + free (filename); + + return retval; +} + + +/* Return a hosting username if password matches, else NULL. */ +char * +check_password (username, password, repository) + char *username, *password, *repository; +{ + int rc; + char *host_user; + + /* First we see if this user has a password in the CVS-specific + password file. If so, that's enough to authenticate with. If + not, we'll check /etc/passwd. */ + + rc = check_repository_password (username, password, repository, + &host_user); + + if (rc == 1) + return host_user; + else if (rc == 2) + return 0; + else if (rc == 0) + { + /* No cvs password found, so try /etc/passwd. */ + + struct passwd *pw; + char *found_passwd; + + pw = getpwnam (username); + if (pw == NULL) + { + printf ("E Fatal error, aborting.\n\ +error 0 %s: no such user\n", username); + exit (EXIT_FAILURE); + } + found_passwd = pw->pw_passwd; + + if (found_passwd && *found_passwd) + return ((! strcmp (found_passwd, crypt (password, found_passwd))) + ? username : NULL); + else if (password && *password) + return username; + else + return NULL; + } + else + { + /* Something strange happened. We don't know what it was, but + we certainly won't grant authorization. */ + return NULL; + } +} + + +/* Read username and password from client (i.e., stdin). + If correct, then switch to run as that user and send an ACK to the + client via stdout, else send NACK and die. */ +void +authenticate_connection () +{ + char tmp[PATH_MAX]; + char repository[PATH_MAX]; + char username[PATH_MAX]; + char password[PATH_MAX]; + char *host_user; + char *descrambled_password; + struct passwd *pw; + int verify_and_exit = 0; + + /* The Authentication Protocol. Client sends: + * + * BEGIN AUTH REQUEST\n + * <REPOSITORY>\n + * <USERNAME>\n + * <PASSWORD>\n + * END AUTH REQUEST\n + * + * Server uses above information to authenticate, then sends + * + * I LOVE YOU\n + * + * if it grants access, else + * + * I HATE YOU\n + * + * if it denies access (and it exits if denying). + * + * When the client is "cvs login", the user does not desire actual + * repository access, but would like to confirm the password with + * the server. In this case, the start and stop strings are + * + * BEGIN VERIFICATION REQUEST\n + * + * and + * + * END VERIFICATION REQUEST\n + * + * On a verification request, the server's responses are the same + * (with the obvious semantics), but it exits immediately after + * sending the response in both cases. + * + * Why is the repository sent? Well, note that the actual + * client/server protocol can't start up until authentication is + * successful. But in order to perform authentication, the server + * needs to look up the password in the special CVS passwd file, + * before trying /etc/passwd. So the client transmits the + * repository as part of the "authentication protocol". The + * repository will be redundantly retransmitted later, but that's no + * big deal. + */ + + /* Since we're in the server parent process, error should use the + protocol to report error messages. */ + error_use_protocol = 1; + + /* Make sure the protocol starts off on the right foot... */ + fgets (tmp, PATH_MAX, stdin); + if (strcmp (tmp, "BEGIN VERIFICATION REQUEST\n") == 0) + verify_and_exit = 1; + else if (strcmp (tmp, "BEGIN AUTH REQUEST\n") != 0) + error (1, 0, "bad auth protocol start: %s", tmp); + + /* Get the three important pieces of information in order. */ + fgets (repository, PATH_MAX, stdin); + fgets (username, PATH_MAX, stdin); + fgets (password, PATH_MAX, stdin); + + /* Make them pure. */ + strip_trailing_newlines (repository); + strip_trailing_newlines (username); + strip_trailing_newlines (password); + + /* ... and make sure the protocol ends on the right foot. */ + fgets (tmp, PATH_MAX, stdin); + if (strcmp (tmp, + verify_and_exit ? + "END VERIFICATION REQUEST\n" : "END AUTH REQUEST\n") + != 0) + { + error (1, 0, "bad auth protocol end: %s", tmp); + } + + /* We need the real cleartext before we hash it. */ + descrambled_password = descramble (password); + host_user = check_password (username, descrambled_password, repository); + if (host_user) + { + printf ("I LOVE YOU\n"); + fflush (stdout); + memset (descrambled_password, 0, strlen (descrambled_password)); + free (descrambled_password); + } + else + { + printf ("I HATE YOU\n"); + fflush (stdout); + memset (descrambled_password, 0, strlen (descrambled_password)); + free (descrambled_password); + exit (EXIT_FAILURE); + } + + /* Don't go any farther if we're just responding to "cvs login". */ + if (verify_and_exit) + exit (0); + + /* Switch to run as this user. */ + pw = getpwnam (host_user); + if (pw == NULL) + { + error (1, 0, + "fatal error, aborting.\nerror 0 %s: no such user\n", + username); + } + +#if HAVE_INITGROUPS + initgroups (pw->pw_name, pw->pw_gid); +#endif /* HAVE_INITGROUPS */ + + setgid (pw->pw_gid); + setuid (pw->pw_uid); + /* Inhibit access by randoms. Don't want people randomly + changing our temporary tree before we check things in. */ + umask (077); + +#if HAVE_PUTENV + /* Set LOGNAME and USER in the environment, in case they are + already set to something else. */ + { + char *env; + + env = xmalloc (sizeof "LOGNAME=" + strlen (username)); + (void) sprintf (env, "LOGNAME=%s", username); + (void) putenv (env); + + env = xmalloc (sizeof "USER=" + strlen (username)); + (void) sprintf (env, "USER=%s", username); + (void) putenv (env); + } +#endif /* HAVE_PUTENV */ +} + +#endif /* AUTH_SERVER_SUPPORT */ + + +#endif /* SERVER_SUPPORT */ + +/* Output LEN bytes at STR. If LEN is zero, then output up to (not including) + the first '\0' byte. Should not be called from the server parent process + (yet at least, in the future it might be extended so that works). */ + +void +cvs_output (str, len) + char *str; + size_t len; +{ + if (len == 0) + len = strlen (str); + if (error_use_protocol) + /* Eventually we'll probably want to make it so this case works, + but for now, callers who want to output something with + error_use_protocol in effect can just printf the "M foo" + themselves. */ + abort (); +#ifdef SERVER_SUPPORT + if (server_active) + { + buf_output (&saved_output, str, len); + buf_copy_lines (&protocol, &saved_output, 'M'); + buf_send_counted (&protocol); + } + else +#endif + { + size_t written; + size_t to_write = len; + char *p = str; + + while (to_write > 0) + { + written = fwrite (str, 1, to_write, stdout); + if (written == 0) + break; + p += written; + to_write -= written; + } + } +} + +/* Like CVS_OUTPUT but output is for stderr not stdout. */ + +void +cvs_outerr (str, len) + char *str; + size_t len; +{ + if (len == 0) + len = strlen (str); + if (error_use_protocol) + /* Eventually we'll probably want to make it so this case works, + but for now, callers who want to output something with + error_use_protocol in effect can just printf the "E foo" + themselves. */ + abort (); +#ifdef SERVER_SUPPORT + if (server_active) + { + buf_output (&saved_outerr, str, len); + buf_copy_lines (&protocol, &saved_outerr, 'E'); + buf_send_counted (&protocol); + } + else +#endif + { + size_t written; + size_t to_write = len; + char *p = str; + + while (to_write > 0) + { + written = fwrite (str, 1, to_write, stderr); + if (written == 0) + break; + p += written; + to_write -= written; + } + } +} |