diff options
Diffstat (limited to 'gnu/usr.bin/cvs/cvs/server.c')
-rw-r--r-- | gnu/usr.bin/cvs/cvs/server.c | 3992 |
1 files changed, 0 insertions, 3992 deletions
diff --git a/gnu/usr.bin/cvs/cvs/server.c b/gnu/usr.bin/cvs/cvs/server.c deleted file mode 100644 index fbfca86..0000000 --- a/gnu/usr.bin/cvs/cvs/server.c +++ /dev/null @@ -1,3992 +0,0 @@ -#include "cvs.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 - - -/* 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?"); -} - -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 (1); - } - 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 void -dirswitch (dir, repos) - char *dir; - char *repos; -{ - char *dirname; - int status; - FILE *f; - - server_write_entries (); - - if (error_pending()) return; - - dirname = malloc (strlen (server_temp_dir) + strlen (dir) + 40); - if (dirname == NULL) - { - pending_error = ENOMEM; - return; - } - - strcpy (dirname, server_temp_dir); - strcat (dirname, "/"); - strcat (dirname, dir); - - status = mkdir_p (dirname); - if (status != 0 - && status != EEXIST) - { - pending_error = status; - pending_error_text = malloc (80 + strlen(dirname)); - sprintf(pending_error_text, "E cannot mkdir %s", dirname); - return; - } - if (chdir (dirname) < 0) - { - pending_error = errno; - pending_error_text = malloc (80 + strlen(dirname)); - sprintf(pending_error_text, "E cannot change to %s", dirname); - 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; - } - free (dirname); -} - -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 %d", 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); - } -} - -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; - } -} - -/* - * 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->output) - abort (); - - 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*/ -} - -static struct buffer protocol; - -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 (1); -} - -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; - - /* - * 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; - - 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 outbuf; - 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; - } - - outbuf.data = outbuf.last = NULL; - outbuf.fd = STDOUT_FILENO; - outbuf.output = 1; - outbuf.nonblocking = 0; - outbuf.memory_error = outbuf_memory_error; - - 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 (&outbuf); - 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 (&outbuf); - 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 (&outbuf)) - 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 (&outbuf); - } - - if (stdout_pipe[0] >= 0 - && (FD_ISSET (stdout_pipe[0], &readfds))) - { - int status; - - status = buf_input_data (&stdoutbuf, (int *) NULL); - - buf_copy_lines (&outbuf, &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 (&outbuf); - } - - if (stderr_pipe[0] >= 0 - && (FD_ISSET (stderr_pipe[0], &readfds))) - { - int status; - - status = buf_input_data (&stderrbuf, (int *) NULL); - - buf_copy_lines (&outbuf, &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 (&outbuf); - } - - 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 (&outbuf, &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 (&outbuf); - } - } - - /* - * 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 (&outbuf, &stdoutbuf, 'M'); - } - if (! buf_empty_p (&stderrbuf)) - { - buf_append_char (&stderrbuf, '\n'); - buf_copy_lines (&outbuf, &stderrbuf, 'E'); - } - if (! buf_empty_p (&protocol_inbuf)) - buf_output0 (&outbuf, - "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 (&outbuf); - buf_send_output (&outbuf); - } - - 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 (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 (options == NULL) - options = ""; - - 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); -} - -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 - { - buf_output0 (&protocol, "Checked-in "); - output_dir (update_dir, repository); - buf_output0 (&protocol, file); - buf_output (&protocol, "\n", 1); - new_entries_line (); - } - 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) - buf_output0 (&protocol, "Checked-in "); - 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_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) - { - 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, 0); - if (fd < 0) - error (1, errno, "reading %s", short_pathname); - fd = filter_through_gzip (fd, 1, gzip_level, &gzip_pid); - f = fdopen (fd, "r"); - 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 %d", 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, "r"); - 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); -} - -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\n", mwhere); - 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("Argument", serve_argument, rq_essential), - REQ_LINE("Argumentx", serve_argumentx, rq_essential), - REQ_LINE("Global_option", serve_global_option, 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(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 (1); - } - 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 (1); - } - 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, "%d", 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 (1); - } - - argument_count = 1; - argument_vector[0] = "Dummy argument 0"; - - 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 - -/* 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) - char *username, *password, *repository; -{ - int retval = 0; - FILE *fp; - char *filename; - char linebuf[MAXLINELEN]; - int found_it = 0, len; - - 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) - { - /* This is ok -- the cvs passwd file might not exist. */ - fclose (fp); - return 0; - } - - /* Look for a relevant line -- one with this user's name. */ - len = strlen (username); - while (fgets (linebuf, MAXPATHLEN - 1, fp)) - { - if ((strncmp (linebuf, username, len) == 0) - && (linebuf[len] == ':')) - { - found_it = 1; - break; - } - } - fclose (fp); - - /* If found_it != 0, then linebuf contains the information we need. */ - if (found_it) - { - char *found_password; - - strtok (linebuf, ":"); - found_password = strtok (NULL, ": \n"); - - if (strcmp (found_password, crypt (password, found_password)) == 0) - retval = 1; - else - retval = 2; - } - else - retval = 0; - - free (filename); - - return retval; -} - - -/* Return 1 if password matches, else 0. */ -int -check_password (username, password, repository) - char *username, *password, *repository; -{ - int rc; - - /* 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); - - if (rc == 1) - return 1; - 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 (1); - } - found_passwd = pw->pw_passwd; - - if (found_passwd && *found_passwd) - return (! strcmp (found_passwd, crypt (password, found_passwd))); - else if (password && *password) - return 1; - else - return 0; - } - else - { - /* Something strange happened. We don't know what it was, but - we certainly won't grant authorization. */ - return 0; - } -} - - -/* 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 () -{ - int len; - char tmp[PATH_MAX]; - char repository[PATH_MAX]; - char username[PATH_MAX]; - char password[PATH_MAX]; - char server_user[PATH_MAX]; - struct passwd *pw; - - /* 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). - * - * Note that the actual client/server protocol has not started up - * yet, because we haven't authenticated! Therefore, there are - * certain things we can't take for granted. For example, don't use - * error() because `error_use_protocol' has not yet been set by - * server(). - * - * We need to know where the repository is too, to look up the - * password in the special CVS passwd file before we try - * /etc/passwd. However, the repository is normally transmitted in - * the regular client/server protocol, which has not yet started, - * blah blah blah. This is why the client transmits the repository - * as part of the "authentication protocol". Thus, the repository - * will be redundantly retransmitted later, but that's no big deal. - */ - - /* Make sure the protocol starts off on the right foot... */ - fgets (tmp, PATH_MAX, stdin); - if (strcmp (tmp, "BEGIN AUTH REQUEST\n")) - { - printf ("error: bad auth protocol start: %s", tmp); - fflush (stdout); - exit (1); - } - - /* 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, "END AUTH REQUEST\n")) - { - printf ("error: bad auth protocol end: %s", tmp); - fflush (stdout); - exit (1); - } - - if (check_password (username, password, repository)) - { - printf ("I LOVE YOU\n"); - fflush (stdout); - } - else - { - printf ("I HATE YOU\n"); - fflush (stdout); - exit (1); - } - - /* Do everything that kerberos did. */ - pw = getpwnam (username); - if (pw == NULL) - { - printf ("E Fatal error, aborting.\n" - "error 0 %s: no such user\n", username); - exit (1); - } - - initgroups (pw->pw_name, pw->pw_gid); - 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 */ - |