summaryrefslogtreecommitdiffstats
path: root/contrib/cvs/src/client.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/cvs/src/client.c')
-rw-r--r--contrib/cvs/src/client.c5948
1 files changed, 5948 insertions, 0 deletions
diff --git a/contrib/cvs/src/client.c b/contrib/cvs/src/client.c
new file mode 100644
index 0000000..1c8a5d0
--- /dev/null
+++ b/contrib/cvs/src/client.c
@@ -0,0 +1,5948 @@
+/* CVS client-related stuff.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details. */
+
+/*
+ * $FreeBSD$
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#include <assert.h>
+#include "cvs.h"
+#include "getline.h"
+#include "edit.h"
+#include "buffer.h"
+#include "savecwd.h"
+
+#ifdef CLIENT_SUPPORT
+
+# include "md5.h"
+
+# if defined(AUTH_CLIENT_SUPPORT) || defined(HAVE_KERBEROS) || defined(HAVE_GSSAPI) || defined(SOCK_ERRNO) || defined(SOCK_STRERROR)
+# ifdef HAVE_WINSOCK_H
+# include <winsock.h>
+# else /* No winsock.h */
+# include <sys/socket.h>
+# include <netinet/in.h>
+# include <arpa/inet.h>
+# include <netdb.h>
+# endif /* No winsock.h */
+# endif
+
+/* If SOCK_ERRNO is defined, then send()/recv() and other socket calls
+ do not set errno, but that this macro should be used to obtain an
+ error code. This probably doesn't make sense unless
+ NO_SOCKET_TO_FD is also defined. */
+# ifndef SOCK_ERRNO
+# define SOCK_ERRNO errno
+# endif
+
+/* If SOCK_STRERROR is defined, then the error codes returned by
+ socket operations are not known to strerror, and this macro must be
+ used instead to convert those error codes to strings. */
+# ifndef SOCK_STRERROR
+# define SOCK_STRERROR strerror
+
+# if STDC_HEADERS
+# include <string.h>
+# endif
+
+# ifndef strerror
+extern char *strerror ();
+# endif
+# endif /* ! SOCK_STRERROR */
+
+# if HAVE_KERBEROS
+
+# include <krb.h>
+
+extern char *krb_realmofhost ();
+# ifndef HAVE_KRB_GET_ERR_TEXT
+# define krb_get_err_text(status) krb_err_txt[status]
+# endif /* HAVE_KRB_GET_ERR_TEXT */
+
+/* Information we need if we are going to use Kerberos encryption. */
+static C_Block kblock;
+static Key_schedule sched;
+
+# endif /* HAVE_KERBEROS */
+
+# ifdef HAVE_GSSAPI
+
+# include "xgssapi.h"
+
+/* This is needed for GSSAPI encryption. */
+static gss_ctx_id_t gcontext;
+
+static int connect_to_gserver PROTO((cvsroot_t *, int, struct hostent *));
+
+# endif /* HAVE_GSSAPI */
+
+
+
+/* Keep track of any paths we are sending for Max-dotdot so that we can verify
+ * that uplevel paths coming back form the server are valid.
+ *
+ * FIXME: The correct way to do this is probably provide some sort of virtual
+ * path map on the client side. This would be generic enough to be applied to
+ * absolute paths supplied by the user too.
+ */
+static List *uppaths = NULL;
+
+
+
+static void add_prune_candidate PROTO((const char *));
+
+/* All the commands. */
+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));
+
+/* All the response handling functions. */
+static void handle_ok PROTO((char *, int));
+static void handle_error PROTO((char *, int));
+static void handle_valid_requests PROTO((char *, int));
+static void handle_checked_in PROTO((char *, int));
+static void handle_new_entry PROTO((char *, int));
+static void handle_checksum PROTO((char *, int));
+static void handle_copy_file PROTO((char *, int));
+static void handle_updated PROTO((char *, int));
+static void handle_merged PROTO((char *, int));
+static void handle_patched PROTO((char *, int));
+static void handle_rcs_diff PROTO((char *, int));
+static void handle_removed PROTO((char *, int));
+static void handle_remove_entry PROTO((char *, int));
+static void handle_set_static_directory PROTO((char *, int));
+static void handle_clear_static_directory PROTO((char *, int));
+static void handle_set_sticky PROTO((char *, int));
+static void handle_clear_sticky PROTO((char *, int));
+static void handle_module_expansion PROTO((char *, int));
+static void handle_wrapper_rcs_option PROTO((char *, int));
+static void handle_m PROTO((char *, int));
+static void handle_e PROTO((char *, int));
+static void handle_f PROTO((char *, int));
+static void handle_notified PROTO((char *, int));
+
+static size_t try_read_from_server PROTO ((char *, size_t));
+
+static void auth_server PROTO ((cvsroot_t *, struct buffer *, struct buffer *,
+ int, int, struct hostent *));
+
+/* We need to keep track of the list of directories we've sent to the
+ server. This list, along with the current CVSROOT, will help us
+ decide which command-line arguments to send. */
+List *dirs_sent_to_server = NULL;
+
+static int is_arg_a_parent_or_listed_dir PROTO((Node *, void *));
+
+static int
+is_arg_a_parent_or_listed_dir (n, d)
+ Node *n;
+ void *d;
+{
+ char *directory = n->key; /* name of the dir sent to server */
+ char *this_argv_elem = xstrdup (d); /* this argv element */
+ int retval;
+
+ /* Say we should send this argument if the argument matches the
+ beginning of a directory name sent to the server. This way,
+ the server will know to start at the top of that directory
+ hierarchy and descend. */
+
+ strip_trailing_slashes (this_argv_elem);
+ if (strncmp (directory, this_argv_elem, strlen (this_argv_elem)) == 0)
+ retval = 1;
+ else
+ retval = 0;
+
+ free (this_argv_elem);
+ return retval;
+}
+
+static int arg_should_not_be_sent_to_server PROTO((char *));
+
+/* Return nonzero if this argument should not be sent to the
+ server. */
+
+static int
+arg_should_not_be_sent_to_server (arg)
+ char *arg;
+{
+ /* Decide if we should send this directory name to the server. We
+ should always send argv[i] if:
+
+ 1) the list of directories sent to the server is empty (as it
+ will be for checkout, etc.).
+
+ 2) the argument is "."
+
+ 3) the argument is a file in the cwd and the cwd is checked out
+ from the current root
+
+ 4) the argument lies within one of the paths in
+ dirs_sent_to_server.
+
+ */
+
+ if (list_isempty (dirs_sent_to_server))
+ return 0; /* always send it */
+
+ if (strcmp (arg, ".") == 0)
+ return 0; /* always send it */
+
+ /* We should send arg if it is one of the directories sent to the
+ server or the parent of one; this tells the server to descend
+ the hierarchy starting at this level. */
+ if (isdir (arg))
+ {
+ if (walklist (dirs_sent_to_server, is_arg_a_parent_or_listed_dir, arg))
+ return 0;
+
+ /* If arg wasn't a parent, we don't know anything about it (we
+ would have seen something related to it during the
+ send_files phase). Don't send it. */
+ return 1;
+ }
+
+ /* Try to decide whether we should send arg to the server by
+ checking the contents of the corresponding CVSADM directory. */
+ {
+ char *t, *root_string;
+ cvsroot_t *this_root = NULL;
+
+ /* Calculate "dirname arg" */
+ for (t = arg + strlen (arg) - 1; t >= arg; t--)
+ {
+ if (ISDIRSEP(*t))
+ break;
+ }
+
+ /* Now we're either poiting to the beginning of the
+ string, or we found a path separator. */
+ if (t >= arg)
+ {
+ /* Found a path separator. */
+ char c = *t;
+ *t = '\0';
+
+ /* First, check to see if we sent this directory to the
+ server, because it takes less time than actually
+ opening the stuff in the CVSADM directory. */
+ if (walklist (dirs_sent_to_server, is_arg_a_parent_or_listed_dir,
+ arg))
+ {
+ *t = c; /* make sure to un-truncate the arg */
+ return 0;
+ }
+
+ /* Since we didn't find it in the list, check the CVSADM
+ files on disk. */
+ this_root = Name_Root (arg, (char *) NULL);
+ root_string = this_root->original;
+ *t = c;
+ }
+ else
+ {
+ /* We're at the beginning of the string. Look at the
+ CVSADM files in cwd. */
+ if (CVSroot_cmdline)
+ root_string = CVSroot_cmdline;
+ else
+ {
+ this_root = Name_Root ((char *) NULL, (char *) NULL);
+ root_string = this_root->original;
+ }
+ }
+
+ /* Now check the value for root. */
+ if (CVSroot_cmdline == NULL &&
+ root_string && current_parsed_root
+ && (strcmp (root_string, current_parsed_root->original) != 0))
+ {
+ /* Don't send this, since the CVSROOTs don't match. */
+ if (this_root) free_cvsroot_t (this_root);
+ return 1;
+ }
+ if (this_root) free_cvsroot_t (this_root);
+ }
+
+ /* OK, let's send it. */
+ return 0;
+}
+
+
+
+#endif /* CLIENT_SUPPORT */
+
+
+
+#if defined(CLIENT_SUPPORT) || defined(SERVER_SUPPORT)
+
+/* Shared with server. */
+
+/*
+ * Return a malloc'd, '\0'-terminated string
+ * corresponding to the mode in SB.
+ */
+char *
+#ifdef __STDC__
+mode_to_string (mode_t mode)
+#else /* ! __STDC__ */
+mode_to_string (mode)
+ mode_t mode;
+#endif /* __STDC__ */
+{
+ char buf[18], u[4], g[4], o[4];
+ int i;
+
+ i = 0;
+ if (mode & S_IRUSR) u[i++] = 'r';
+ if (mode & S_IWUSR) u[i++] = 'w';
+ if (mode & S_IXUSR) u[i++] = 'x';
+ u[i] = '\0';
+
+ i = 0;
+ if (mode & S_IRGRP) g[i++] = 'r';
+ if (mode & S_IWGRP) g[i++] = 'w';
+ if (mode & S_IXGRP) g[i++] = 'x';
+ g[i] = '\0';
+
+ i = 0;
+ if (mode & S_IROTH) o[i++] = 'r';
+ if (mode & S_IWOTH) o[i++] = 'w';
+ if (mode & S_IXOTH) o[i++] = 'x';
+ o[i] = '\0';
+
+ sprintf(buf, "u=%s,g=%s,o=%s", u, g, o);
+ return xstrdup(buf);
+}
+
+/*
+ * Change mode of FILENAME to MODE_STRING.
+ * Returns 0 for success or errno code.
+ * If RESPECT_UMASK is set, then honor the umask.
+ */
+int
+change_mode (filename, mode_string, respect_umask)
+ char *filename;
+ char *mode_string;
+ int respect_umask;
+{
+#ifdef CHMOD_BROKEN
+ char *p;
+ int writeable = 0;
+
+ /* We can only distinguish between
+ 1) readable
+ 2) writeable
+ 3) Picasso's "Blue Period"
+ We handle the first two. */
+ p = mode_string;
+ while (*p != '\0')
+ {
+ if ((p[0] == 'u' || p[0] == 'g' || p[0] == 'o') && p[1] == '=')
+ {
+ char *q = p + 2;
+ while (*q != ',' && *q != '\0')
+ {
+ if (*q == 'w')
+ writeable = 1;
+ ++q;
+ }
+ }
+ /* Skip to the next field. */
+ while (*p != ',' && *p != '\0')
+ ++p;
+ if (*p == ',')
+ ++p;
+ }
+
+ /* xchmod honors the umask for us. In the !respect_umask case, we
+ don't try to cope with it (probably to handle that well, the server
+ needs to deal with modes in data structures, rather than via the
+ modes in temporary files). */
+ xchmod (filename, writeable);
+ return 0;
+
+#else /* ! CHMOD_BROKEN */
+
+ char *p;
+ mode_t mode = 0;
+ mode_t oumask;
+
+ p = mode_string;
+ while (*p != '\0')
+ {
+ if ((p[0] == 'u' || p[0] == 'g' || p[0] == 'o') && p[1] == '=')
+ {
+ int can_read = 0, can_write = 0, can_execute = 0;
+ char *q = p + 2;
+ while (*q != ',' && *q != '\0')
+ {
+ if (*q == 'r')
+ can_read = 1;
+ else if (*q == 'w')
+ can_write = 1;
+ else if (*q == 'x')
+ can_execute = 1;
+ ++q;
+ }
+ if (p[0] == 'u')
+ {
+ if (can_read)
+ mode |= S_IRUSR;
+ if (can_write)
+ mode |= S_IWUSR;
+ if (can_execute)
+ mode |= S_IXUSR;
+ }
+ else if (p[0] == 'g')
+ {
+ if (can_read)
+ mode |= S_IRGRP;
+ if (can_write)
+ mode |= S_IWGRP;
+ if (can_execute)
+ mode |= S_IXGRP;
+ }
+ else if (p[0] == 'o')
+ {
+ if (can_read)
+ mode |= S_IROTH;
+ if (can_write)
+ mode |= S_IWOTH;
+ if (can_execute)
+ mode |= S_IXOTH;
+ }
+ }
+ /* Skip to the next field. */
+ while (*p != ',' && *p != '\0')
+ ++p;
+ if (*p == ',')
+ ++p;
+ }
+
+ if (respect_umask)
+ {
+ oumask = umask (0);
+ (void) umask (oumask);
+ mode &= ~oumask;
+ }
+
+ if (chmod (filename, mode) < 0)
+ return errno;
+ return 0;
+#endif /* ! CHMOD_BROKEN */
+}
+
+#endif /* CLIENT_SUPPORT or SERVER_SUPPORT */
+
+#ifdef CLIENT_SUPPORT
+
+int client_prune_dirs;
+
+static List *ignlist = (List *) NULL;
+
+/* Buffer to write to the server. */
+static struct buffer *to_server;
+
+/* Buffer used to read from the server. */
+static struct buffer *from_server;
+
+
+/* We want to be able to log data sent between us and the server. We
+ do it using log buffers. Each log buffer has another buffer which
+ handles the actual I/O, and a file to log information to.
+
+ This structure is the closure field of a log buffer. */
+
+struct log_buffer
+{
+ /* The underlying buffer. */
+ struct buffer *buf;
+ /* The file to log information to. */
+ FILE *log;
+};
+
+static struct buffer *log_buffer_initialize
+ PROTO((struct buffer *, FILE *, int, void (*) (struct buffer *)));
+static int log_buffer_input PROTO((void *, char *, int, int, int *));
+static int log_buffer_output PROTO((void *, const char *, int, int *));
+static int log_buffer_flush PROTO((void *));
+static int log_buffer_block PROTO((void *, int));
+static int log_buffer_shutdown PROTO((struct buffer *));
+
+/* Create a log buffer. */
+
+static struct buffer *
+log_buffer_initialize (buf, fp, input, memory)
+ struct buffer *buf;
+ FILE *fp;
+ int input;
+ void (*memory) PROTO((struct buffer *));
+{
+ struct log_buffer *n;
+
+ n = (struct log_buffer *) xmalloc (sizeof *n);
+ n->buf = buf;
+ n->log = fp;
+ return buf_initialize (input ? log_buffer_input : NULL,
+ input ? NULL : log_buffer_output,
+ input ? NULL : log_buffer_flush,
+ log_buffer_block,
+ log_buffer_shutdown,
+ memory,
+ n);
+}
+
+/* The input function for a log buffer. */
+
+static int
+log_buffer_input (closure, data, need, size, got)
+ void *closure;
+ char *data;
+ int need;
+ int size;
+ int *got;
+{
+ struct log_buffer *lb = (struct log_buffer *) closure;
+ int status;
+ size_t n_to_write;
+
+ if (lb->buf->input == NULL)
+ abort ();
+
+ status = (*lb->buf->input) (lb->buf->closure, data, need, size, got);
+ if (status != 0)
+ return status;
+
+ if (*got > 0)
+ {
+ n_to_write = *got;
+ if (fwrite (data, 1, n_to_write, lb->log) != n_to_write)
+ error (0, errno, "writing to log file");
+ }
+
+ return 0;
+}
+
+/* The output function for a log buffer. */
+
+static int
+log_buffer_output (closure, data, have, wrote)
+ void *closure;
+ const char *data;
+ int have;
+ int *wrote;
+{
+ struct log_buffer *lb = (struct log_buffer *) closure;
+ int status;
+ size_t n_to_write;
+
+ if (lb->buf->output == NULL)
+ abort ();
+
+ status = (*lb->buf->output) (lb->buf->closure, data, have, wrote);
+ if (status != 0)
+ return status;
+
+ if (*wrote > 0)
+ {
+ n_to_write = *wrote;
+ if (fwrite (data, 1, n_to_write, lb->log) != n_to_write)
+ error (0, errno, "writing to log file");
+ }
+
+ return 0;
+}
+
+/* The flush function for a log buffer. */
+
+static int
+log_buffer_flush (closure)
+ void *closure;
+{
+ struct log_buffer *lb = (struct log_buffer *) closure;
+
+ if (lb->buf->flush == NULL)
+ abort ();
+
+ /* We don't really have to flush the log file here, but doing it
+ will let tail -f on the log file show what is sent to the
+ network as it is sent. */
+ if (fflush (lb->log) != 0)
+ error (0, errno, "flushing log file");
+
+ return (*lb->buf->flush) (lb->buf->closure);
+}
+
+/* The block function for a log buffer. */
+
+static int
+log_buffer_block (closure, block)
+ void *closure;
+ int block;
+{
+ struct log_buffer *lb = (struct log_buffer *) closure;
+
+ if (block)
+ return set_block (lb->buf);
+ else
+ return set_nonblock (lb->buf);
+}
+
+/* The shutdown function for a log buffer. */
+
+static int
+log_buffer_shutdown (buf)
+ struct buffer *buf;
+{
+ struct log_buffer *lb = (struct log_buffer *) buf->closure;
+ int retval;
+
+ retval = buf_shutdown (lb->buf);
+ if (fclose (lb->log) < 0)
+ error (0, errno, "closing log file");
+ return retval;
+}
+
+#ifdef NO_SOCKET_TO_FD
+
+/* Under certain circumstances, we must communicate with the server
+ via a socket using send() and recv(). This is because under some
+ operating systems (OS/2 and Windows 95 come to mind), a socket
+ cannot be converted to a file descriptor -- it must be treated as a
+ socket and nothing else.
+
+ We may also need to deal with socket routine error codes differently
+ in these cases. This is handled through the SOCK_ERRNO and
+ SOCK_STRERROR macros. */
+
+/* These routines implement a buffer structure which uses send and
+ recv. The buffer is always in blocking mode so we don't implement
+ the block routine. */
+
+/* Note that it is important that these routines always handle errors
+ internally and never return a positive errno code, since it would in
+ general be impossible for the caller to know in general whether any
+ error code came from a socket routine (to decide whether to use
+ SOCK_STRERROR or simply strerror to print an error message). */
+
+/* We use an instance of this structure as the closure field. */
+
+struct socket_buffer
+{
+ /* The socket number. */
+ int socket;
+};
+
+static struct buffer *socket_buffer_initialize
+ PROTO ((int, int, void (*) (struct buffer *)));
+static int socket_buffer_input PROTO((void *, char *, int, int, int *));
+static int socket_buffer_output PROTO((void *, const char *, int, int *));
+static int socket_buffer_flush PROTO((void *));
+static int socket_buffer_shutdown PROTO((struct buffer *));
+
+
+
+/* Create a buffer based on a socket. */
+
+static struct buffer *
+socket_buffer_initialize (socket, input, memory)
+ int socket;
+ int input;
+ void (*memory) PROTO((struct buffer *));
+{
+ struct socket_buffer *n;
+
+ n = (struct socket_buffer *) xmalloc (sizeof *n);
+ n->socket = socket;
+ return buf_initialize (input ? socket_buffer_input : NULL,
+ input ? NULL : socket_buffer_output,
+ input ? NULL : socket_buffer_flush,
+ (int (*) PROTO((void *, int))) NULL,
+ socket_buffer_shutdown,
+ memory,
+ n);
+}
+
+
+
+/* The buffer input function for a buffer built on a socket. */
+
+static int
+socket_buffer_input (closure, data, need, size, got)
+ void *closure;
+ char *data;
+ int need;
+ int size;
+ int *got;
+{
+ struct socket_buffer *sb = (struct socket_buffer *) closure;
+ int nbytes;
+
+ /* I believe that the recv function gives us exactly the semantics
+ we want. If there is a message, it returns immediately with
+ whatever it could get. If there is no message, it waits until
+ one comes in. In other words, it is not like read, which in
+ blocking mode normally waits until all the requested data is
+ available. */
+
+ *got = 0;
+
+ do
+ {
+
+ /* Note that for certain (broken?) networking stacks, like
+ VMS's UCX (not sure what version, problem reported with
+ recv() in 1997), and (according to windows-NT/config.h)
+ Windows NT 3.51, we must call recv or send with a
+ moderately sized buffer (say, less than 200K or something),
+ or else there may be network errors (somewhat hard to
+ produce, e.g. WAN not LAN or some such). buf_read_data
+ makes sure that we only recv() BUFFER_DATA_SIZE bytes at
+ a time. */
+
+ nbytes = recv (sb->socket, data, size, 0);
+ if (nbytes < 0)
+ error (1, 0, "reading from server: %s", SOCK_STRERROR (SOCK_ERRNO));
+ if (nbytes == 0)
+ {
+ /* End of file (for example, the server has closed
+ the connection). If we've already read something, we
+ just tell the caller about the data, not about the end of
+ file. If we've read nothing, we return end of file. */
+ if (*got == 0)
+ return -1;
+ else
+ return 0;
+ }
+ need -= nbytes;
+ size -= nbytes;
+ data += nbytes;
+ *got += nbytes;
+ }
+ while (need > 0);
+
+ return 0;
+}
+
+
+
+/* The buffer output function for a buffer built on a socket. */
+
+static int
+socket_buffer_output (closure, data, have, wrote)
+ void *closure;
+ const char *data;
+ int have;
+ int *wrote;
+{
+ struct socket_buffer *sb = (struct socket_buffer *) closure;
+
+ *wrote = have;
+
+ /* See comment in socket_buffer_input regarding buffer size we pass
+ to send and recv. */
+
+#ifdef SEND_NEVER_PARTIAL
+ /* If send() never will produce a partial write, then just do it. This
+ is needed for systems where its return value is something other than
+ the number of bytes written. */
+ if (send (sb->socket, data, have, 0) < 0)
+ error (1, 0, "writing to server socket: %s", SOCK_STRERROR (SOCK_ERRNO));
+#else
+ while (have > 0)
+ {
+ int nbytes;
+
+ nbytes = send (sb->socket, data, have, 0);
+ if (nbytes < 0)
+ error (1, 0, "writing to server socket: %s", SOCK_STRERROR (SOCK_ERRNO));
+
+ have -= nbytes;
+ data += nbytes;
+ }
+#endif
+
+ return 0;
+}
+
+
+
+/* The buffer flush function for a buffer built on a socket. */
+
+/*ARGSUSED*/
+static int
+socket_buffer_flush (closure)
+ void *closure;
+{
+ /* Nothing to do. Sockets are always flushed. */
+ return 0;
+}
+
+
+
+static int
+socket_buffer_shutdown (buf)
+ struct buffer *buf;
+{
+ struct socket_buffer *n = (struct socket_buffer *) buf->closure;
+ char tmp;
+
+ /* no need to flush children of an endpoint buffer here */
+
+ if (buf->input)
+ {
+ int err = 0;
+ if (! buf_empty_p (buf)
+ || (err = recv (n->socket, &tmp, 1, 0)) > 0)
+ error (0, 0, "dying gasps from %s unexpected", current_parsed_root->hostname);
+ else if (err == -1)
+ error (0, 0, "reading from %s: %s", current_parsed_root->hostname, SOCK_STRERROR (SOCK_ERRNO));
+
+ /* shutdown() socket */
+# ifdef SHUTDOWN_SERVER
+ if (current_parsed_root->method != server_method)
+# endif
+ if (shutdown (n->socket, 0) < 0)
+ {
+ error (1, 0, "shutting down server socket: %s", SOCK_STRERROR (SOCK_ERRNO));
+ }
+
+ buf->input = NULL;
+ }
+ else if (buf->output)
+ {
+ /* shutdown() socket */
+# ifdef SHUTDOWN_SERVER
+ /* FIXME: Should have a SHUTDOWN_SERVER_INPUT &
+ * SHUTDOWN_SERVER_OUTPUT
+ */
+ if (current_parsed_root->method == server_method)
+ SHUTDOWN_SERVER (n->socket);
+ else
+# endif
+ if (shutdown (n->socket, 1) < 0)
+ {
+ error (1, 0, "shutting down server socket: %s", SOCK_STRERROR (SOCK_ERRNO));
+ }
+
+ buf->output = NULL;
+ }
+
+ return 0;
+}
+
+#endif /* NO_SOCKET_TO_FD */
+
+/*
+ * Read a line from the server. Result does not include the terminating \n.
+ *
+ * Space for the result is malloc'd and should be freed by the caller.
+ *
+ * Returns number of bytes read.
+ */
+static int
+read_line (resultp)
+ char **resultp;
+{
+ int status;
+ char *result;
+ int len;
+
+ status = buf_flush (to_server, 1);
+ if (status != 0)
+ error (1, status, "writing to server");
+
+ status = buf_read_line (from_server, &result, &len);
+ if (status != 0)
+ {
+ if (status == -1)
+ error (1, 0, "end of file from server (consult above messages if any)");
+ else if (status == -2)
+ error (1, 0, "out of memory");
+ else
+ error (1, status, "reading from server");
+ }
+
+ if (resultp != NULL)
+ *resultp = result;
+ else
+ free (result);
+
+ return len;
+}
+
+#endif /* CLIENT_SUPPORT */
+
+
+#if defined(CLIENT_SUPPORT) || defined(SERVER_SUPPORT)
+
+/*
+ * Level of compression to use when running gzip on a single file.
+ */
+int file_gzip_level;
+
+#endif /* CLIENT_SUPPORT or SERVER_SUPPORT */
+
+#ifdef CLIENT_SUPPORT
+
+/*
+ * The Repository for the top level of this command (not necessarily
+ * the CVSROOT, just the current directory at the time we do it).
+ */
+static char *toplevel_repos = NULL;
+
+/* Working directory when we first started. Note: we could speed things
+ up on some systems by using savecwd.h here instead of just always
+ storing a name. */
+char *toplevel_wd;
+
+static void
+handle_ok (args, len)
+ char *args;
+ int len;
+{
+ return;
+}
+
+static void
+handle_error (args, len)
+ char *args;
+ int len;
+{
+ int something_printed;
+
+ /*
+ * First there is a symbolic error code followed by a space, which
+ * we ignore.
+ */
+ char *p = strchr (args, ' ');
+ if (p == NULL)
+ {
+ error (0, 0, "invalid data from cvs server");
+ return;
+ }
+ ++p;
+
+ /* Next we print the text of the message from the server. We
+ probably should be prefixing it with "server error" or some
+ such, because if it is something like "Out of memory", the
+ current behavior doesn't say which machine is out of
+ memory. */
+
+ len -= p - args;
+ something_printed = 0;
+ for (; len > 0; --len)
+ {
+ something_printed = 1;
+ putc (*p++, stderr);
+ }
+ if (something_printed)
+ putc ('\n', stderr);
+}
+
+static void
+handle_valid_requests (args, len)
+ char *args;
+ int len;
+{
+ char *p = args;
+ char *q;
+ struct request *rq;
+ do
+ {
+ q = strchr (p, ' ');
+ if (q != NULL)
+ *q++ = '\0';
+ for (rq = requests; rq->name != NULL; ++rq)
+ {
+ if (strcmp (rq->name, p) == 0)
+ break;
+ }
+ if (rq->name == NULL)
+ /*
+ * It is a request we have never heard of (and thus never
+ * will want to use). So don't worry about it.
+ */
+ ;
+ else
+ {
+ if (rq->flags & RQ_ENABLEME)
+ {
+ /*
+ * Server wants to know if we have this, to enable the
+ * feature.
+ */
+ send_to_server (rq->name, 0);
+ send_to_server ("\012", 0);
+ }
+ else
+ rq->flags |= RQ_SUPPORTED;
+ }
+ p = q;
+ } while (q != NULL);
+ for (rq = requests; rq->name != NULL; ++rq)
+ {
+ if ((rq->flags & RQ_SUPPORTED)
+ || (rq->flags & RQ_ENABLEME))
+ continue;
+ if (rq->flags & RQ_ESSENTIAL)
+ error (1, 0, "request `%s' not supported by server", rq->name);
+ }
+}
+
+
+
+/*
+ * This is a proc for walklist(). It inverts the error return premise of
+ * walklist.
+ *
+ * RETURNS
+ * True If this path is prefixed by one of the paths in walklist and
+ * does not step above the prefix path.
+ * False Otherwise.
+ */
+static
+int path_list_prefixed (p, closure)
+ Node *p;
+ void *closure;
+{
+ const char *questionable = closure;
+ const char *prefix = p->key;
+ if (strncmp (prefix, questionable, strlen (prefix))) return 0;
+ questionable += strlen (prefix);
+ while (ISDIRSEP (*questionable)) questionable++;
+ if (*questionable == '\0') return 1;
+ return pathname_levels (questionable);
+}
+
+
+
+/*
+ * Need to validate the client pathname. Disallowed paths include:
+ *
+ * 1. Absolute paths.
+ * 2. Pathnames that do not reference a specifically requested update
+ * directory.
+ *
+ * In case 2, we actually only check that the directory is under the uppermost
+ * directories mentioned on the command line.
+ *
+ * RETURNS
+ * True If the path is valid.
+ * False Otherwise.
+ */
+static
+int is_valid_client_path (pathname)
+ const char *pathname;
+{
+ /* 1. Absolute paths. */
+ if (isabsolute (pathname)) return 0;
+ /* 2. No up-references in path. */
+ if (pathname_levels (pathname) == 0) return 1;
+ /* 2. No Max-dotdot paths registered. */
+ if (uppaths == NULL) return 0;
+
+ return walklist (uppaths, path_list_prefixed, (void *)pathname);
+}
+
+
+
+/*
+ * Do all the processing for PATHNAME, where pathname consists of the
+ * repository and the filename. The parameters we pass to FUNC are:
+ * DATA is just the DATA parameter which was passed to
+ * call_in_directory; ENT_LIST is a pointer to an entries list (which
+ * we manage the storage for); SHORT_PATHNAME is the pathname of the
+ * file relative to the (overall) directory in which the command is
+ * taking place; and FILENAME is the filename portion only of
+ * SHORT_PATHNAME. When we call FUNC, the curent directory points to
+ * the directory portion of SHORT_PATHNAME. */
+
+static void
+call_in_directory (pathname, func, data)
+ char *pathname;
+ void (*func) PROTO((char *data, List *ent_list, char *short_pathname,
+ char *filename));
+ char *data;
+{
+ /* This variable holds the result of Entries_Open. */
+ List *last_entries = NULL;
+ char *dir_name;
+ char *filename;
+ /* This is what we get when we hook up the directory (working directory
+ name) from PATHNAME with the filename from REPOSNAME. For example:
+ pathname: ccvs/src/
+ reposname: /u/src/master/ccvs/foo/ChangeLog
+ short_pathname: ccvs/src/ChangeLog
+ */
+ char *short_pathname;
+ char *p;
+
+ /*
+ * Do the whole descent in parallel for the repositories, so we
+ * know what to put in CVS/Repository files. I'm not sure the
+ * full hair is necessary since the server does a similar
+ * computation; I suspect that we only end up creating one
+ * directory at a time anyway.
+ *
+ * Also note that we must *only* worry about this stuff when we
+ * are creating directories; `cvs co foo/bar; cd foo/bar; cvs co
+ * CVSROOT; cvs update' is legitimate, but in this case
+ * foo/bar/CVSROOT/CVS/Repository is not a subdirectory of
+ * foo/bar/CVS/Repository.
+ */
+ char *reposname;
+ char *short_repos;
+ char *reposdirname;
+ char *rdirp;
+ int reposdirname_absolute;
+ int newdir = 0;
+
+ assert (pathname);
+
+ reposname = NULL;
+ read_line (&reposname);
+ assert (reposname != NULL);
+
+ reposdirname_absolute = 0;
+ if (strncmp (reposname, toplevel_repos, strlen (toplevel_repos)) != 0)
+ {
+ reposdirname_absolute = 1;
+ short_repos = reposname;
+ }
+ else
+ {
+ short_repos = reposname + strlen (toplevel_repos) + 1;
+ if (short_repos[-1] != '/')
+ {
+ reposdirname_absolute = 1;
+ short_repos = reposname;
+ }
+ }
+
+ /* Now that we have SHORT_REPOS, we can calculate the path to the file we
+ * are being requested to operate on.
+ */
+ filename = strrchr (short_repos, '/');
+ if (filename == NULL)
+ filename = short_repos;
+ else
+ ++filename;
+
+ short_pathname = xmalloc (strlen (pathname) + strlen (filename) + 5);
+ strcpy (short_pathname, pathname);
+ strcat (short_pathname, filename);
+
+ /* Now that we know the path to the file we were requested to operate on,
+ * we can verify that it is valid.
+ *
+ * For security reasons, if SHORT_PATHNAME is absolute or attempts to
+ * ascend outside of the current sanbbox, we abort. The server should not
+ * send us anything but relative paths which remain inside the sandbox
+ * here. Anything less means a trojan CVS server could create and edit
+ * arbitrary files on the client.
+ */
+ if (!is_valid_client_path (short_pathname))
+ {
+ error (0, 0,
+ "Server attempted to update a file via an invalid pathname:");
+ error (1, 0, "`%s'.", short_pathname);
+ }
+
+ reposdirname = xstrdup (short_repos);
+ p = strrchr (reposdirname, '/');
+ if (p == NULL)
+ {
+ reposdirname = xrealloc (reposdirname, 2);
+ reposdirname[0] = '.'; reposdirname[1] = '\0';
+ }
+ else
+ *p = '\0';
+
+ dir_name = xstrdup (pathname);
+ p = strrchr (dir_name, '/');
+ if (p == NULL)
+ {
+ dir_name = xrealloc (dir_name, 2);
+ dir_name[0] = '.'; dir_name[1] = '\0';
+ }
+ else
+ *p = '\0';
+ if (client_prune_dirs)
+ add_prune_candidate (dir_name);
+
+ if (toplevel_wd == NULL)
+ {
+ toplevel_wd = xgetwd ();
+ if (toplevel_wd == NULL)
+ error (1, errno, "could not get working directory");
+ }
+
+ if (CVS_CHDIR (toplevel_wd) < 0)
+ error (1, errno, "could not chdir to %s", toplevel_wd);
+
+ if (CVS_CHDIR (dir_name) < 0)
+ {
+ char *dir;
+ char *dirp;
+
+ if (! existence_error (errno))
+ error (1, errno, "could not chdir to %s", dir_name);
+
+ /* Directory does not exist, we need to create it. */
+ newdir = 1;
+
+ /* Provided we are willing to assume that directories get
+ created one at a time, we could simplify this a lot.
+ Do note that one aspect still would need to walk the
+ dir_name path: the checking for "fncmp (dir, CVSADM)". */
+
+ dir = xmalloc (strlen (dir_name) + 1);
+ dirp = dir_name;
+ rdirp = reposdirname;
+
+ /* This algorithm makes nested directories one at a time
+ and create CVS administration files in them. For
+ example, we're checking out foo/bar/baz from the
+ repository:
+
+ 1) create foo, point CVS/Repository to <root>/foo
+ 2) .. foo/bar .. <root>/foo/bar
+ 3) .. foo/bar/baz .. <root>/foo/bar/baz
+
+ As you can see, we're just stepping along DIR_NAME (with
+ DIRP) and REPOSDIRNAME (with RDIRP) respectively.
+
+ We need to be careful when we are checking out a
+ module, however, since DIR_NAME and REPOSDIRNAME are not
+ going to be the same. Since modules will not have any
+ slashes in their names, we should watch the output of
+ STRCHR to decide whether or not we should use STRCHR on
+ the RDIRP. That is, if we're down to a module name,
+ don't keep picking apart the repository directory name. */
+
+ do
+ {
+ dirp = strchr (dirp, '/');
+ if (dirp)
+ {
+ strncpy (dir, dir_name, dirp - dir_name);
+ dir[dirp - dir_name] = '\0';
+ /* Skip the slash. */
+ ++dirp;
+ if (rdirp == NULL)
+ /* This just means that the repository string has
+ fewer components than the dir_name string. But
+ that is OK (e.g. see modules3-8 in testsuite). */
+ ;
+ else
+ rdirp = strchr (rdirp, '/');
+ }
+ else
+ {
+ /* If there are no more slashes in the dir name,
+ we're down to the most nested directory -OR- to
+ the name of a module. In the first case, we
+ should be down to a DIRP that has no slashes,
+ so it won't help/hurt to do another STRCHR call
+ on DIRP. It will definitely hurt, however, if
+ we're down to a module name, since a module
+ name can point to a nested directory (that is,
+ DIRP will still have slashes in it. Therefore,
+ we should set it to NULL so the routine below
+ copies the contents of REMOTEDIRNAME onto the
+ root repository directory (does this if rdirp
+ is set to NULL, because we used to do an extra
+ STRCHR call here). */
+
+ rdirp = NULL;
+ strcpy (dir, dir_name);
+ }
+
+ if (fncmp (dir, CVSADM) == 0)
+ {
+ error (0, 0, "cannot create a directory named %s", dir);
+ error (0, 0, "because CVS uses \"%s\" for its own uses",
+ CVSADM);
+ error (1, 0, "rename the directory and try again");
+ }
+
+ if (mkdir_if_needed (dir))
+ {
+ /* It already existed, fine. Just keep going. */
+ }
+ else if (strcmp (cvs_cmd_name, "export") == 0)
+ /* Don't create CVSADM directories if this is export. */
+ ;
+ else
+ {
+ /*
+ * Put repository in CVS/Repository. For historical
+ * (pre-CVS/Root) reasons, this is an absolute pathname,
+ * but what really matters is the part of it which is
+ * relative to cvsroot.
+ */
+ char *repo;
+ char *r, *b;
+
+ repo = xmalloc (strlen (reposdirname)
+ + strlen (toplevel_repos)
+ + 80);
+ if (reposdirname_absolute)
+ r = repo;
+ else
+ {
+ strcpy (repo, toplevel_repos);
+ strcat (repo, "/");
+ r = repo + strlen (repo);
+ }
+
+ if (rdirp)
+ {
+ /* See comment near start of function; the only
+ way that the server can put the right thing
+ in each CVS/Repository file is to create the
+ directories one at a time. I think that the
+ CVS server has been doing this all along. */
+ error (0, 0, "\
+warning: server is not creating directories one at a time");
+ strncpy (r, reposdirname, rdirp - reposdirname);
+ r[rdirp - reposdirname] = '\0';
+ }
+ else
+ strcpy (r, reposdirname);
+
+ Create_Admin (dir, dir, repo,
+ (char *)NULL, (char *)NULL, 0, 0, 1);
+ free (repo);
+
+ b = strrchr (dir, '/');
+ if (b == NULL)
+ Subdir_Register ((List *) NULL, (char *) NULL, dir);
+ else
+ {
+ *b = '\0';
+ Subdir_Register ((List *) NULL, dir, b + 1);
+ *b = '/';
+ }
+ }
+
+ if (rdirp != NULL)
+ {
+ /* Skip the slash. */
+ ++rdirp;
+ }
+
+ } while (dirp != NULL);
+ free (dir);
+ /* Now it better work. */
+ if ( CVS_CHDIR (dir_name) < 0)
+ error (1, errno, "could not chdir to %s", dir_name);
+ }
+ else if (strcmp (cvs_cmd_name, "export") == 0)
+ /* Don't create CVSADM directories if this is export. */
+ ;
+ else if (!isdir (CVSADM))
+ {
+ /*
+ * Put repository in CVS/Repository. For historical
+ * (pre-CVS/Root) reasons, this is an absolute pathname,
+ * but what really matters is the part of it which is
+ * relative to cvsroot.
+ */
+ char *repo;
+
+ if (reposdirname_absolute)
+ repo = reposdirname;
+ else
+ {
+ repo = xmalloc (strlen (reposdirname)
+ + strlen (toplevel_repos)
+ + 10);
+ strcpy (repo, toplevel_repos);
+ strcat (repo, "/");
+ strcat (repo, reposdirname);
+ }
+
+ Create_Admin (".", ".", repo, (char *)NULL, (char *)NULL, 0, 1, 1);
+ if (repo != reposdirname)
+ free (repo);
+ }
+
+ if (strcmp (cvs_cmd_name, "export") != 0)
+ {
+ last_entries = Entries_Open (0, dir_name);
+
+ /* If this is a newly created directory, we will record
+ all subdirectory information, so call Subdirs_Known in
+ case there are no subdirectories. If this is not a
+ newly created directory, it may be an old working
+ directory from before we recorded subdirectory
+ information in the Entries file. We force a search for
+ all subdirectories now, to make sure our subdirectory
+ information is up to date. If the Entries file does
+ record subdirectory information, then this call only
+ does list manipulation. */
+ if (newdir)
+ Subdirs_Known (last_entries);
+ else
+ {
+ List *dirlist;
+
+ dirlist = Find_Directories ((char *) NULL, W_LOCAL,
+ last_entries);
+ dellist (&dirlist);
+ }
+ }
+ free (reposdirname);
+ (*func) (data, last_entries, short_pathname, filename);
+ if (last_entries != NULL)
+ Entries_Close (last_entries);
+ free (dir_name);
+ free (short_pathname);
+ free (reposname);
+}
+
+static void
+copy_a_file (data, ent_list, short_pathname, filename)
+ char *data;
+ List *ent_list;
+ char *short_pathname;
+ char *filename;
+{
+ char *newname;
+#ifdef USE_VMS_FILENAMES
+ char *p;
+#endif
+
+ read_line (&newname);
+
+#ifdef USE_VMS_FILENAMES
+ /* Mogrify the filename so VMS is happy with it. */
+ for(p = newname; *p; p++)
+ if(*p == '.' || *p == '#') *p = '_';
+#endif
+ /* cvsclient.texi has said for a long time that newname must be in the
+ same directory. Wouldn't want a malicious or buggy server overwriting
+ ~/.profile, /etc/passwd, or anything like that. */
+ if (last_component (newname) != newname)
+ error (1, 0, "protocol error: Copy-file tried to specify directory");
+
+ if (unlink_file (newname) && !existence_error (errno))
+ error (0, errno, "unable to remove %s", newname);
+ copy_file (filename, newname);
+ free (newname);
+}
+
+static void
+handle_copy_file (args, len)
+ char *args;
+ int len;
+{
+ call_in_directory (args, copy_a_file, (char *)NULL);
+}
+
+
+
+/* Attempt to read a file size from a string. Accepts base 8 (0N), base 16
+ * (0xN), or base 10. Exits on error.
+ *
+ * RETURNS
+ * The file size, in a size_t.
+ *
+ * FATAL ERRORS
+ * 1. As strtoul().
+ * 2. If the number read exceeds SIZE_MAX.
+ */
+static size_t
+strto_file_size (const char *s)
+{
+ unsigned long tmp;
+ char *endptr;
+
+ /* Read it. */
+ errno = 0;
+ tmp = strtoul (s, &endptr, 0);
+
+ /* Check for errors. */
+ if (errno || endptr == s)
+ error (1, errno, "Server sent invalid file size `%s'", s);
+ if (*endptr != '\0')
+ error (1, 0,
+ "Server sent trailing characters in file size `%s'",
+ endptr);
+ if (tmp > SIZE_MAX)
+ error (1, 0, "Server sent file size exceeding client max.");
+
+ /* Return it. */
+ return (size_t)tmp;
+}
+
+
+
+static void read_counted_file PROTO ((char *, char *));
+
+/* Read from the server the count for the length of a file, then read
+ the contents of that file and write them to FILENAME. FULLNAME is
+ the name of the file for use in error messages. FIXME-someday:
+ extend this to deal with compressed files and make update_entries
+ use it. On error, gives a fatal error. */
+static void
+read_counted_file (filename, fullname)
+ char *filename;
+ char *fullname;
+{
+ char *size_string;
+ size_t size;
+ char *buf;
+
+ /* Pointers in buf to the place to put data which will be read,
+ and the data which needs to be written, respectively. */
+ char *pread;
+ char *pwrite;
+ /* Number of bytes left to read and number of bytes in buf waiting to
+ be written, respectively. */
+ size_t nread;
+ size_t nwrite;
+
+ FILE *fp;
+
+ read_line (&size_string);
+ if (size_string[0] == 'z')
+ error (1, 0, "\
+protocol error: compressed files not supported for that operation");
+ size = strto_file_size (size_string);
+ free (size_string);
+
+ /* A more sophisticated implementation would use only a limited amount
+ of buffer space (8K perhaps), and read that much at a time. We allocate
+ a buffer for the whole file only to make it easy to keep track what
+ needs to be read and written. */
+ buf = xmalloc (size);
+
+ /* FIXME-someday: caller should pass in a flag saying whether it
+ is binary or not. I haven't carefully looked into whether
+ CVS/Template files should use local text file conventions or
+ not. */
+ fp = CVS_FOPEN (filename, "wb");
+ if (fp == NULL)
+ error (1, errno, "cannot write %s", fullname);
+ nread = size;
+ nwrite = 0;
+ pread = buf;
+ pwrite = buf;
+ while (nread > 0 || nwrite > 0)
+ {
+ size_t n;
+
+ if (nread > 0)
+ {
+ n = try_read_from_server (pread, nread);
+ nread -= n;
+ pread += n;
+ nwrite += n;
+ }
+
+ if (nwrite > 0)
+ {
+ n = fwrite (pwrite, 1, nwrite, fp);
+ if (ferror (fp))
+ error (1, errno, "cannot write %s", fullname);
+ nwrite -= n;
+ pwrite += n;
+ }
+ }
+ free (buf);
+ if (fclose (fp) < 0)
+ error (1, errno, "cannot close %s", fullname);
+}
+
+/* OK, we want to swallow the "U foo.c" response and then output it only
+ if we can update the file. In the future we probably want some more
+ systematic approach to parsing tagged text, but for now we keep it
+ ad hoc. "Why," I hear you cry, "do we not just look at the
+ Update-existing and Created responses?" That is an excellent question,
+ and the answer is roughly conservatism/laziness--I haven't read through
+ update.c enough to figure out the exact correspondence or lack thereof
+ between those responses and a "U foo.c" line (note that Merged, from
+ join_file, can be either "C foo" or "U foo" depending on the context). */
+/* Nonzero if we have seen +updated and not -updated. */
+static int updated_seen;
+/* Filename from an "fname" tagged response within +updated/-updated. */
+static char *updated_fname;
+
+/* This struct is used to hold data when reading the +importmergecmd
+ and -importmergecmd tags. We put the variables in a struct only
+ for namespace issues. FIXME: As noted above, we need to develop a
+ more systematic approach. */
+static struct
+{
+ /* Nonzero if we have seen +importmergecmd and not -importmergecmd. */
+ int seen;
+ /* Number of conflicts, from a "conflicts" tagged response. */
+ int conflicts;
+ /* First merge tag, from a "mergetag1" tagged response. */
+ char *mergetag1;
+ /* Second merge tag, from a "mergetag2" tagged response. */
+ char *mergetag2;
+ /* Repository, from a "repository" tagged response. */
+ char *repository;
+} importmergecmd;
+
+/* Nonzero if we should arrange to return with a failure exit status. */
+static int failure_exit;
+
+
+/*
+ * The time stamp of the last file we registered.
+ */
+static time_t last_register_time;
+
+/*
+ * The Checksum response gives the checksum for the file transferred
+ * over by the next Updated, Merged or Patch response. We just store
+ * it here, and then check it in update_entries.
+ */
+
+static int stored_checksum_valid;
+static unsigned char stored_checksum[16];
+
+static void
+handle_checksum (args, len)
+ char *args;
+ int len;
+{
+ char *s;
+ char buf[3];
+ int i;
+
+ if (stored_checksum_valid)
+ error (1, 0, "Checksum received before last one was used");
+
+ s = args;
+ buf[2] = '\0';
+ for (i = 0; i < 16; i++)
+ {
+ char *bufend;
+
+ buf[0] = *s++;
+ buf[1] = *s++;
+ stored_checksum[i] = (char) strtol (buf, &bufend, 16);
+ if (bufend != buf + 2)
+ break;
+ }
+
+ if (i < 16 || *s != '\0')
+ error (1, 0, "Invalid Checksum response: `%s'", args);
+
+ stored_checksum_valid = 1;
+}
+
+/* Mode that we got in a "Mode" response (malloc'd), or NULL if none. */
+static char *stored_mode;
+
+static void handle_mode PROTO ((char *, int));
+
+static void
+handle_mode (args, len)
+ char *args;
+ int len;
+{
+ if (stored_mode != NULL)
+ error (1, 0, "protocol error: duplicate Mode");
+ stored_mode = xstrdup (args);
+}
+
+/* Nonzero if time was specified in Mod-time. */
+static int stored_modtime_valid;
+/* Time specified in Mod-time. */
+static time_t stored_modtime;
+
+static void handle_mod_time PROTO ((char *, int));
+
+static void
+handle_mod_time (args, len)
+ char *args;
+ int len;
+{
+ if (stored_modtime_valid)
+ error (0, 0, "protocol error: duplicate Mod-time");
+ stored_modtime = get_date (args, NULL);
+ if (stored_modtime == (time_t) -1)
+ error (0, 0, "protocol error: cannot parse date %s", args);
+ else
+ stored_modtime_valid = 1;
+}
+
+/*
+ * If we receive a patch, but the patch program fails to apply it, we
+ * want to request the original file. We keep a list of files whose
+ * patches have failed.
+ */
+
+char **failed_patches;
+int failed_patches_count;
+
+struct update_entries_data
+{
+ enum {
+ /*
+ * We are just getting an Entries line; the local file is
+ * correct.
+ */
+ UPDATE_ENTRIES_CHECKIN,
+ /* We are getting the file contents as well. */
+ UPDATE_ENTRIES_UPDATE,
+ /*
+ * We are getting a patch against the existing local file, not
+ * an entire new file.
+ */
+ UPDATE_ENTRIES_PATCH,
+ /*
+ * We are getting an RCS change text (diff -n output) against
+ * the existing local file, not an entire new file.
+ */
+ UPDATE_ENTRIES_RCS_DIFF
+ } contents;
+
+ enum {
+ /* We are replacing an existing file. */
+ UPDATE_ENTRIES_EXISTING,
+ /* We are creating a new file. */
+ UPDATE_ENTRIES_NEW,
+ /* We don't know whether it is existing or new. */
+ UPDATE_ENTRIES_EXISTING_OR_NEW
+ } existp;
+
+ /*
+ * String to put in the timestamp field or NULL to use the timestamp
+ * of the file.
+ */
+ char *timestamp;
+};
+
+/* Update the Entries line for this file. */
+static void
+update_entries (data_arg, ent_list, short_pathname, filename)
+ char *data_arg;
+ List *ent_list;
+ char *short_pathname;
+ char *filename;
+{
+ char *entries_line;
+ struct update_entries_data *data = (struct update_entries_data *)data_arg;
+
+ char *cp;
+ char *user;
+ char *vn;
+ /* Timestamp field. Always empty according to the protocol. */
+ char *ts;
+ char *options = NULL;
+ char *tag = NULL;
+ char *date = NULL;
+ char *tag_or_date;
+ char *scratch_entries = NULL;
+ int bin;
+
+#ifdef UTIME_EXPECTS_WRITABLE
+ int change_it_back = 0;
+#endif
+
+ read_line (&entries_line);
+
+ /*
+ * Parse the entries line.
+ */
+ scratch_entries = xstrdup (entries_line);
+
+ if (scratch_entries[0] != '/')
+ error (1, 0, "bad entries line `%s' from server", entries_line);
+ user = scratch_entries + 1;
+ if ((cp = strchr (user, '/')) == NULL)
+ error (1, 0, "bad entries line `%s' from server", entries_line);
+ *cp++ = '\0';
+ vn = cp;
+ if ((cp = strchr (vn, '/')) == NULL)
+ error (1, 0, "bad entries line `%s' from server", entries_line);
+ *cp++ = '\0';
+
+ ts = cp;
+ if ((cp = strchr (ts, '/')) == NULL)
+ error (1, 0, "bad entries line `%s' from server", entries_line);
+ *cp++ = '\0';
+ options = cp;
+ if ((cp = strchr (options, '/')) == NULL)
+ error (1, 0, "bad entries line `%s' from server", entries_line);
+ *cp++ = '\0';
+ tag_or_date = cp;
+
+ /* If a slash ends the tag_or_date, ignore everything after it. */
+ cp = strchr (tag_or_date, '/');
+ if (cp != NULL)
+ *cp = '\0';
+ if (*tag_or_date == 'T')
+ tag = tag_or_date + 1;
+ else if (*tag_or_date == 'D')
+ date = tag_or_date + 1;
+
+ /* Done parsing the entries line. */
+
+ if (data->contents == UPDATE_ENTRIES_UPDATE
+ || data->contents == UPDATE_ENTRIES_PATCH
+ || data->contents == UPDATE_ENTRIES_RCS_DIFF)
+ {
+ char *size_string;
+ char *mode_string;
+ size_t size;
+ char *buf;
+ char *temp_filename;
+ int use_gzip;
+ int patch_failed;
+ char *s;
+
+ read_line (&mode_string);
+
+ read_line (&size_string);
+ if (size_string[0] == 'z')
+ {
+ use_gzip = 1;
+ s = size_string + 1;
+ }
+ else
+ {
+ use_gzip = 0;
+ s = size_string;
+ }
+ size = strto_file_size (s);
+ free (size_string);
+
+ /* Note that checking this separately from writing the file is
+ a race condition: if the existence or lack thereof of the
+ file changes between now and the actual calls which
+ operate on it, we lose. However (a) there are so many
+ cases, I'm reluctant to try to fix them all, (b) in some
+ cases the system might not even have a system call which
+ does the right thing, and (c) it isn't clear this needs to
+ work. */
+ if (data->existp == UPDATE_ENTRIES_EXISTING
+ && !isfile (filename))
+ /* Emit a warning and update the file anyway. */
+ error (0, 0, "warning: %s unexpectedly disappeared",
+ short_pathname);
+
+ if (data->existp == UPDATE_ENTRIES_NEW
+ && isfile (filename))
+ {
+ /* Emit a warning and refuse to update the file; we don't want
+ to clobber a user's file. */
+ size_t nread;
+ size_t toread;
+
+ /* size should be unsigned, but until we get around to fixing
+ that, work around it. */
+ size_t usize;
+
+ char buf[8192];
+
+ /* This error might be confusing; it isn't really clear to
+ the user what to do about it. Keep in mind that it has
+ several causes: (1) something/someone creates the file
+ during the time that CVS is running, (2) the repository
+ has two files whose names clash for the client because
+ of case-insensitivity or similar causes, See 3 for
+ additional notes. (3) a special case of this is that a
+ file gets renamed for example from a.c to A.C. A
+ "cvs update" on a case-insensitive client will get this
+ error. In this case and in case 2, the filename
+ (short_pathname) printed in the error message will likely _not_
+ have the same case as seen by the user in a directory listing.
+ (4) the client has a file which the server doesn't know
+ about (e.g. "? foo" file), and that name clashes with a file
+ the server does know about, (5) classify.c will print the same
+ message for other reasons.
+
+ I hope the above paragraph makes it clear that making this
+ clearer is not a one-line fix. */
+ error (0, 0, "move away %s; it is in the way", short_pathname);
+ if (updated_fname != NULL)
+ {
+ cvs_output ("C ", 0);
+ cvs_output (updated_fname, 0);
+ cvs_output ("\n", 1);
+ }
+ failure_exit = 1;
+
+ discard_file_and_return:
+ /* Now read and discard the file contents. */
+ usize = size;
+ nread = 0;
+ while (nread < usize)
+ {
+ toread = usize - nread;
+ if (toread > sizeof buf)
+ toread = sizeof buf;
+
+ nread += try_read_from_server (buf, toread);
+ if (nread == usize)
+ break;
+ }
+
+ free (mode_string);
+ free (scratch_entries);
+ free (entries_line);
+
+ /* The Mode, Mod-time, and Checksum responses should not carry
+ over to a subsequent Created (or whatever) response, even
+ in the error case. */
+ if (stored_mode != NULL)
+ {
+ free (stored_mode);
+ stored_mode = NULL;
+ }
+ stored_modtime_valid = 0;
+ stored_checksum_valid = 0;
+
+ if (updated_fname != NULL)
+ {
+ free (updated_fname);
+ updated_fname = NULL;
+ }
+ return;
+ }
+
+ temp_filename = xmalloc (strlen (filename) + 80);
+#ifdef USE_VMS_FILENAMES
+ /* A VMS rename of "blah.dat" to "foo" to implies a
+ destination of "foo.dat" which is unfortinate for CVS */
+ sprintf (temp_filename, "%s_new_", filename);
+#else
+#ifdef _POSIX_NO_TRUNC
+ sprintf (temp_filename, ".new.%.9s", filename);
+#else /* _POSIX_NO_TRUNC */
+ sprintf (temp_filename, ".new.%s", filename);
+#endif /* _POSIX_NO_TRUNC */
+#endif /* USE_VMS_FILENAMES */
+
+ buf = xmalloc (size);
+
+ /* Some systems, like OS/2 and Windows NT, end lines with CRLF
+ instead of just LF. Format translation is done in the C
+ library I/O funtions. Here we tell them whether or not to
+ convert -- if this file is marked "binary" with the RCS -kb
+ flag, then we don't want to convert, else we do (because
+ CVS assumes text files by default). */
+
+ if (options)
+ bin = !(strcmp (options, "-kb"));
+ else
+ bin = 0;
+
+ if (data->contents == UPDATE_ENTRIES_RCS_DIFF)
+ {
+ /* This is an RCS change text. We just hold the change
+ text in memory. */
+
+ if (use_gzip)
+ error (1, 0,
+ "server error: gzip invalid with RCS change text");
+
+ read_from_server (buf, size);
+ }
+ else
+ {
+ int fd;
+
+ fd = CVS_OPEN (temp_filename,
+ (O_WRONLY | O_CREAT | O_TRUNC
+ | (bin ? OPEN_BINARY : 0)),
+ 0777);
+
+ if (fd < 0)
+ {
+ /* I can see a case for making this a fatal error; for
+ a condition like disk full or network unreachable
+ (for a file server), carrying on and giving an
+ error on each file seems unnecessary. But if it is
+ a permission problem, or some such, then it is
+ entirely possible that future files will not have
+ the same problem. */
+ error (0, errno, "cannot write %s", short_pathname);
+ free (temp_filename);
+ free (buf);
+ goto discard_file_and_return;
+ }
+
+ if (size > 0)
+ {
+ read_from_server (buf, size);
+
+ if (use_gzip)
+ {
+ if (gunzip_and_write (fd, short_pathname,
+ (unsigned char *) buf, size))
+ error (1, 0, "aborting due to compression error");
+ }
+ else if (write (fd, buf, size) != size)
+ error (1, errno, "writing %s", short_pathname);
+ }
+
+ if (close (fd) < 0)
+ error (1, errno, "writing %s", short_pathname);
+ }
+
+ /* This is after we have read the file from the net (a change
+ from previous versions, where the server would send us
+ "M U foo.c" before Update-existing or whatever), but before
+ we finish writing the file (arguably a bug). The timing
+ affects a user who wants status info about how far we have
+ gotten, and also affects whether "U foo.c" appears in addition
+ to various error messages. */
+ if (updated_fname != NULL)
+ {
+ cvs_output ("U ", 0);
+ cvs_output (updated_fname, 0);
+ cvs_output ("\n", 1);
+ free (updated_fname);
+ updated_fname = 0;
+ }
+
+ patch_failed = 0;
+
+ if (data->contents == UPDATE_ENTRIES_UPDATE)
+ {
+ rename_file (temp_filename, filename);
+ }
+ else if (data->contents == UPDATE_ENTRIES_PATCH)
+ {
+ /* You might think we could just leave Patched out of
+ Valid-responses and not get this response. However, if
+ memory serves, the CVS 1.9 server bases this on -u
+ (update-patches), and there is no way for us to send -u
+ or not based on whether the server supports "Rcs-diff".
+
+ Fall back to transmitting entire files. */
+ patch_failed = 1;
+ }
+ else
+ {
+ char *filebuf;
+ size_t filebufsize;
+ size_t nread;
+ char *patchedbuf;
+ size_t patchedlen;
+
+ /* Handle UPDATE_ENTRIES_RCS_DIFF. */
+
+ if (!isfile (filename))
+ error (1, 0, "patch original file %s does not exist",
+ short_pathname);
+ filebuf = NULL;
+ filebufsize = 0;
+ nread = 0;
+
+ get_file (filename, short_pathname, bin ? FOPEN_BINARY_READ : "r",
+ &filebuf, &filebufsize, &nread);
+ /* At this point the contents of the existing file are in
+ FILEBUF, and the length of the contents is in NREAD.
+ The contents of the patch from the network are in BUF,
+ and the length of the patch is in SIZE. */
+
+ if (! rcs_change_text (short_pathname, filebuf, nread, buf, size,
+ &patchedbuf, &patchedlen))
+ patch_failed = 1;
+ else
+ {
+ if (stored_checksum_valid)
+ {
+ struct cvs_MD5Context context;
+ unsigned char checksum[16];
+
+ /* We have a checksum. Check it before writing
+ the file out, so that we don't have to read it
+ back in again. */
+ cvs_MD5Init (&context);
+ cvs_MD5Update (&context,
+ (unsigned char *) patchedbuf, patchedlen);
+ cvs_MD5Final (checksum, &context);
+ if (memcmp (checksum, stored_checksum, 16) != 0)
+ {
+ error (0, 0,
+ "checksum failure after patch to %s; will refetch",
+ short_pathname);
+
+ patch_failed = 1;
+ }
+
+ stored_checksum_valid = 0;
+ }
+
+ if (! patch_failed)
+ {
+ FILE *e;
+
+ e = open_file (temp_filename,
+ bin ? FOPEN_BINARY_WRITE : "w");
+ if (fwrite (patchedbuf, 1, patchedlen, e) != patchedlen)
+ error (1, errno, "cannot write %s", temp_filename);
+ if (fclose (e) == EOF)
+ error (1, errno, "cannot close %s", temp_filename);
+ rename_file (temp_filename, filename);
+ }
+
+ free (patchedbuf);
+ }
+
+ free (filebuf);
+ }
+
+ free (temp_filename);
+
+ if (stored_checksum_valid && ! patch_failed)
+ {
+ FILE *e;
+ struct cvs_MD5Context context;
+ unsigned char buf[8192];
+ unsigned len;
+ unsigned char checksum[16];
+
+ /*
+ * Compute the MD5 checksum. This will normally only be
+ * used when receiving a patch, so we always compute it
+ * here on the final file, rather than on the received
+ * data.
+ *
+ * Note that if the file is a text file, we should read it
+ * here using text mode, so its lines will be terminated the same
+ * way they were transmitted.
+ */
+ e = CVS_FOPEN (filename, "r");
+ if (e == NULL)
+ error (1, errno, "could not open %s", short_pathname);
+
+ cvs_MD5Init (&context);
+ while ((len = fread (buf, 1, sizeof buf, e)) != 0)
+ cvs_MD5Update (&context, buf, len);
+ if (ferror (e))
+ error (1, errno, "could not read %s", short_pathname);
+ cvs_MD5Final (checksum, &context);
+
+ fclose (e);
+
+ stored_checksum_valid = 0;
+
+ if (memcmp (checksum, stored_checksum, 16) != 0)
+ {
+ if (data->contents != UPDATE_ENTRIES_PATCH)
+ error (1, 0, "checksum failure on %s",
+ short_pathname);
+
+ error (0, 0,
+ "checksum failure after patch to %s; will refetch",
+ short_pathname);
+
+ patch_failed = 1;
+ }
+ }
+
+ if (patch_failed)
+ {
+ /* Save this file to retrieve later. */
+ failed_patches = (char **) xrealloc ((char *) failed_patches,
+ ((failed_patches_count + 1)
+ * sizeof (char *)));
+ failed_patches[failed_patches_count] = xstrdup (short_pathname);
+ ++failed_patches_count;
+
+ stored_checksum_valid = 0;
+
+ free (mode_string);
+ free (buf);
+ free (scratch_entries);
+ free (entries_line);
+
+ return;
+ }
+
+ {
+ int status = change_mode (filename, mode_string, 1);
+ if (status != 0)
+ error (0, status, "cannot change mode of %s", short_pathname);
+ }
+
+ free (mode_string);
+ free (buf);
+ }
+
+ if (stored_mode != NULL)
+ {
+ change_mode (filename, stored_mode, 1);
+ free (stored_mode);
+ stored_mode = NULL;
+ }
+
+ if (stored_modtime_valid)
+ {
+ struct utimbuf t;
+
+ memset (&t, 0, sizeof (t));
+ t.modtime = stored_modtime;
+ (void) time (&t.actime);
+
+#ifdef UTIME_EXPECTS_WRITABLE
+ if (!iswritable (filename))
+ {
+ xchmod (filename, 1);
+ change_it_back = 1;
+ }
+#endif /* UTIME_EXPECTS_WRITABLE */
+
+ if (utime (filename, &t) < 0)
+ error (0, errno, "cannot set time on %s", filename);
+
+#ifdef UTIME_EXPECTS_WRITABLE
+ if (change_it_back)
+ {
+ xchmod (filename, 0);
+ change_it_back = 0;
+ }
+#endif /* UTIME_EXPECTS_WRITABLE */
+
+ stored_modtime_valid = 0;
+ }
+
+ /*
+ * Process the entries line. Do this after we've written the file,
+ * since we need the timestamp.
+ */
+ if (strcmp (cvs_cmd_name, "export") != 0)
+ {
+ char *local_timestamp;
+ char *file_timestamp;
+
+ (void) time (&last_register_time);
+
+ local_timestamp = data->timestamp;
+ if (local_timestamp == NULL || ts[0] == '+')
+ file_timestamp = time_stamp (filename);
+ else
+ file_timestamp = NULL;
+
+ /*
+ * These special version numbers signify that it is not up to
+ * date. Create a dummy timestamp which will never compare
+ * equal to the timestamp of the file.
+ */
+ if (vn[0] == '\0' || strcmp (vn, "0") == 0 || vn[0] == '-')
+ local_timestamp = "dummy timestamp";
+ else if (local_timestamp == NULL)
+ {
+ local_timestamp = file_timestamp;
+
+ /* Checking for cvs_cmd_name of "commit" doesn't seem like
+ the cleanest way to handle this, but it seem to roughly
+ parallel what the :local: code which calls
+ mark_up_to_date ends up amounting to. Some day, should
+ think more about what the Checked-in response means
+ vis-a-vis both Entries and Base and clarify
+ cvsclient.texi accordingly. */
+
+ if (!strcmp (cvs_cmd_name, "commit"))
+ mark_up_to_date (filename);
+ }
+
+ Register (ent_list, filename, vn, local_timestamp,
+ options, tag, date, ts[0] == '+' ? file_timestamp : NULL);
+
+ if (file_timestamp)
+ free (file_timestamp);
+
+ }
+ free (scratch_entries);
+ free (entries_line);
+}
+
+static void
+handle_checked_in (args, len)
+ char *args;
+ int len;
+{
+ struct update_entries_data dat;
+ dat.contents = UPDATE_ENTRIES_CHECKIN;
+ dat.existp = UPDATE_ENTRIES_EXISTING_OR_NEW;
+ dat.timestamp = NULL;
+ call_in_directory (args, update_entries, (char *)&dat);
+}
+
+static void
+handle_new_entry (args, len)
+ char *args;
+ int len;
+{
+ struct update_entries_data dat;
+ dat.contents = UPDATE_ENTRIES_CHECKIN;
+ dat.existp = UPDATE_ENTRIES_EXISTING_OR_NEW;
+ dat.timestamp = "dummy timestamp from new-entry";
+ call_in_directory (args, update_entries, (char *)&dat);
+}
+
+static void
+handle_updated (args, len)
+ char *args;
+ int len;
+{
+ struct update_entries_data dat;
+ dat.contents = UPDATE_ENTRIES_UPDATE;
+ dat.existp = UPDATE_ENTRIES_EXISTING_OR_NEW;
+ dat.timestamp = NULL;
+ call_in_directory (args, update_entries, (char *)&dat);
+}
+
+static void handle_created PROTO((char *, int));
+
+static void
+handle_created (args, len)
+ char *args;
+ int len;
+{
+ struct update_entries_data dat;
+ dat.contents = UPDATE_ENTRIES_UPDATE;
+ dat.existp = UPDATE_ENTRIES_NEW;
+ dat.timestamp = NULL;
+ call_in_directory (args, update_entries, (char *)&dat);
+}
+
+static void handle_update_existing PROTO((char *, int));
+
+static void
+handle_update_existing (args, len)
+ char *args;
+ int len;
+{
+ struct update_entries_data dat;
+ dat.contents = UPDATE_ENTRIES_UPDATE;
+ dat.existp = UPDATE_ENTRIES_EXISTING;
+ dat.timestamp = NULL;
+ call_in_directory (args, update_entries, (char *)&dat);
+}
+
+static void
+handle_merged (args, len)
+ char *args;
+ int len;
+{
+ struct update_entries_data dat;
+ dat.contents = UPDATE_ENTRIES_UPDATE;
+ /* Think this could be UPDATE_ENTRIES_EXISTING, but just in case... */
+ dat.existp = UPDATE_ENTRIES_EXISTING_OR_NEW;
+ dat.timestamp = "Result of merge";
+ call_in_directory (args, update_entries, (char *)&dat);
+}
+
+static void
+handle_patched (args, len)
+ char *args;
+ int len;
+{
+ struct update_entries_data dat;
+ dat.contents = UPDATE_ENTRIES_PATCH;
+ /* Think this could be UPDATE_ENTRIES_EXISTING, but just in case... */
+ dat.existp = UPDATE_ENTRIES_EXISTING_OR_NEW;
+ dat.timestamp = NULL;
+ call_in_directory (args, update_entries, (char *)&dat);
+}
+
+static void
+handle_rcs_diff (args, len)
+ char *args;
+ int len;
+{
+ struct update_entries_data dat;
+ dat.contents = UPDATE_ENTRIES_RCS_DIFF;
+ /* Think this could be UPDATE_ENTRIES_EXISTING, but just in case... */
+ dat.existp = UPDATE_ENTRIES_EXISTING_OR_NEW;
+ dat.timestamp = NULL;
+ call_in_directory (args, update_entries, (char *)&dat);
+}
+
+static void
+remove_entry (data, ent_list, short_pathname, filename)
+ char *data;
+ List *ent_list;
+ char *short_pathname;
+ char *filename;
+{
+ Scratch_Entry (ent_list, filename);
+}
+
+static void
+handle_remove_entry (args, len)
+ char *args;
+ int len;
+{
+ call_in_directory (args, remove_entry, (char *)NULL);
+}
+
+static void
+remove_entry_and_file (data, ent_list, short_pathname, filename)
+ char *data;
+ List *ent_list;
+ char *short_pathname;
+ char *filename;
+{
+ Scratch_Entry (ent_list, filename);
+ /* Note that we don't ignore existence_error's here. The server
+ should be sending Remove-entry rather than Removed in cases
+ where the file does not exist. And if the user removes the
+ file halfway through a cvs command, we should be printing an
+ error. */
+ if (unlink_file (filename) < 0)
+ error (0, errno, "unable to remove %s", short_pathname);
+}
+
+static void
+handle_removed (args, len)
+ char *args;
+ int len;
+{
+ call_in_directory (args, remove_entry_and_file, (char *)NULL);
+}
+
+/* Is this the top level (directory containing CVSROOT)? */
+static int
+is_cvsroot_level (pathname)
+ char *pathname;
+{
+ if (strcmp (toplevel_repos, current_parsed_root->directory) != 0)
+ return 0;
+
+ return strchr (pathname, '/') == NULL;
+}
+
+static void
+set_static (data, ent_list, short_pathname, filename)
+ char *data;
+ List *ent_list;
+ char *short_pathname;
+ char *filename;
+{
+ FILE *fp;
+ fp = open_file (CVSADM_ENTSTAT, "w+");
+ if (fclose (fp) == EOF)
+ error (1, errno, "cannot close %s", CVSADM_ENTSTAT);
+}
+
+static void
+handle_set_static_directory (args, len)
+ char *args;
+ int len;
+{
+ if (strcmp (cvs_cmd_name, "export") == 0)
+ {
+ /* Swallow the repository. */
+ read_line (NULL);
+ return;
+ }
+ call_in_directory (args, set_static, (char *)NULL);
+}
+
+static void
+clear_static (data, ent_list, short_pathname, filename)
+ char *data;
+ List *ent_list;
+ char *short_pathname;
+ char *filename;
+{
+ if (unlink_file (CVSADM_ENTSTAT) < 0 && ! existence_error (errno))
+ error (1, errno, "cannot remove file %s", CVSADM_ENTSTAT);
+}
+
+static void
+handle_clear_static_directory (pathname, len)
+ char *pathname;
+ int len;
+{
+ if (strcmp (cvs_cmd_name, "export") == 0)
+ {
+ /* Swallow the repository. */
+ read_line (NULL);
+ return;
+ }
+
+ if (is_cvsroot_level (pathname))
+ {
+ /*
+ * Top level (directory containing CVSROOT). This seems to normally
+ * lack a CVS directory, so don't try to create files in it.
+ */
+ return;
+ }
+ call_in_directory (pathname, clear_static, (char *)NULL);
+}
+
+static void
+set_sticky (data, ent_list, short_pathname, filename)
+ char *data;
+ List *ent_list;
+ char *short_pathname;
+ char *filename;
+{
+ char *tagspec;
+ FILE *f;
+
+ read_line (&tagspec);
+
+ /* FIXME-update-dir: error messages should include the directory. */
+ f = CVS_FOPEN (CVSADM_TAG, "w+");
+ if (f == NULL)
+ {
+ /* Making this non-fatal is a bit of a kludge (see dirs2
+ in testsuite). A better solution would be to avoid having
+ the server tell us about a directory we shouldn't be doing
+ anything with anyway (e.g. by handling directory
+ addition/removal better). */
+ error (0, errno, "cannot open %s", CVSADM_TAG);
+ free (tagspec);
+ return;
+ }
+ if (fprintf (f, "%s\n", tagspec) < 0)
+ error (1, errno, "writing %s", CVSADM_TAG);
+ if (fclose (f) == EOF)
+ error (1, errno, "closing %s", CVSADM_TAG);
+ free (tagspec);
+}
+
+static void
+handle_set_sticky (pathname, len)
+ char *pathname;
+ int len;
+{
+ if (strcmp (cvs_cmd_name, "export") == 0)
+ {
+ /* Swallow the repository. */
+ read_line (NULL);
+ /* Swallow the tag line. */
+ read_line (NULL);
+ return;
+ }
+ if (is_cvsroot_level (pathname))
+ {
+ /*
+ * Top level (directory containing CVSROOT). This seems to normally
+ * lack a CVS directory, so don't try to create files in it.
+ */
+
+ /* Swallow the repository. */
+ read_line (NULL);
+ /* Swallow the tag line. */
+ read_line (NULL);
+ return;
+ }
+
+ call_in_directory (pathname, set_sticky, (char *)NULL);
+}
+
+static void
+clear_sticky (data, ent_list, short_pathname, filename)
+ char *data;
+ List *ent_list;
+ char *short_pathname;
+ char *filename;
+{
+ if (unlink_file (CVSADM_TAG) < 0 && ! existence_error (errno))
+ error (1, errno, "cannot remove %s", CVSADM_TAG);
+}
+
+static void
+handle_clear_sticky (pathname, len)
+ char *pathname;
+ int len;
+{
+ if (strcmp (cvs_cmd_name, "export") == 0)
+ {
+ /* Swallow the repository. */
+ read_line (NULL);
+ return;
+ }
+
+ if (is_cvsroot_level (pathname))
+ {
+ /*
+ * Top level (directory containing CVSROOT). This seems to normally
+ * lack a CVS directory, so don't try to create files in it.
+ */
+ return;
+ }
+
+ call_in_directory (pathname, clear_sticky, (char *)NULL);
+}
+
+
+static void template PROTO ((char *, List *, char *, char *));
+
+static void
+template (data, ent_list, short_pathname, filename)
+ char *data;
+ List *ent_list;
+ char *short_pathname;
+ char *filename;
+{
+ char *buf = xmalloc ( strlen ( short_pathname )
+ + strlen ( CVSADM_TEMPLATE )
+ + 2 );
+ sprintf ( buf, "%s/%s", short_pathname, CVSADM_TEMPLATE );
+ read_counted_file ( CVSADM_TEMPLATE, buf );
+ free ( buf );
+}
+
+static void handle_template PROTO ((char *, int));
+
+static void
+handle_template (pathname, len)
+ char *pathname;
+ int len;
+{
+ call_in_directory (pathname, template, NULL);
+}
+
+
+
+struct save_dir {
+ char *dir;
+ struct save_dir *next;
+};
+
+struct save_dir *prune_candidates;
+
+static void
+add_prune_candidate (dir)
+ const char *dir;
+{
+ struct save_dir *p;
+
+ if ((dir[0] == '.' && dir[1] == '\0')
+ || (prune_candidates != NULL
+ && strcmp (dir, prune_candidates->dir) == 0))
+ return;
+ p = (struct save_dir *) xmalloc (sizeof (struct save_dir));
+ p->dir = xstrdup (dir);
+ p->next = prune_candidates;
+ prune_candidates = p;
+}
+
+static void process_prune_candidates PROTO((void));
+
+static void
+process_prune_candidates ()
+{
+ struct save_dir *p;
+ struct save_dir *q;
+
+ if (toplevel_wd != NULL)
+ {
+ if (CVS_CHDIR (toplevel_wd) < 0)
+ error (1, errno, "could not chdir to %s", toplevel_wd);
+ }
+ for (p = prune_candidates; p != NULL; )
+ {
+ if (isemptydir (p->dir, 1))
+ {
+ char *b;
+
+ if (unlink_file_dir (p->dir) < 0)
+ error (0, errno, "cannot remove %s", p->dir);
+ b = strrchr (p->dir, '/');
+ if (b == NULL)
+ Subdir_Deregister ((List *) NULL, (char *) NULL, p->dir);
+ else
+ {
+ *b = '\0';
+ Subdir_Deregister ((List *) NULL, p->dir, b + 1);
+ }
+ }
+ free (p->dir);
+ q = p->next;
+ free (p);
+ p = q;
+ }
+ prune_candidates = NULL;
+}
+
+/* Send a Repository line. */
+
+static char *last_repos;
+static char *last_update_dir;
+
+static void send_repository PROTO((const char *, const char *, const char *));
+
+static void
+send_repository (dir, repos, update_dir)
+ const char *dir;
+ const char *repos;
+ const char *update_dir;
+{
+ char *adm_name;
+
+ /* FIXME: this is probably not the best place to check; I wish I
+ * knew where in here's callers to really trap this bug. To
+ * reproduce the bug, just do this:
+ *
+ * mkdir junk
+ * cd junk
+ * cvs -d some_repos update foo
+ *
+ * Poof, CVS seg faults and dies! It's because it's trying to
+ * send a NULL string to the server but dies in send_to_server.
+ * That string was supposed to be the repository, but it doesn't
+ * get set because there's no CVSADM dir, and somehow it's not
+ * getting set from the -d argument either... ?
+ */
+ if (repos == NULL)
+ {
+ /* Lame error. I want a real fix but can't stay up to track
+ this down right now. */
+ error (1, 0, "no repository");
+ }
+
+ if (update_dir == NULL || update_dir[0] == '\0')
+ update_dir = ".";
+
+ if (last_repos != NULL
+ && strcmp (repos, last_repos) == 0
+ && last_update_dir != NULL
+ && strcmp (update_dir, last_update_dir) == 0)
+ /* We've already sent it. */
+ return;
+
+ if (client_prune_dirs)
+ add_prune_candidate (update_dir);
+
+ /* Add a directory name to the list of those sent to the
+ server. */
+ if (update_dir && (*update_dir != '\0')
+ && (strcmp (update_dir, ".") != 0)
+ && (findnode (dirs_sent_to_server, update_dir) == NULL))
+ {
+ Node *n;
+ n = getnode ();
+ n->type = NT_UNKNOWN;
+ n->key = xstrdup (update_dir);
+ n->data = NULL;
+
+ if (addnode (dirs_sent_to_server, n))
+ error (1, 0, "cannot add directory %s to list", n->key);
+ }
+
+ /* 80 is large enough for any of CVSADM_*. */
+ adm_name = xmalloc (strlen (dir) + 80);
+
+ send_to_server ("Directory ", 0);
+ {
+ /* Send the directory name. I know that this
+ sort of duplicates code elsewhere, but each
+ case seems slightly different... */
+ char buf[1];
+ const char *p = update_dir;
+ while (*p != '\0')
+ {
+ assert (*p != '\012');
+ if (ISDIRSEP (*p))
+ {
+ buf[0] = '/';
+ send_to_server (buf, 1);
+ }
+ else
+ {
+ buf[0] = *p;
+ send_to_server (buf, 1);
+ }
+ ++p;
+ }
+ }
+ send_to_server ("\012", 1);
+ send_to_server (repos, 0);
+ send_to_server ("\012", 1);
+
+ if (strcmp (cvs_cmd_name, "import")
+ && supported_request ("Static-directory"))
+ {
+ adm_name[0] = '\0';
+ if (dir[0] != '\0')
+ {
+ strcat (adm_name, dir);
+ strcat (adm_name, "/");
+ }
+ strcat (adm_name, CVSADM_ENTSTAT);
+ if (isreadable (adm_name))
+ {
+ send_to_server ("Static-directory\012", 0);
+ }
+ }
+ if (strcmp (cvs_cmd_name, "import")
+ && supported_request ("Sticky"))
+ {
+ FILE *f;
+ if (dir[0] == '\0')
+ strcpy (adm_name, CVSADM_TAG);
+ else
+ sprintf (adm_name, "%s/%s", dir, CVSADM_TAG);
+
+ f = CVS_FOPEN (adm_name, "r");
+ if (f == NULL)
+ {
+ if (! existence_error (errno))
+ error (1, errno, "reading %s", adm_name);
+ }
+ else
+ {
+ char line[80];
+ char *nl = NULL;
+ send_to_server ("Sticky ", 0);
+ while (fgets (line, sizeof (line), f) != NULL)
+ {
+ send_to_server (line, 0);
+ nl = strchr (line, '\n');
+ if (nl != NULL)
+ break;
+ }
+ if (nl == NULL)
+ send_to_server ("\012", 1);
+ if (fclose (f) == EOF)
+ error (0, errno, "closing %s", adm_name);
+ }
+ }
+ free (adm_name);
+ if (last_repos != NULL)
+ free (last_repos);
+ if (last_update_dir != NULL)
+ free (last_update_dir);
+ last_repos = xstrdup (repos);
+ last_update_dir = xstrdup (update_dir);
+}
+
+/* Send a Repository line and set toplevel_repos. */
+
+void
+send_a_repository (dir, repository, update_dir_in)
+ const char *dir;
+ const char *repository;
+ const char *update_dir_in;
+{
+ char *update_dir;
+
+ assert (update_dir_in);
+ update_dir = xstrdup (update_dir_in);
+
+ if (toplevel_repos == NULL && repository != NULL)
+ {
+ if (update_dir[0] == '\0'
+ || (update_dir[0] == '.' && update_dir[1] == '\0'))
+ toplevel_repos = xstrdup (repository);
+ else
+ {
+ /*
+ * Get the repository from a CVS/Repository file if update_dir
+ * is absolute. This is not correct in general, because
+ * the CVS/Repository file might not be the top-level one.
+ * This is for cases like "cvs update /foo/bar" (I'm not
+ * sure it matters what toplevel_repos we get, but it does
+ * matter that we don't hit the "internal error" code below).
+ */
+ if (update_dir[0] == '/')
+ toplevel_repos = Name_Repository (update_dir, update_dir);
+ else
+ {
+ /*
+ * Guess the repository of that directory by looking at a
+ * subdirectory and removing as many pathname components
+ * as are in update_dir. I think that will always (or at
+ * least almost always) be 1.
+ *
+ * So this deals with directories which have been
+ * renamed, though it doesn't necessarily deal with
+ * directories which have been put inside other
+ * directories (and cvs invoked on the containing
+ * directory). I'm not sure the latter case needs to
+ * work.
+ *
+ * 21 Aug 1998: Well, Mr. Above-Comment-Writer, it
+ * does need to work after all. When we are using the
+ * client in a multi-cvsroot environment, it will be
+ * fairly common that we have the above case (e.g.,
+ * cwd checked out from one repository but
+ * subdirectory checked out from another). We can't
+ * assume that by walking up a directory in our wd we
+ * necessarily walk up a directory in the repository.
+ */
+ /*
+ * This gets toplevel_repos wrong for "cvs update ../foo"
+ * but I'm not sure toplevel_repos matters in that case.
+ */
+
+ int repository_len, update_dir_len;
+
+ strip_trailing_slashes (update_dir);
+
+ repository_len = strlen (repository);
+ update_dir_len = strlen (update_dir);
+
+ /* Try to remove the path components in UPDATE_DIR
+ from REPOSITORY. If the path elements don't exist
+ in REPOSITORY, or the removal of those path
+ elements mean that we "step above"
+ current_parsed_root->directory, set toplevel_repos to
+ current_parsed_root->directory. */
+ if ((repository_len > update_dir_len)
+ && (strcmp (repository + repository_len - update_dir_len,
+ update_dir) == 0)
+ /* TOPLEVEL_REPOS shouldn't be above current_parsed_root->directory */
+ && ((size_t)(repository_len - update_dir_len)
+ > strlen (current_parsed_root->directory)))
+ {
+ /* The repository name contains UPDATE_DIR. Set
+ toplevel_repos to the repository name without
+ UPDATE_DIR. */
+
+ toplevel_repos = xmalloc (repository_len - update_dir_len);
+ /* Note that we don't copy the trailing '/'. */
+ strncpy (toplevel_repos, repository,
+ repository_len - update_dir_len - 1);
+ toplevel_repos[repository_len - update_dir_len - 1] = '\0';
+ }
+ else
+ {
+ toplevel_repos = xstrdup (current_parsed_root->directory);
+ }
+ }
+ }
+ }
+
+ send_repository (dir, repository, update_dir);
+ free (update_dir);
+}
+
+
+
+/* The "expanded" modules. */
+static int modules_count;
+static int modules_allocated;
+static char **modules_vector;
+
+static void
+handle_module_expansion (args, len)
+ char *args;
+ int len;
+{
+ if (modules_vector == NULL)
+ {
+ modules_allocated = 1; /* Small for testing */
+ modules_vector = (char **) xmalloc
+ (modules_allocated * sizeof (modules_vector[0]));
+ }
+ else if (modules_count >= modules_allocated)
+ {
+ modules_allocated *= 2;
+ modules_vector = (char **) xrealloc
+ ((char *) modules_vector,
+ modules_allocated * sizeof (modules_vector[0]));
+ }
+ modules_vector[modules_count] = xmalloc (strlen (args) + 1);
+ strcpy (modules_vector[modules_count], args);
+ ++modules_count;
+}
+
+/* Original, not "expanded" modules. */
+static int module_argc;
+static char **module_argv;
+
+void
+client_expand_modules (argc, argv, local)
+ int argc;
+ char **argv;
+ int local;
+{
+ int errs;
+ int i;
+
+ module_argc = argc;
+ module_argv = (char **) xmalloc ((argc + 1) * sizeof (module_argv[0]));
+ for (i = 0; i < argc; ++i)
+ module_argv[i] = xstrdup (argv[i]);
+ module_argv[argc] = NULL;
+
+ for (i = 0; i < argc; ++i)
+ send_arg (argv[i]);
+ send_a_repository ("", current_parsed_root->directory, "");
+
+ send_to_server ("expand-modules\012", 0);
+
+ errs = get_server_responses ();
+ if (last_repos != NULL)
+ free (last_repos);
+ last_repos = NULL;
+ if (last_update_dir != NULL)
+ free (last_update_dir);
+ last_update_dir = NULL;
+ if (errs)
+ error (errs, 0, "cannot expand modules");
+}
+
+void
+client_send_expansions (local, where, build_dirs)
+ int local;
+ char *where;
+ int build_dirs;
+{
+ int i;
+ char *argv[1];
+
+ /* Send the original module names. The "expanded" module name might
+ not be suitable as an argument to a co request (e.g. it might be
+ the result of a -d argument in the modules file). It might be
+ cleaner if we genuinely expanded module names, all the way to a
+ local directory and repository, but that isn't the way it works
+ now. */
+ send_file_names (module_argc, module_argv, 0);
+
+ for (i = 0; i < modules_count; ++i)
+ {
+ argv[0] = where ? where : modules_vector[i];
+ if (isfile (argv[0]))
+ send_files (1, argv, local, 0, build_dirs ? SEND_BUILD_DIRS : 0);
+ }
+ send_a_repository ("", current_parsed_root->directory, "");
+}
+
+void
+client_nonexpanded_setup ()
+{
+ send_a_repository ("", current_parsed_root->directory, "");
+}
+
+/* Receive a cvswrappers line from the server; it must be a line
+ containing an RCS option (e.g., "*.exe -k 'b'").
+
+ Note that this doesn't try to handle -t/-f options (which are a
+ whole separate issue which noone has thought much about, as far
+ as I know).
+
+ We need to know the keyword expansion mode so we know whether to
+ read the file in text or binary mode. */
+
+static void
+handle_wrapper_rcs_option (args, len)
+ char *args;
+ int len;
+{
+ char *p;
+
+ /* Enforce the notes in cvsclient.texi about how the response is not
+ as free-form as it looks. */
+ p = strchr (args, ' ');
+ if (p == NULL)
+ goto handle_error;
+ if (*++p != '-'
+ || *++p != 'k'
+ || *++p != ' '
+ || *++p != '\'')
+ goto handle_error;
+ if (strchr (p, '\'') == NULL)
+ goto handle_error;
+
+ /* Add server-side cvswrappers line to our wrapper list. */
+ wrap_add (args, 0);
+ return;
+ handle_error:
+ error (0, errno, "protocol error: ignoring invalid wrappers %s", args);
+}
+
+
+static void
+handle_m (args, len)
+ char *args;
+ int len;
+{
+ /* In the case where stdout and stderr point to the same place,
+ fflushing stderr will make output happen in the correct order.
+ Often stderr will be line-buffered and this won't be needed,
+ but not always (is that true? I think the comment is probably
+ based on being confused between default buffering between
+ stdout and stderr. But I'm not sure). */
+ fflush (stderr);
+ fwrite (args, len, sizeof (*args), stdout);
+ putc ('\n', stdout);
+}
+
+static void handle_mbinary PROTO ((char *, int));
+
+static void
+handle_mbinary (args, len)
+ char *args;
+ int len;
+{
+ char *size_string;
+ size_t size;
+ size_t totalread;
+ size_t nread;
+ size_t toread;
+ char buf[8192];
+
+ /* See comment at handle_m about (non)flush of stderr. */
+
+ /* Get the size. */
+ read_line (&size_string);
+ size = strto_file_size (size_string);
+ free (size_string);
+
+ /* OK, now get all the data. The algorithm here is that we read
+ as much as the network wants to give us in
+ try_read_from_server, and then we output it all, and then
+ repeat, until we get all the data. */
+ totalread = 0;
+ while (totalread < size)
+ {
+ toread = size - totalread;
+ if (toread > sizeof buf)
+ toread = sizeof buf;
+
+ nread = try_read_from_server (buf, toread);
+ cvs_output_binary (buf, nread);
+ totalread += nread;
+ }
+}
+
+static void
+handle_e (args, len)
+ char *args;
+ int len;
+{
+ /* In the case where stdout and stderr point to the same place,
+ fflushing stdout will make output happen in the correct order. */
+ fflush (stdout);
+ fwrite (args, len, sizeof (*args), stderr);
+ putc ('\n', stderr);
+}
+
+/*ARGSUSED*/
+static void
+handle_f (args, len)
+ char *args;
+ int len;
+{
+ fflush (stderr);
+}
+
+static void handle_mt PROTO ((char *, int));
+
+static void
+handle_mt (args, len)
+ char *args;
+ int len;
+{
+ char *p;
+ char *tag = args;
+ char *text;
+
+ /* See comment at handle_m for more details. */
+ fflush (stderr);
+
+ p = strchr (args, ' ');
+ if (p == NULL)
+ text = NULL;
+ else
+ {
+ *p++ = '\0';
+ text = p;
+ }
+
+ switch (tag[0])
+ {
+ case '+':
+ if (strcmp (tag, "+updated") == 0)
+ updated_seen = 1;
+ else if (strcmp (tag, "+importmergecmd") == 0)
+ importmergecmd.seen = 1;
+ break;
+ case '-':
+ if (strcmp (tag, "-updated") == 0)
+ updated_seen = 0;
+ else if (strcmp (tag, "-importmergecmd") == 0)
+ {
+ char buf[80];
+
+ /* Now that we have gathered the information, we can
+ output the suggested merge command. */
+
+ if (importmergecmd.conflicts == 0
+ || importmergecmd.mergetag1 == NULL
+ || importmergecmd.mergetag2 == NULL
+ || importmergecmd.repository == NULL)
+ {
+ error (0, 0,
+ "invalid server: incomplete importmergecmd tags");
+ break;
+ }
+
+ sprintf (buf, "\n%d conflicts created by this import.\n",
+ importmergecmd.conflicts);
+ cvs_output (buf, 0);
+ cvs_output ("Use the following command to help the merge:\n\n",
+ 0);
+ cvs_output ("\t", 1);
+ cvs_output (program_name, 0);
+ if (CVSroot_cmdline != NULL)
+ {
+ cvs_output (" -d ", 0);
+ cvs_output (CVSroot_cmdline, 0);
+ }
+ cvs_output (" checkout -j", 0);
+ cvs_output (importmergecmd.mergetag1, 0);
+ cvs_output (" -j", 0);
+ cvs_output (importmergecmd.mergetag2, 0);
+ cvs_output (" ", 1);
+ cvs_output (importmergecmd.repository, 0);
+ cvs_output ("\n\n", 0);
+
+ /* Clear the static variables so that everything is
+ ready for any subsequent importmergecmd tag. */
+ importmergecmd.conflicts = 0;
+ free (importmergecmd.mergetag1);
+ importmergecmd.mergetag1 = NULL;
+ free (importmergecmd.mergetag2);
+ importmergecmd.mergetag2 = NULL;
+ free (importmergecmd.repository);
+ importmergecmd.repository = NULL;
+
+ importmergecmd.seen = 0;
+ }
+ break;
+ default:
+ if (updated_seen)
+ {
+ if (strcmp (tag, "fname") == 0)
+ {
+ if (updated_fname != NULL)
+ {
+ /* Output the previous message now. This can happen
+ if there was no Update-existing or other such
+ response, due to the -n global option. */
+ cvs_output ("U ", 0);
+ cvs_output (updated_fname, 0);
+ cvs_output ("\n", 1);
+ free (updated_fname);
+ }
+ updated_fname = xstrdup (text);
+ }
+ /* Swallow all other tags. Either they are extraneous
+ or they reflect future extensions that we can
+ safely ignore. */
+ }
+ else if (importmergecmd.seen)
+ {
+ if (strcmp (tag, "conflicts") == 0)
+ importmergecmd.conflicts = text ? atoi (text) : -1;
+ else if (strcmp (tag, "mergetag1") == 0)
+ importmergecmd.mergetag1 = xstrdup (text);
+ else if (strcmp (tag, "mergetag2") == 0)
+ importmergecmd.mergetag2 = xstrdup (text);
+ else if (strcmp (tag, "repository") == 0)
+ importmergecmd.repository = xstrdup (text);
+ /* Swallow all other tags. Either they are text for
+ which we are going to print our own version when we
+ see -importmergecmd, or they are future extensions
+ we can safely ignore. */
+ }
+ else if (strcmp (tag, "newline") == 0)
+ printf ("\n");
+ else if (text != NULL)
+ printf ("%s", text);
+ }
+}
+
+#endif /* CLIENT_SUPPORT */
+#if defined(CLIENT_SUPPORT) || defined(SERVER_SUPPORT)
+
+/* This table must be writeable if the server code is included. */
+struct response responses[] =
+{
+#ifdef CLIENT_SUPPORT
+#define RSP_LINE(n, f, t, s) {n, f, t, s}
+#else /* ! CLIENT_SUPPORT */
+#define RSP_LINE(n, f, t, s) {n, s}
+#endif /* CLIENT_SUPPORT */
+
+ RSP_LINE("ok", handle_ok, response_type_ok, rs_essential),
+ RSP_LINE("error", handle_error, response_type_error, rs_essential),
+ RSP_LINE("Valid-requests", handle_valid_requests, response_type_normal,
+ rs_essential),
+ RSP_LINE("Checked-in", handle_checked_in, response_type_normal,
+ rs_essential),
+ RSP_LINE("New-entry", handle_new_entry, response_type_normal, rs_optional),
+ RSP_LINE("Checksum", handle_checksum, response_type_normal, rs_optional),
+ RSP_LINE("Copy-file", handle_copy_file, response_type_normal, rs_optional),
+ RSP_LINE("Updated", handle_updated, response_type_normal, rs_essential),
+ RSP_LINE("Created", handle_created, response_type_normal, rs_optional),
+ RSP_LINE("Update-existing", handle_update_existing, response_type_normal,
+ rs_optional),
+ RSP_LINE("Merged", handle_merged, response_type_normal, rs_essential),
+ RSP_LINE("Patched", handle_patched, response_type_normal, rs_optional),
+ RSP_LINE("Rcs-diff", handle_rcs_diff, response_type_normal, rs_optional),
+ RSP_LINE("Mode", handle_mode, response_type_normal, rs_optional),
+ RSP_LINE("Mod-time", handle_mod_time, response_type_normal, rs_optional),
+ RSP_LINE("Removed", handle_removed, response_type_normal, rs_essential),
+ RSP_LINE("Remove-entry", handle_remove_entry, response_type_normal,
+ rs_optional),
+ RSP_LINE("Set-static-directory", handle_set_static_directory,
+ response_type_normal,
+ rs_optional),
+ RSP_LINE("Clear-static-directory", handle_clear_static_directory,
+ response_type_normal,
+ rs_optional),
+ RSP_LINE("Set-sticky", handle_set_sticky, response_type_normal,
+ rs_optional),
+ RSP_LINE("Clear-sticky", handle_clear_sticky, response_type_normal,
+ rs_optional),
+ RSP_LINE("Template", handle_template, response_type_normal,
+ rs_optional),
+ RSP_LINE("Notified", handle_notified, response_type_normal, rs_optional),
+ RSP_LINE("Module-expansion", handle_module_expansion, response_type_normal,
+ rs_optional),
+ RSP_LINE("Wrapper-rcsOption", handle_wrapper_rcs_option,
+ response_type_normal,
+ rs_optional),
+ RSP_LINE("M", handle_m, response_type_normal, rs_essential),
+ RSP_LINE("Mbinary", handle_mbinary, response_type_normal, rs_optional),
+ RSP_LINE("E", handle_e, response_type_normal, rs_essential),
+ RSP_LINE("F", handle_f, response_type_normal, rs_optional),
+ RSP_LINE("MT", handle_mt, response_type_normal, rs_optional),
+ /* Possibly should be response_type_error. */
+ RSP_LINE(NULL, NULL, response_type_normal, rs_essential)
+
+#undef RSP_LINE
+};
+
+#endif /* CLIENT_SUPPORT or SERVER_SUPPORT */
+#ifdef CLIENT_SUPPORT
+
+/*
+ * If LEN is 0, then send_to_server() computes string's length itself.
+ *
+ * Therefore, pass the real length when transmitting data that might
+ * contain 0's.
+ */
+void
+send_to_server (str, len)
+ const char *str;
+ size_t len;
+{
+ static int nbytes;
+
+ if (len == 0)
+ len = strlen (str);
+
+ buf_output (to_server, str, len);
+
+ /* There is no reason not to send data to the server, so do it
+ whenever we've accumulated enough information in the buffer to
+ make it worth sending. */
+ nbytes += len;
+ if (nbytes >= 2 * BUFFER_DATA_SIZE)
+ {
+ int status;
+
+ status = buf_send_output (to_server);
+ if (status != 0)
+ error (1, status, "error writing to server");
+ nbytes = 0;
+ }
+}
+
+/* Read up to LEN bytes from the server. Returns actual number of
+ bytes read, which will always be at least one; blocks if there is
+ no data available at all. Gives a fatal error on EOF or error. */
+static size_t
+try_read_from_server (buf, len)
+ char *buf;
+ size_t len;
+{
+ int status, nread;
+ char *data;
+
+ status = buf_read_data (from_server, len, &data, &nread);
+ if (status != 0)
+ {
+ if (status == -1)
+ error (1, 0,
+ "end of file from server (consult above messages if any)");
+ else if (status == -2)
+ error (1, 0, "out of memory");
+ else
+ error (1, status, "reading from server");
+ }
+
+ memcpy (buf, data, nread);
+
+ return nread;
+}
+
+/*
+ * Read LEN bytes from the server or die trying.
+ */
+void
+read_from_server (buf, len)
+ char *buf;
+ size_t len;
+{
+ size_t red = 0;
+ while (red < len)
+ {
+ red += try_read_from_server (buf + red, len - red);
+ if (red == len)
+ break;
+ }
+}
+
+/*
+ * Get some server responses and process them. Returns nonzero for
+ * error, 0 for success. */
+int
+get_server_responses ()
+{
+ struct response *rs;
+ do
+ {
+ char *cmd;
+ int len;
+
+ len = read_line (&cmd);
+ for (rs = responses; rs->name != NULL; ++rs)
+ if (strncmp (cmd, rs->name, strlen (rs->name)) == 0)
+ {
+ int cmdlen = strlen (rs->name);
+ if (cmd[cmdlen] == '\0')
+ ;
+ else if (cmd[cmdlen] == ' ')
+ ++cmdlen;
+ else
+ /*
+ * The first len characters match, but it's a different
+ * response. e.g. the response is "oklahoma" but we
+ * matched "ok".
+ */
+ continue;
+ (*rs->func) (cmd + cmdlen, len - cmdlen);
+ break;
+ }
+ if (rs->name == NULL)
+ /* It's OK to print just to the first '\0'. */
+ /* We might want to handle control characters and the like
+ in some other way other than just sending them to stdout.
+ One common reason for this error is if people use :ext:
+ with a version of rsh which is doing CRLF translation or
+ something, and so the client gets "ok^M" instead of "ok".
+ Right now that will tend to print part of this error
+ message over the other part of it. It seems like we could
+ do better (either in general, by quoting or omitting all
+ control characters, and/or specifically, by detecting the CRLF
+ case and printing a specific error message). */
+ error (0, 0,
+ "warning: unrecognized response `%s' from cvs server",
+ cmd);
+ free (cmd);
+ } while (rs->type == response_type_normal);
+
+ if (updated_fname != NULL)
+ {
+ /* Output the previous message now. This can happen
+ if there was no Update-existing or other such
+ response, due to the -n global option. */
+ cvs_output ("U ", 0);
+ cvs_output (updated_fname, 0);
+ cvs_output ("\n", 1);
+ free (updated_fname);
+ updated_fname = NULL;
+ }
+
+ if (rs->type == response_type_error)
+ return 1;
+ if (failure_exit)
+ return 1;
+ return 0;
+}
+
+
+
+/* Get the responses and then close the connection. */
+
+/*
+ * Flag var; we'll set it in start_server() and not one of its
+ * callees, such as start_rsh_server(). This means that there might
+ * be a small window between the starting of the server and the
+ * setting of this var, but all the code in that window shouldn't care
+ * because it's busy checking return values to see if the server got
+ * started successfully anyway.
+ */
+int server_started = 0;
+
+int
+get_responses_and_close ()
+{
+ int errs = get_server_responses ();
+ int status;
+
+ /* The following is necessary when working with multiple cvsroots, at least
+ * with commit. It used to be buried nicely in do_deferred_progs() before
+ * that function was removed. I suspect it wouldn't be necessary if
+ * call_in_directory() saved its working directory via save_cwd() before
+ * changing its directory and restored the saved working directory via
+ * restore_cwd() before exiting. Of course, calling CVS_CHDIR only once,
+ * here, may be more efficient.
+ */
+ if( toplevel_wd != NULL )
+ {
+ if( CVS_CHDIR( toplevel_wd ) < 0 )
+ error( 1, errno, "could not chdir to %s", toplevel_wd );
+ }
+
+ if (client_prune_dirs)
+ process_prune_candidates ();
+
+ /* First we shut down TO_SERVER. That tells the server that its input is
+ * finished. It then shuts down the buffer it is sending to us, at which
+ * point our shut down of FROM_SERVER will complete.
+ */
+
+ status = buf_shutdown (to_server);
+ if (status != 0)
+ error (0, status, "shutting down buffer to server");
+ buf_free (to_server);
+ to_server = NULL;
+
+ status = buf_shutdown (from_server);
+ if (status != 0)
+ error (0, status, "shutting down buffer from server");
+ buf_free (from_server);
+ from_server = NULL;
+ server_started = 0;
+
+ /* see if we need to sleep before returning to avoid time-stamp races */
+ if (last_register_time)
+ {
+ sleep_past (last_register_time);
+ }
+
+ return errs;
+}
+
+#ifndef NO_EXT_METHOD
+static void start_rsh_server PROTO((cvsroot_t *, struct buffer **, struct buffer **));
+#endif
+
+int
+supported_request (name)
+ char *name;
+{
+ struct request *rq;
+
+ for (rq = requests; rq->name; rq++)
+ if (!strcmp (rq->name, name))
+ return (rq->flags & RQ_SUPPORTED) != 0;
+ error (1, 0, "internal error: testing support for unknown option?");
+ /* NOTREACHED */
+ return 0;
+}
+
+
+
+#if defined (AUTH_CLIENT_SUPPORT) || defined (HAVE_KERBEROS) || defined (HAVE_GSSAPI)
+static struct hostent *init_sockaddr PROTO ((struct sockaddr_in *, char *,
+ unsigned int));
+
+static struct hostent *
+init_sockaddr (name, hostname, port)
+ struct sockaddr_in *name;
+ char *hostname;
+ unsigned int port;
+{
+ struct hostent *hostinfo;
+ unsigned short shortport = port;
+
+ memset (name, 0, sizeof (*name));
+ name->sin_family = AF_INET;
+ name->sin_port = htons (shortport);
+ hostinfo = gethostbyname (hostname);
+ if (hostinfo == NULL)
+ {
+ fprintf (stderr, "Unknown host %s.\n", hostname);
+ error_exit ();
+ }
+ name->sin_addr = *(struct in_addr *) hostinfo->h_addr;
+ return hostinfo;
+}
+
+
+
+/* Generic function to do port number lookup tasks.
+ *
+ * In order of precedence, will return:
+ * getenv (envname), if defined
+ * getservbyname (portname), if defined
+ * defaultport
+ */
+static int
+get_port_number (envname, portname, defaultport)
+ const char *envname;
+ const char *portname;
+ int defaultport;
+{
+ struct servent *s;
+ char *port_s;
+
+ if (envname && (port_s = getenv (envname)))
+ {
+ int port = atoi (port_s);
+ if (port <= 0)
+ {
+ error (0, 0, "%s must be a positive integer! If you", envname);
+ error (0, 0, "are trying to force a connection via rsh, please");
+ error (0, 0, "put \":server:\" at the beginning of your CVSROOT");
+ error (1, 0, "variable.");
+ }
+ return port;
+ }
+ else if (portname && (s = getservbyname (portname, "tcp")))
+ return ntohs (s->s_port);
+ else
+ return defaultport;
+}
+
+
+
+/* get the port number for a client to connect to based on the port
+ * and method of a cvsroot_t.
+ *
+ * we do this here instead of in parse_cvsroot so that we can keep network
+ * code confined to a localized area and also to delay the lookup until the
+ * last possible moment so it remains possible to run cvs client commands that
+ * skip opening connections to the server (i.e. skip network operations
+ * entirely)
+ *
+ * and yes, I know none of the commands do that now, but here's to planning
+ * for the future, eh? cheers.
+ *
+ * FIXME - We could cache the port lookup safely right now as we never change
+ * it for a single root on the fly, but we'd have to un'const some other
+ * functions - REMOVE_FIXME? This may be unecessary. We're talking about,
+ * what, usually one, sometimes two lookups of the port per invocation. I
+ * think twice is by far the rarer of the two cases - only the login function
+ * will need to do it to save the canonical CVSROOT. -DRP
+ */
+int
+get_cvs_port_number (root)
+ const cvsroot_t *root;
+{
+
+ if (root->port) return root->port;
+
+ switch (root->method)
+ {
+# ifdef HAVE_GSSAPI
+ case gserver_method:
+# endif /* HAVE_GSSAPI */
+# ifdef AUTH_CLIENT_SUPPORT
+ case pserver_method:
+# endif /* AUTH_CLIENT_SUPPORT */
+# if defined (AUTH_CLIENT_SUPPORT) || defined (HAVE_GSSAPI)
+ return get_port_number ("CVS_CLIENT_PORT", "cvspserver", CVS_AUTH_PORT);
+# endif /* defined (AUTH_CLIENT_SUPPORT) || defined (HAVE_GSSAPI) */
+# ifdef HAVE_KERBEROS
+ case kserver_method:
+ return get_port_number ("CVS_CLIENT_PORT", "cvs", CVS_PORT);
+# endif /* HAVE_KERBEROS */
+ default:
+ error(1, EINVAL, "internal error: get_cvs_port_number called for invalid connection method (%s)",
+ method_names[root->method]);
+ break;
+ }
+ /* NOTREACHED */
+ return -1;
+}
+
+
+
+void
+make_bufs_from_fds (tofd, fromfd, child_pid, to_server, from_server, is_sock)
+ int tofd;
+ int fromfd;
+ int child_pid;
+ struct buffer **to_server;
+ struct buffer **from_server;
+ int is_sock;
+{
+ FILE *to_server_fp;
+ FILE *from_server_fp;
+
+# ifdef NO_SOCKET_TO_FD
+ if (is_sock)
+ {
+ assert (tofd == fromfd);
+ *to_server = socket_buffer_initialize (tofd, 0,
+ (BUFMEMERRPROC) NULL);
+ *from_server = socket_buffer_initialize (tofd, 1,
+ (BUFMEMERRPROC) NULL);
+ }
+ else
+# endif /* NO_SOCKET_TO_FD */
+ {
+ /* todo: some OS's don't need these calls... */
+ close_on_exec (tofd);
+ close_on_exec (fromfd);
+
+ /* SCO 3 and AIX have a nasty bug in the I/O libraries which precludes
+ fdopening the same file descriptor twice, so dup it if it is the
+ same. */
+ if (tofd == fromfd)
+ {
+ fromfd = dup (tofd);
+ if (fromfd < 0)
+ error (1, errno, "cannot dup net connection");
+ }
+
+ /* These will use binary mode on systems which have it. */
+ /*
+ * Also, we know that from_server is shut down second, so we pass
+ * child_pid in there. In theory, it should be stored in both
+ * buffers with a ref count...
+ */
+ to_server_fp = fdopen (tofd, FOPEN_BINARY_WRITE);
+ if (to_server_fp == NULL)
+ error (1, errno, "cannot fdopen %d for write", tofd);
+ *to_server = stdio_buffer_initialize (to_server_fp, 0, 0,
+ (BUFMEMERRPROC) NULL);
+
+ from_server_fp = fdopen (fromfd, FOPEN_BINARY_READ);
+ if (from_server_fp == NULL)
+ error (1, errno, "cannot fdopen %d for read", fromfd);
+ *from_server = stdio_buffer_initialize (from_server_fp, child_pid, 1,
+ (BUFMEMERRPROC) NULL);
+ }
+}
+#endif /* defined (AUTH_CLIENT_SUPPORT) || defined (HAVE_KERBEROS) || defined(HAVE_GSSAPI) */
+
+
+
+#if defined (AUTH_CLIENT_SUPPORT) || defined(HAVE_GSSAPI)
+/* Connect to the authenticating server.
+
+ If VERIFY_ONLY is non-zero, then just verify that the password is
+ correct and then shutdown the connection.
+
+ If VERIFY_ONLY is 0, then really connect to the server.
+
+ If DO_GSSAPI is non-zero, then we use GSSAPI authentication rather
+ than the pserver password authentication.
+
+ If we fail to connect or if access is denied, then die with fatal
+ error. */
+void
+connect_to_pserver (root, to_server_p, from_server_p, verify_only, do_gssapi)
+ cvsroot_t *root;
+ struct buffer **to_server_p;
+ struct buffer **from_server_p;
+ int verify_only;
+ int do_gssapi;
+{
+ int sock;
+ int port_number;
+ struct sockaddr_in client_sai;
+ struct hostent *hostinfo;
+ struct buffer *to_server, *from_server;
+
+ sock = socket (AF_INET, SOCK_STREAM, 0);
+ if (sock == -1)
+ {
+ error (1, 0, "cannot create socket: %s", SOCK_STRERROR (SOCK_ERRNO));
+ }
+ port_number = get_cvs_port_number (root);
+ hostinfo = init_sockaddr (&client_sai, root->hostname, port_number);
+ if (trace)
+ {
+ fprintf (stderr, " -> Connecting to %s(%s):%d\n",
+ root->hostname,
+ inet_ntoa (client_sai.sin_addr), port_number);
+ }
+ if (connect (sock, (struct sockaddr *) &client_sai, sizeof (client_sai))
+ < 0)
+ error (1, 0, "connect to %s(%s):%d failed: %s",
+ root->hostname,
+ inet_ntoa (client_sai.sin_addr),
+ port_number, SOCK_STRERROR (SOCK_ERRNO));
+
+ make_bufs_from_fds (sock, sock, 0, &to_server, &from_server, 1);
+
+ auth_server (root, to_server, from_server, verify_only, do_gssapi, hostinfo);
+
+ if (verify_only)
+ {
+ int status;
+
+ status = buf_shutdown (to_server);
+ if (status != 0)
+ error (0, status, "shutting down buffer to server");
+ buf_free (to_server);
+ to_server = NULL;
+
+ status = buf_shutdown (from_server);
+ if (status != 0)
+ error (0, status, "shutting down buffer from server");
+ buf_free (from_server);
+ from_server = NULL;
+
+ /* Don't need to set server_started = 0 since we don't set it to 1
+ * until returning from this call.
+ */
+ }
+ else
+ {
+ *to_server_p = to_server;
+ *from_server_p = from_server;
+ }
+
+ return;
+}
+
+
+
+static void
+auth_server (root, lto_server, lfrom_server, verify_only, do_gssapi, hostinfo)
+ cvsroot_t *root;
+ struct buffer *lto_server;
+ struct buffer *lfrom_server;
+ int verify_only;
+ int do_gssapi;
+ struct hostent *hostinfo;
+{
+ char *username = ""; /* the username we use to connect */
+ char no_passwd = 0; /* gets set if no password found */
+
+ /* FIXME!!!!!!!!!!!!!!!!!!
+ *
+ * THIS IS REALLY UGLY!
+ *
+ * I'm setting the globals here so we can make calls to send_to_server &
+ * read_line. This happens again _after_ we return if we're not in
+ * verify_only mode. We should be relying on the values we passed in, but
+ * sent_to_server and read_line don't require an outside buf yet.
+ */
+ to_server = lto_server;
+ from_server = lfrom_server;
+
+ /* Run the authorization mini-protocol before anything else. */
+ if (do_gssapi)
+ {
+# ifdef HAVE_GSSAPI
+ FILE *fp = stdio_buffer_get_file(lto_server);
+ int fd = fp ? fileno(fp) : -1;
+ struct stat s;
+
+ if ((fd < 0) || (fstat (fd, &s) < 0) || !S_ISSOCK(s.st_mode))
+ {
+ error (1, 0, "gserver currently only enabled for socket connections");
+ }
+
+ if (! connect_to_gserver (root, fd, hostinfo))
+ {
+ error (1, 0,
+ "authorization failed: server %s rejected access to %s",
+ root->hostname, root->directory);
+ }
+# else /* ! HAVE_GSSAPI */
+ error (1, 0, "INTERNAL ERROR: This client does not support GSSAPI authentication");
+# endif /* HAVE_GSSAPI */
+ }
+ else /* ! do_gssapi */
+ {
+# ifdef AUTH_CLIENT_SUPPORT
+ char *begin = NULL;
+ char *password = NULL;
+ char *end = NULL;
+
+ if (verify_only)
+ {
+ begin = "BEGIN VERIFICATION REQUEST";
+ end = "END VERIFICATION REQUEST";
+ }
+ else
+ {
+ begin = "BEGIN AUTH REQUEST";
+ end = "END AUTH REQUEST";
+ }
+
+ /* Get the password, probably from ~/.cvspass. */
+ password = get_cvs_password ();
+ username = root->username ? root->username : getcaller();
+
+ /* Send the empty string by default. This is so anonymous CVS
+ access doesn't require client to have done "cvs login". */
+ if (password == NULL)
+ {
+ no_passwd = 1;
+ password = scramble ("");
+ }
+
+ /* Announce that we're starting the authorization protocol. */
+ send_to_server(begin, 0);
+ send_to_server("\012", 1);
+
+ /* Send the data the server needs. */
+ send_to_server(root->directory, 0);
+ send_to_server("\012", 1);
+ send_to_server(username, 0);
+ send_to_server("\012", 1);
+ send_to_server(password, 0);
+ send_to_server("\012", 1);
+
+ /* Announce that we're ending the authorization protocol. */
+ send_to_server(end, 0);
+ send_to_server("\012", 1);
+
+ free_cvs_password (password);
+ password = NULL;
+# else /* ! AUTH_CLIENT_SUPPORT */
+ error (1, 0, "INTERNAL ERROR: This client does not support pserver authentication");
+# endif /* AUTH_CLIENT_SUPPORT */
+ } /* if (do_gssapi) */
+
+ {
+ char *read_buf;
+
+ /* Loop, getting responses from the server. */
+ while (1)
+ {
+ read_line (&read_buf);
+
+ if (strcmp (read_buf, "I HATE YOU") == 0)
+ {
+ /* Authorization not granted.
+ *
+ * This is a little confusing since we can reach this while loop in GSSAPI
+ * mode, but if GSSAPI authentication failed, we already jumped to the
+ * rejected label (there is no case where the connect_to_gserver function
+ * can return 1 and we will not receive "I LOVE YOU" from the server, barring
+ * broken connections and garbled messages, of course).
+ *
+ * i.e. This is a pserver specific error message and should be since
+ * GSSAPI doesn't use username.
+ */
+ error (0, 0,
+ "authorization failed: server %s rejected access to %s for user %s",
+ root->hostname, root->directory, username);
+
+ /* Output a special error message if authentication was attempted
+ with no password -- the user should be made aware that they may
+ have missed a step. */
+ if (no_passwd)
+ {
+ error (0, 0,
+ "used empty password; try \"cvs login\" with a real password");
+ }
+ error_exit();
+ }
+ else if (strncmp (read_buf, "E ", 2) == 0)
+ {
+ fprintf (stderr, "%s\n", read_buf + 2);
+
+ /* Continue with the authentication protocol. */
+ }
+ else if (strncmp (read_buf, "error ", 6) == 0)
+ {
+ char *p;
+
+ /* First skip the code. */
+ p = read_buf + 6;
+ while (*p != ' ' && *p != '\0')
+ ++p;
+
+ /* Skip the space that follows the code. */
+ if (*p == ' ')
+ ++p;
+
+ /* Now output the text. */
+ fprintf (stderr, "%s\n", p);
+ error_exit();
+ }
+ else if (strcmp (read_buf, "I LOVE YOU") == 0)
+ {
+ free (read_buf);
+ break;
+ }
+ else
+ {
+ error (1, 0,
+ "unrecognized auth response from %s: %s",
+ root->hostname, read_buf);
+ }
+ free (read_buf);
+ }
+ }
+}
+#endif /* defined (AUTH_CLIENT_SUPPORT) || defined(HAVE_GSSAPI) */
+
+
+
+#ifdef CLIENT_SUPPORT
+/* void
+ * connect_to_forked_server ( struct buffer **to_server,
+ * struct buffer **from_server )
+ *
+ * Connect to a forked server process.
+ */
+void
+connect_to_forked_server (to_server, from_server)
+ struct buffer **to_server;
+ struct buffer **from_server;
+{
+ int tofd, fromfd;
+ int child_pid;
+
+ /* This is pretty simple. All we need to do is choose the correct
+ cvs binary and call piped_child. */
+
+ const char *command[3];
+
+ command[0] = getenv ("CVS_SERVER");
+ if (! command[0])
+ command[0] = program_path;
+
+ command[1] = "server";
+ command[2] = NULL;
+
+ if (trace)
+ {
+ fprintf (stderr, " -> Forking server: %s %s\n", command[0], command[1]);
+ }
+
+ child_pid = piped_child (command, &tofd, &fromfd, 0);
+ if (child_pid < 0)
+ error (1, 0, "could not fork server process");
+
+ make_bufs_from_fds (tofd, fromfd, child_pid, to_server, from_server, 0);
+}
+#endif /* CLIENT_SUPPORT */
+
+
+
+#ifdef HAVE_KERBEROS
+/* This function has not been changed to deal with NO_SOCKET_TO_FD
+ (i.e., systems on which sockets cannot be converted to file
+ descriptors). The first person to try building a kerberos client
+ on such a system (OS/2, Windows 95, and maybe others) will have to
+ take care of this. */
+void
+start_tcp_server (root, to_server, from_server)
+ cvsroot_t *root;
+ struct buffer **to_server;
+ struct buffer **from_server;
+{
+ int s;
+ const char *portenv;
+ int port;
+ struct hostent *hp;
+ struct sockaddr_in sin;
+ char *hname;
+
+ s = socket (AF_INET, SOCK_STREAM, 0);
+ if (s < 0)
+ error (1, 0, "cannot create socket: %s", SOCK_STRERROR (SOCK_ERRNO));
+
+ port = get_cvs_port_number (root);
+
+ hp = init_sockaddr (&sin, root->hostname, port);
+
+ hname = xstrdup (hp->h_name);
+
+ if (trace)
+ {
+ fprintf (stderr, " -> Connecting to %s(%s):%d\n",
+ root->hostname,
+ inet_ntoa (sin.sin_addr), port);
+ }
+
+ if (connect (s, (struct sockaddr *) &sin, sizeof sin) < 0)
+ error (1, 0, "connect to %s(%s):%d failed: %s",
+ root->hostname,
+ inet_ntoa (sin.sin_addr),
+ port, SOCK_STRERROR (SOCK_ERRNO));
+
+ {
+ const char *realm;
+ struct sockaddr_in laddr;
+ int laddrlen;
+ KTEXT_ST ticket;
+ MSG_DAT msg_data;
+ CREDENTIALS cred;
+ int status;
+
+ realm = krb_realmofhost (hname);
+
+ laddrlen = sizeof (laddr);
+ if (getsockname (s, (struct sockaddr *) &laddr, &laddrlen) < 0)
+ error (1, 0, "getsockname failed: %s", SOCK_STRERROR (SOCK_ERRNO));
+
+ /* We don't care about the checksum, and pass it as zero. */
+ status = krb_sendauth (KOPT_DO_MUTUAL, s, &ticket, "rcmd",
+ hname, realm, (unsigned long) 0, &msg_data,
+ &cred, sched, &laddr, &sin, "KCVSV1.0");
+ if (status != KSUCCESS)
+ error (1, 0, "kerberos authentication failed: %s",
+ krb_get_err_text (status));
+ memcpy (kblock, cred.session, sizeof (C_Block));
+ }
+
+ close_on_exec (s);
+
+ free (hname);
+
+ /* Give caller the values it wants. */
+ make_bufs_from_fds (s, s, 0, to_server, from_server, 1);
+}
+
+#endif /* HAVE_KERBEROS */
+
+#ifdef HAVE_GSSAPI
+
+/* Receive a given number of bytes. */
+
+static void
+recv_bytes (sock, buf, need)
+ int sock;
+ char *buf;
+ int need;
+{
+ while (need > 0)
+ {
+ int got;
+
+ got = recv (sock, buf, need, 0);
+ if (got <= 0)
+ error (1, 0, "recv() from server %s: %s", current_parsed_root->hostname,
+ got == 0 ? "EOF" : SOCK_STRERROR (SOCK_ERRNO));
+
+ buf += got;
+ need -= got;
+ }
+}
+
+/* Connect to the server using GSSAPI authentication. */
+
+/* FIXME
+ *
+ * This really needs to be rewritten to use a buffer and not a socket.
+ * This would enable gserver to work with the SSL code I'm about to commit
+ * since the SSL connection is going to look like a FIFO and not a socket.
+ *
+ * I think, basically, it will need to use buf_output and buf_read directly
+ * since I don't think there is a read_bytes function - only read_line.
+ *
+ * recv_bytes could then be removed too.
+ *
+ * Besides, I added some cruft to reenable the socket which shouldn't be
+ * there. This would also enable its removal.
+ */
+#define BUFSIZE 1024
+static int
+connect_to_gserver (root, sock, hostinfo)
+ cvsroot_t *root;
+ int sock;
+ struct hostent *hostinfo;
+{
+ char *str;
+ char buf[BUFSIZE];
+ gss_buffer_desc *tok_in_ptr, tok_in, tok_out;
+ OM_uint32 stat_min, stat_maj;
+ gss_name_t server_name;
+
+ str = "BEGIN GSSAPI REQUEST\012";
+
+ if (send (sock, str, strlen (str), 0) < 0)
+ error (1, 0, "cannot send: %s", SOCK_STRERROR (SOCK_ERRNO));
+
+ if (strlen (hostinfo->h_name) > BUFSIZE - 5)
+ error (1, 0, "Internal error: hostname exceeds length of buffer");
+ sprintf (buf, "cvs@%s", hostinfo->h_name);
+ tok_in.length = strlen (buf);
+ tok_in.value = buf;
+ gss_import_name (&stat_min, &tok_in, GSS_C_NT_HOSTBASED_SERVICE,
+ &server_name);
+
+ tok_in_ptr = GSS_C_NO_BUFFER;
+ gcontext = GSS_C_NO_CONTEXT;
+
+ do
+ {
+ stat_maj = gss_init_sec_context (&stat_min, GSS_C_NO_CREDENTIAL,
+ &gcontext, server_name,
+ GSS_C_NULL_OID,
+ (GSS_C_MUTUAL_FLAG
+ | GSS_C_REPLAY_FLAG),
+ 0, NULL, tok_in_ptr, NULL, &tok_out,
+ NULL, NULL);
+ if (stat_maj != GSS_S_COMPLETE && stat_maj != GSS_S_CONTINUE_NEEDED)
+ {
+ OM_uint32 message_context;
+ OM_uint32 new_stat_min;
+
+ message_context = 0;
+ gss_display_status (&new_stat_min, stat_maj, GSS_C_GSS_CODE,
+ GSS_C_NULL_OID, &message_context, &tok_out);
+ error (0, 0, "GSSAPI authentication failed: %s",
+ (char *) tok_out.value);
+
+ message_context = 0;
+ gss_display_status (&new_stat_min, stat_min, GSS_C_MECH_CODE,
+ GSS_C_NULL_OID, &message_context, &tok_out);
+ error (1, 0, "GSSAPI authentication failed: %s",
+ (char *) tok_out.value);
+ }
+
+ if (tok_out.length == 0)
+ {
+ tok_in.length = 0;
+ }
+ else
+ {
+ char cbuf[2];
+ int need;
+
+ cbuf[0] = (tok_out.length >> 8) & 0xff;
+ cbuf[1] = tok_out.length & 0xff;
+ if (send (sock, cbuf, 2, 0) < 0)
+ error (1, 0, "cannot send: %s", SOCK_STRERROR (SOCK_ERRNO));
+ if (send (sock, tok_out.value, tok_out.length, 0) < 0)
+ error (1, 0, "cannot send: %s", SOCK_STRERROR (SOCK_ERRNO));
+
+ recv_bytes (sock, cbuf, 2);
+ need = ((cbuf[0] & 0xff) << 8) | (cbuf[1] & 0xff);
+
+ if (need > sizeof buf)
+ {
+ ssize_t got;
+ size_t total;
+
+ /* This usually means that the server sent us an error
+ message. Read it byte by byte and print it out.
+ FIXME: This is a terrible error handling strategy.
+ However, even if we fix the server, we will still
+ want to do this to work with older servers. */
+ buf[0] = cbuf[0];
+ buf[1] = cbuf[1];
+ total = 2;
+ while (got = recv (sock, buf + total, sizeof buf - total, 0))
+ {
+ if (got < 0)
+ error (1, 0, "recv() from server %s: %s",
+ root->hostname, SOCK_STRERROR (SOCK_ERRNO));
+ total += got;
+ if (strrchr (buf + total - got, '\n'))
+ break;
+ }
+ buf[total] = '\0';
+ if (buf[total - 1] == '\n')
+ buf[total - 1] = '\0';
+ error (1, 0, "error from server %s: %s", root->hostname,
+ buf);
+ }
+
+ recv_bytes (sock, buf, need);
+ tok_in.length = need;
+ }
+
+ tok_in.value = buf;
+ tok_in_ptr = &tok_in;
+ }
+ while (stat_maj == GSS_S_CONTINUE_NEEDED);
+
+ return 1;
+}
+
+#endif /* HAVE_GSSAPI */
+
+
+
+static int send_variable_proc PROTO ((Node *, void *));
+
+static int
+send_variable_proc (node, closure)
+ Node *node;
+ void *closure;
+{
+ send_to_server ("Set ", 0);
+ send_to_server (node->key, 0);
+ send_to_server ("=", 1);
+ send_to_server (node->data, 0);
+ send_to_server ("\012", 1);
+ return 0;
+}
+
+
+
+/* Contact the server. */
+void
+start_server ()
+{
+ int rootless;
+ char *log = getenv ("CVS_CLIENT_LOG");
+
+ /* Clear our static variables for this invocation. */
+ if (toplevel_repos != NULL)
+ free (toplevel_repos);
+ toplevel_repos = NULL;
+
+ /* Note that generally speaking we do *not* fall back to a different
+ way of connecting if the first one does not work. This is slow
+ (*really* slow on a 14.4kbps link); the clean way to have a CVS
+ which supports several ways of connecting is with access methods. */
+
+ switch (current_parsed_root->method)
+ {
+
+#ifdef AUTH_CLIENT_SUPPORT
+ case pserver_method:
+ /* Toss the return value. It will die with an error message if
+ * anything goes wrong anyway.
+ */
+ connect_to_pserver (current_parsed_root, &to_server, &from_server, 0, 0);
+ break;
+#endif /* AUTH_CLIENT_SUPPORT */
+
+#if HAVE_KERBEROS
+ case kserver_method:
+ start_tcp_server (current_parsed_root, &to_server, &from_server);
+ break;
+#endif /* HAVE_KERBEROS */
+
+#ifdef HAVE_GSSAPI
+ case gserver_method:
+ /* GSSAPI authentication is handled by the pserver. */
+ connect_to_pserver (current_parsed_root, &to_server, &from_server, 0, 1);
+ break;
+#endif /* HAVE_GSSAPI */
+
+ case ext_method:
+ case extssh_method:
+#ifdef NO_EXT_METHOD
+ error (0, 0, ":ext: method not supported by this port of CVS");
+ error (1, 0, "try :server: instead");
+#else /* ! NO_EXT_METHOD */
+ start_rsh_server (current_parsed_root, &to_server, &from_server);
+#endif /* NO_EXT_METHOD */
+ break;
+
+ case server_method:
+#ifdef START_SERVER
+ {
+ int tofd, fromfd;
+ START_SERVER (&tofd, &fromfd, getcaller (),
+ current_parsed_root->username, current_parsed_root->hostname,
+ current_parsed_root->directory);
+# ifdef START_SERVER_RETURNS_SOCKET
+ make_bufs_from_fds (tofd, fromfd, 0, &to_server, &from_server, 1);
+# else /* ! START_SERVER_RETURNS_SOCKET */
+ make_bufs_from_fds (tofd, fromfd, 0, &to_server, &from_server, 0);
+# endif /* START_SERVER_RETURNS_SOCKET */
+ }
+#else /* ! START_SERVER */
+ /* FIXME: It should be possible to implement this portably,
+ like pserver, which would get rid of the duplicated code
+ in {vms,windows-NT,...}/startserver.c. */
+ error (1, 0,
+"the :server: access method is not supported by this port of CVS");
+#endif /* START_SERVER */
+ break;
+
+ case fork_method:
+ connect_to_forked_server (&to_server, &from_server);
+ break;
+
+ default:
+ error (1, 0, "\
+(start_server internal error): unknown access method");
+ break;
+ }
+
+ /* "Hi, I'm Darlene and I'll be your server tonight..." */
+ server_started = 1;
+
+ /* Set up logfiles, if any.
+ *
+ * We do this _after_ authentication on purpose. Wouldn't really like to
+ * worry about logging passwords...
+ */
+ if (log)
+ {
+ int len = strlen (log);
+ char *buf = xmalloc (len + 5);
+ char *p;
+ FILE *fp;
+
+ strcpy (buf, log);
+ p = buf + len;
+
+ /* Open logfiles in binary mode so that they reflect
+ exactly what was transmitted and received (that is
+ more important than that they be maximally
+ convenient to view). */
+ /* Note that if we create several connections in a single CVS client
+ (currently used by update.c), then the last set of logfiles will
+ overwrite the others. There is currently no way around this. */
+ strcpy (p, ".in");
+ fp = open_file (buf, "wb");
+ if (fp == NULL)
+ error (0, errno, "opening to-server logfile %s", buf);
+ else
+ to_server = log_buffer_initialize (to_server, fp, 0,
+ (BUFMEMERRPROC) NULL);
+
+ strcpy (p, ".out");
+ fp = open_file (buf, "wb");
+ if (fp == NULL)
+ error (0, errno, "opening from-server logfile %s", buf);
+ else
+ from_server = log_buffer_initialize (from_server, fp, 1,
+ (BUFMEMERRPROC) NULL);
+
+ free (buf);
+ }
+
+ /* Clear static variables. */
+ if (toplevel_repos != NULL)
+ free (toplevel_repos);
+ toplevel_repos = NULL;
+ if (last_repos != NULL)
+ free (last_repos);
+ last_repos = NULL;
+ if (last_update_dir != NULL)
+ free (last_update_dir);
+ last_update_dir = NULL;
+ stored_checksum_valid = 0;
+ if (stored_mode != NULL)
+ {
+ free (stored_mode);
+ stored_mode = NULL;
+ }
+
+ rootless = (strcmp (cvs_cmd_name, "init") == 0);
+ if (!rootless)
+ {
+ send_to_server ("Root ", 0);
+ send_to_server (current_parsed_root->directory, 0);
+ send_to_server ("\012", 1);
+ }
+
+ {
+ struct response *rs;
+
+ send_to_server ("Valid-responses", 0);
+
+ for (rs = responses; rs->name != NULL; ++rs)
+ {
+ send_to_server (" ", 0);
+ send_to_server (rs->name, 0);
+ }
+ send_to_server ("\012", 1);
+ }
+ send_to_server ("valid-requests\012", 0);
+
+ if (get_server_responses ())
+ error_exit ();
+
+ /*
+ * Now handle global options.
+ *
+ * -H, -f, -d, -e should be handled OK locally.
+ *
+ * -b we ignore (treating it as a server installation issue).
+ * FIXME: should be an error message.
+ *
+ * -v we print local version info; FIXME: Add a protocol request to get
+ * the version from the server so we can print that too.
+ *
+ * -l -t -r -w -q -n and -Q need to go to the server.
+ */
+
+ {
+ int have_global = supported_request ("Global_option");
+
+ if (noexec)
+ {
+ if (have_global)
+ {
+ send_to_server ("Global_option -n\012", 0);
+ }
+ else
+ error (1, 0,
+ "This server does not support the global -n option.");
+ }
+ if (quiet)
+ {
+ if (have_global)
+ {
+ send_to_server ("Global_option -q\012", 0);
+ }
+ else
+ error (1, 0,
+ "This server does not support the global -q option.");
+ }
+ if (really_quiet)
+ {
+ if (have_global)
+ {
+ send_to_server ("Global_option -Q\012", 0);
+ }
+ else
+ error (1, 0,
+ "This server does not support the global -Q option.");
+ }
+ if (!cvswrite)
+ {
+ if (have_global)
+ {
+ send_to_server ("Global_option -r\012", 0);
+ }
+ else
+ error (1, 0,
+ "This server does not support the global -r option.");
+ }
+ if (trace)
+ {
+ if (have_global)
+ {
+ send_to_server ("Global_option -t\012", 0);
+ }
+ else
+ error (1, 0,
+ "This server does not support the global -t option.");
+ }
+ }
+
+ /* Find out about server-side cvswrappers. An extra network
+ turnaround for cvs import seems to be unavoidable, unless we
+ want to add some kind of client-side place to configure which
+ filenames imply binary. For cvs add, we could avoid the
+ problem by keeping a copy of the wrappers in CVSADM (the main
+ reason to bother would be so we could make add work without
+ contacting the server, I suspect). */
+
+ if ((strcmp (cvs_cmd_name, "import") == 0)
+ || (strcmp (cvs_cmd_name, "add") == 0))
+ {
+ if (supported_request ("wrapper-sendme-rcsOptions"))
+ {
+ int err;
+ send_to_server ("wrapper-sendme-rcsOptions\012", 0);
+ err = get_server_responses ();
+ if (err != 0)
+ error (err, 0, "error reading from server");
+ }
+ }
+
+ if (cvsencrypt && !rootless)
+ {
+#ifdef ENCRYPTION
+ /* Turn on encryption before turning on compression. We do
+ not want to try to compress the encrypted stream. Instead,
+ we want to encrypt the compressed stream. If we can't turn
+ on encryption, bomb out; don't let the user think the data
+ is being encrypted when it is not. */
+#ifdef HAVE_KERBEROS
+ if (current_parsed_root->method == kserver_method)
+ {
+ if (! supported_request ("Kerberos-encrypt"))
+ error (1, 0, "This server does not support encryption");
+ send_to_server ("Kerberos-encrypt\012", 0);
+ to_server = krb_encrypt_buffer_initialize (to_server, 0, sched,
+ kblock,
+ (BUFMEMERRPROC) NULL);
+ from_server = krb_encrypt_buffer_initialize (from_server, 1,
+ sched, kblock,
+ (BUFMEMERRPROC) NULL);
+ }
+ else
+#endif /* HAVE_KERBEROS */
+#ifdef HAVE_GSSAPI
+ if (current_parsed_root->method == gserver_method)
+ {
+ if (! supported_request ("Gssapi-encrypt"))
+ error (1, 0, "This server does not support encryption");
+ send_to_server ("Gssapi-encrypt\012", 0);
+ to_server = cvs_gssapi_wrap_buffer_initialize (to_server, 0,
+ gcontext,
+ ((BUFMEMERRPROC)
+ NULL));
+ from_server = cvs_gssapi_wrap_buffer_initialize (from_server, 1,
+ gcontext,
+ ((BUFMEMERRPROC)
+ NULL));
+ cvs_gssapi_encrypt = 1;
+ }
+ else
+#endif /* HAVE_GSSAPI */
+ error (1, 0, "Encryption is only supported when using GSSAPI or Kerberos");
+#else /* ! ENCRYPTION */
+ error (1, 0, "This client does not support encryption");
+#endif /* ! ENCRYPTION */
+ }
+
+ if (gzip_level && !rootless)
+ {
+ if (supported_request ("Gzip-stream"))
+ {
+ char gzip_level_buf[5];
+ send_to_server ("Gzip-stream ", 0);
+ sprintf (gzip_level_buf, "%d", gzip_level);
+ send_to_server (gzip_level_buf, 0);
+ send_to_server ("\012", 1);
+
+ /* All further communication with the server will be
+ compressed. */
+
+ to_server = compress_buffer_initialize (to_server, 0, gzip_level,
+ (BUFMEMERRPROC) NULL);
+ from_server = compress_buffer_initialize (from_server, 1,
+ gzip_level,
+ (BUFMEMERRPROC) NULL);
+ }
+#ifndef NO_CLIENT_GZIP_PROCESS
+ else if (supported_request ("gzip-file-contents"))
+ {
+ char gzip_level_buf[5];
+ send_to_server ("gzip-file-contents ", 0);
+ sprintf (gzip_level_buf, "%d", gzip_level);
+ send_to_server (gzip_level_buf, 0);
+
+ send_to_server ("\012", 1);
+
+ file_gzip_level = gzip_level;
+ }
+#endif
+ else
+ {
+ fprintf (stderr, "server doesn't support gzip-file-contents\n");
+ /* Setting gzip_level to 0 prevents us from giving the
+ error twice if update has to contact the server again
+ to fetch unpatchable files. */
+ gzip_level = 0;
+ }
+ }
+
+ if (cvsauthenticate && ! cvsencrypt && !rootless)
+ {
+ /* Turn on authentication after turning on compression, so
+ that we can compress the authentication information. We
+ assume that encrypted data is always authenticated--the
+ ability to decrypt the data stream is itself a form of
+ authentication. */
+#ifdef HAVE_GSSAPI
+ if (current_parsed_root->method == gserver_method)
+ {
+ if (! supported_request ("Gssapi-authenticate"))
+ error (1, 0,
+ "This server does not support stream authentication");
+ send_to_server ("Gssapi-authenticate\012", 0);
+ to_server = cvs_gssapi_wrap_buffer_initialize (to_server, 0,
+ gcontext,
+ ((BUFMEMERRPROC)
+ NULL));
+ from_server = cvs_gssapi_wrap_buffer_initialize (from_server, 1,
+ gcontext,
+ ((BUFMEMERRPROC)
+ NULL));
+ }
+ else
+ error (1, 0, "Stream authentication is only supported when using GSSAPI");
+#else /* ! HAVE_GSSAPI */
+ error (1, 0, "This client does not support stream authentication");
+#endif /* ! HAVE_GSSAPI */
+ }
+
+ /* If "Set" is not supported, just silently fail to send the variables.
+ Users with an old server should get a useful error message when it
+ fails to recognize the ${=foo} syntax. This way if someone uses
+ several servers, some of which are new and some old, they can still
+ set user variables in their .cvsrc without trouble. */
+ if (supported_request ("Set"))
+ walklist (variable_list, send_variable_proc, NULL);
+}
+
+
+
+#ifndef NO_EXT_METHOD
+
+/* Contact the server by starting it with rsh. */
+
+/* Right now, we have two different definitions for this function,
+ depending on whether we start the rsh server using popenRW or not.
+ This isn't ideal, and the best thing would probably be to change
+ the OS/2 port to be more like the regular Unix client (i.e., by
+ implementing piped_child)... but I'm doing something else at the
+ moment, and wish to make only one change at a time. -Karl */
+
+# ifdef START_RSH_WITH_POPEN_RW
+
+/* This is actually a crock -- it's OS/2-specific, for no one else
+ uses it. If I get time, I want to make piped_child and all the
+ other stuff in os2/run.c work right. In the meantime, this gets us
+ up and running, and that's most important. */
+
+static void
+start_rsh_server (root, to_server, from_server)
+ cvsroot_t *root;
+ struct buffer **to_server;
+ struct buffer **from_server;
+{
+ int pipes[2];
+ int child_pid;
+
+ /* If you're working through firewalls, you can set the
+ CVS_RSH environment variable to a script which uses rsh to
+ invoke another rsh on a proxy machine. */
+ char *env_cvs_rsh = getenv ("CVS_RSH");
+ char *env_cvs_ssh = getenv ("CVS_SSH");
+ char *cvs_rsh;
+ char *cvs_server = getenv ("CVS_SERVER");
+ int i = 0;
+ /* This needs to fit "rsh", "-b", "-l", "USER", "host",
+ "cmd (w/ args)", and NULL. We leave some room to grow. */
+ char *rsh_argv[10];
+
+ if (root->method == extssh_method)
+ cvs_rsh = env_cvs_ssh ? env_cvs_ssh : SSH_DFLT;
+ else
+ cvs_rsh = env_cvs_rsh ? env_cvs_rsh : RSH_DFLT;
+
+ if (!cvs_server)
+ cvs_server = "cvs";
+
+ /* The command line starts out with rsh. */
+ rsh_argv[i++] = cvs_rsh;
+
+# ifdef RSH_NEEDS_BINARY_FLAG
+ /* "-b" for binary, under OS/2. */
+ rsh_argv[i++] = "-b";
+# endif /* RSH_NEEDS_BINARY_FLAG */
+
+ /* Then we strcat more things on the end one by one. */
+ if (root->username != NULL)
+ {
+ rsh_argv[i++] = "-l";
+ rsh_argv[i++] = root->username;
+ }
+
+ rsh_argv[i++] = root->hostname;
+ rsh_argv[i++] = cvs_server;
+ rsh_argv[i++] = "server";
+
+ /* Mark the end of the arg list. */
+ rsh_argv[i] = (char *) NULL;
+
+ if (trace)
+ {
+ fprintf (stderr, " -> Starting server: ");
+ for (i = 0; rsh_argv[i]; i++)
+ fprintf (stderr, "%s ", rsh_argv[i]);
+ putc ('\n', stderr);
+ }
+
+ /* Do the deed. */
+ child_pid = popenRW (rsh_argv, pipes);
+ if (child_pid < 0)
+ error (1, errno, "cannot start server via rsh");
+
+ /* Give caller the file descriptors in a form it can deal with. */
+ make_bufs_from_fds (pipes[0], pipes[1], child_pid, to_server, from_server, 0);
+}
+
+# else /* ! START_RSH_WITH_POPEN_RW */
+
+static void
+start_rsh_server (root, to_server, from_server)
+ cvsroot_t *root;
+ struct buffer **to_server;
+ struct buffer **from_server;
+{
+ /* If you're working through firewalls, you can set the
+ CVS_RSH environment variable to a script which uses rsh to
+ invoke another rsh on a proxy machine. */
+ char *env_cvs_rsh = getenv ("CVS_RSH");
+ char *env_cvs_ssh = getenv ("CVS_SSH");
+ char *cvs_rsh;
+ char *cvs_server = getenv ("CVS_SERVER");
+ char *command;
+ int tofd, fromfd;
+ int child_pid;
+
+ if (root->method == extssh_method)
+ cvs_rsh = env_cvs_ssh ? env_cvs_ssh : SSH_DFLT;
+ else
+ cvs_rsh = env_cvs_rsh ? env_cvs_rsh : RSH_DFLT;
+
+ if (!cvs_server)
+ cvs_server = "cvs";
+
+ /* Pass the command to rsh as a single string. This shouldn't
+ affect most rsh servers at all, and will pacify some buggy
+ versions of rsh that grab switches out of the middle of the
+ command (they're calling the GNU getopt routines incorrectly). */
+ command = xmalloc (strlen (cvs_server) + 8);
+
+ /* If you are running a very old (Nov 3, 1994, before 1.5)
+ * version of the server, you need to make sure that your .bashrc
+ * on the server machine does not set CVSROOT to something
+ * containing a colon (or better yet, upgrade the server). */
+ sprintf (command, "%s server", cvs_server);
+
+ {
+ const char *argv[10];
+ const char **p = argv;
+
+ *p++ = cvs_rsh;
+
+ /* If the login names differ between client and server
+ * pass it on to rsh.
+ */
+ if (root->username != NULL)
+ {
+ *p++ = "-l";
+ *p++ = root->username;
+ }
+
+ *p++ = root->hostname;
+ *p++ = command;
+ *p++ = NULL;
+
+ if (trace)
+ {
+ int i;
+
+ fprintf (stderr, " -> Starting server: ");
+ for (i = 0; argv[i]; i++)
+ fprintf (stderr, "%s ", argv[i]);
+ putc ('\n', stderr);
+ }
+ child_pid = piped_child (argv, &tofd, &fromfd, 1);
+
+ if (child_pid < 0)
+ error (1, errno, "cannot start server via rsh");
+ }
+ free (command);
+
+ make_bufs_from_fds (tofd, fromfd, child_pid, to_server, from_server, 0);
+}
+
+# endif /* START_RSH_WITH_POPEN_RW */
+
+#endif /* NO_EXT_METHOD */
+
+
+
+/* Send an argument STRING. */
+void
+send_arg (string)
+ const char *string;
+{
+ char buf[1];
+ const char *p = string;
+
+ send_to_server ("Argument ", 0);
+
+ while (*p)
+ {
+ if (*p == '\n')
+ {
+ send_to_server ("\012Argumentx ", 0);
+ }
+ else
+ {
+ buf[0] = *p;
+ send_to_server (buf, 1);
+ }
+ ++p;
+ }
+ send_to_server ("\012", 1);
+}
+
+
+
+static void send_modified PROTO ((const char *, const char *, Vers_TS *));
+
+/* VERS->OPTIONS specifies whether the file is binary or not. NOTE: BEFORE
+ using any other fields of the struct vers, we would need to fix
+ client_process_import_file to set them up. */
+
+static void
+send_modified (file, short_pathname, vers)
+ const char *file;
+ const char *short_pathname;
+ Vers_TS *vers;
+{
+ /* File was modified, send it. */
+ struct stat sb;
+ int fd;
+ char *buf;
+ char *mode_string;
+ size_t bufsize;
+ int bin;
+
+ if (trace)
+ (void) fprintf (stderr, " -> Sending file `%s' to server\n", file);
+
+ /* Don't think we can assume fstat exists. */
+ if ( CVS_STAT (file, &sb) < 0)
+ error (1, errno, "reading %s", short_pathname);
+
+ mode_string = mode_to_string (sb.st_mode);
+
+ /* Beware: on systems using CRLF line termination conventions,
+ the read and write functions will convert CRLF to LF, so the
+ number of characters read is not the same as sb.st_size. Text
+ files should always be transmitted using the LF convention, so
+ we don't want to disable this conversion. */
+ bufsize = sb.st_size;
+ buf = xmalloc (bufsize);
+
+ /* Is the file marked as containing binary data by the "-kb" flag?
+ If so, make sure to open it in binary mode: */
+
+ if (vers && vers->options)
+ bin = !(strcmp (vers->options, "-kb"));
+ else
+ bin = 0;
+
+#ifdef BROKEN_READWRITE_CONVERSION
+ if (!bin)
+ {
+ /* If only stdio, not open/write/etc., do text/binary
+ conversion, use convert_file which can compensate
+ (FIXME: we could just use stdio instead which would
+ avoid the whole problem). */
+ char tfile[1024]; strcpy(tfile, file); strcat(tfile, ".CVSBFCTMP");
+ convert_file (file, O_RDONLY,
+ tfile, O_WRONLY | O_CREAT | O_TRUNC | OPEN_BINARY);
+ fd = CVS_OPEN (tfile, O_RDONLY | OPEN_BINARY);
+ if (fd < 0)
+ error (1, errno, "reading %s", short_pathname);
+ }
+ else
+ fd = CVS_OPEN (file, O_RDONLY | OPEN_BINARY);
+#else
+ fd = CVS_OPEN (file, O_RDONLY | (bin ? OPEN_BINARY : 0));
+#endif
+
+ if (fd < 0)
+ error (1, errno, "reading %s", short_pathname);
+
+ if (file_gzip_level && sb.st_size > 100)
+ {
+ size_t newsize = 0;
+
+ if (read_and_gzip (fd, short_pathname, (unsigned char **)&buf,
+ &bufsize, &newsize,
+ file_gzip_level))
+ error (1, 0, "aborting due to compression error");
+
+ if (close (fd) < 0)
+ error (0, errno, "warning: can't close %s", short_pathname);
+
+ {
+ char tmp[80];
+
+ send_to_server ("Modified ", 0);
+ send_to_server (file, 0);
+ send_to_server ("\012", 1);
+ send_to_server (mode_string, 0);
+ send_to_server ("\012z", 2);
+ sprintf (tmp, "%lu\n", (unsigned long) newsize);
+ send_to_server (tmp, 0);
+
+ send_to_server (buf, newsize);
+ }
+ }
+ else
+ {
+ int newsize;
+
+ {
+ char *bufp = buf;
+ int len;
+
+ /* FIXME: This is gross. It assumes that we might read
+ less than st_size bytes (true on NT), but not more.
+ Instead of this we should just be reading a block of
+ data (e.g. 8192 bytes), writing it to the network, and
+ so on until EOF. */
+ while ((len = read (fd, bufp, (buf + sb.st_size) - bufp)) > 0)
+ bufp += len;
+
+ if (len < 0)
+ error (1, errno, "reading %s", short_pathname);
+
+ newsize = bufp - buf;
+ }
+ if (close (fd) < 0)
+ error (0, errno, "warning: can't close %s", short_pathname);
+
+ {
+ char tmp[80];
+
+ send_to_server ("Modified ", 0);
+ send_to_server (file, 0);
+ send_to_server ("\012", 1);
+ send_to_server (mode_string, 0);
+ send_to_server ("\012", 1);
+ sprintf (tmp, "%lu\012", (unsigned long) newsize);
+ send_to_server (tmp, 0);
+ }
+#ifdef BROKEN_READWRITE_CONVERSION
+ if (!bin)
+ {
+ char tfile[1024]; strcpy(tfile, file); strcat(tfile, ".CVSBFCTMP");
+ if (CVS_UNLINK (tfile) < 0)
+ error (0, errno, "warning: can't remove temp file %s", tfile);
+ }
+#endif
+
+ /*
+ * Note that this only ends with a newline if the file ended with
+ * one.
+ */
+ if (newsize > 0)
+ send_to_server (buf, newsize);
+ }
+ free (buf);
+ free (mode_string);
+}
+
+/* The address of an instance of this structure is passed to
+ send_fileproc, send_filesdoneproc, and send_direntproc, as the
+ callerdat parameter. */
+
+struct send_data
+{
+ /* Each of the following flags are zero for clear or nonzero for set. */
+ int build_dirs;
+ int force;
+ int no_contents;
+ int backup_modified;
+};
+
+static int send_fileproc PROTO ((void *callerdat, struct file_info *finfo));
+
+/* Deal with one file. */
+static int
+send_fileproc (callerdat, finfo)
+ void *callerdat;
+ struct file_info *finfo;
+{
+ struct send_data *args = (struct send_data *) callerdat;
+ Vers_TS *vers;
+ struct file_info xfinfo;
+ /* File name to actually use. Might differ in case from
+ finfo->file. */
+ const char *filename;
+
+ send_a_repository ("", finfo->repository, finfo->update_dir);
+
+ xfinfo = *finfo;
+ xfinfo.repository = NULL;
+ xfinfo.rcs = NULL;
+ vers = Version_TS (&xfinfo, NULL, NULL, NULL, 0, 0);
+
+ if (vers->entdata != NULL)
+ filename = vers->entdata->user;
+ else
+ filename = finfo->file;
+
+ if (vers->vn_user != NULL)
+ {
+ /* The Entries request. */
+ send_to_server ("Entry /", 0);
+ send_to_server (filename, 0);
+ send_to_server ("/", 0);
+ send_to_server (vers->vn_user, 0);
+ send_to_server ("/", 0);
+ if (vers->ts_conflict != NULL)
+ {
+ if (vers->ts_user != NULL &&
+ strcmp (vers->ts_conflict, vers->ts_user) == 0)
+ send_to_server ("+=", 0);
+ else
+ send_to_server ("+modified", 0);
+ }
+ send_to_server ("/", 0);
+ send_to_server (vers->entdata != NULL
+ ? vers->entdata->options
+ : vers->options,
+ 0);
+ send_to_server ("/", 0);
+ if (vers->entdata != NULL && vers->entdata->tag)
+ {
+ send_to_server ("T", 0);
+ send_to_server (vers->entdata->tag, 0);
+ }
+ else if (vers->entdata != NULL && vers->entdata->date)
+ {
+ send_to_server ("D", 0);
+ send_to_server (vers->entdata->date, 0);
+ }
+ send_to_server ("\012", 1);
+ }
+ else
+ {
+ /* It seems a little silly to re-read this on each file, but
+ send_dirent_proc doesn't get called if filenames are specified
+ explicitly on the command line. */
+ wrap_add_file (CVSDOTWRAPPER, 1);
+
+ if (wrap_name_has (filename, WRAP_RCSOPTION))
+ {
+ /* No "Entry", but the wrappers did give us a kopt so we better
+ send it with "Kopt". As far as I know this only happens
+ for "cvs add". Question: is there any reason why checking
+ for options from wrappers isn't done in Version_TS?
+
+ Note: it might have been better to just remember all the
+ kopts on the client side, rather than send them to the server,
+ and have it send us back the same kopts. But that seemed like
+ a bigger change than I had in mind making now. */
+
+ if (supported_request ("Kopt"))
+ {
+ char *opt;
+
+ send_to_server ("Kopt ", 0);
+ opt = wrap_rcsoption (filename, 1);
+ send_to_server (opt, 0);
+ send_to_server ("\012", 1);
+ free (opt);
+ }
+ else
+ error (0, 0,
+ "\
+warning: ignoring -k options due to server limitations");
+ }
+ }
+
+ if (vers->ts_user == NULL)
+ {
+ /*
+ * Do we want to print "file was lost" like normal CVS?
+ * Would it always be appropriate?
+ */
+ /* File no longer exists. Don't do anything, missing files
+ just happen. */
+ }
+ else if (vers->ts_rcs == NULL
+ || args->force
+ || strcmp (vers->ts_conflict
+ && supported_request ("Empty-conflicts")
+ ? vers->ts_conflict : vers->ts_rcs, vers->ts_user)
+ || (vers->ts_conflict && !strcmp (cvs_cmd_name, "diff"))
+ || (vers->vn_user && *vers->vn_user == '0'))
+ {
+ if (args->no_contents
+ && supported_request ("Is-modified"))
+ {
+ send_to_server ("Is-modified ", 0);
+ send_to_server (filename, 0);
+ send_to_server ("\012", 1);
+ }
+ else
+ send_modified (filename, finfo->fullname, vers);
+
+ if (args->backup_modified)
+ {
+ char *bakname;
+ bakname = backup_file (filename, vers->vn_user);
+ /* This behavior is sufficiently unexpected to
+ justify overinformativeness, I think. */
+ if (! really_quiet)
+ printf ("(Locally modified %s moved to %s)\n",
+ filename, bakname);
+ free (bakname);
+ }
+ }
+ else
+ {
+ send_to_server ("Unchanged ", 0);
+ send_to_server (filename, 0);
+ send_to_server ("\012", 1);
+ }
+
+ /* if this directory has an ignore list, add this file to it */
+ if (ignlist)
+ {
+ Node *p;
+
+ p = getnode ();
+ p->type = FILES;
+ p->key = xstrdup (finfo->file);
+ (void) addnode (ignlist, p);
+ }
+
+ freevers_ts (&vers);
+ return 0;
+}
+
+
+
+static void send_ignproc PROTO ((const char *, const char *));
+
+static void
+send_ignproc (file, dir)
+ const char *file;
+ const char *dir;
+{
+ if (ign_inhibit_server || !supported_request ("Questionable"))
+ {
+ if (dir[0] != '\0')
+ (void) printf ("? %s/%s\n", dir, file);
+ else
+ (void) printf ("? %s\n", file);
+ }
+ else
+ {
+ send_to_server ("Questionable ", 0);
+ send_to_server (file, 0);
+ send_to_server ("\012", 1);
+ }
+}
+
+
+
+static int send_filesdoneproc PROTO ((void *, int, const char *, const char *,
+ List *));
+
+static int
+send_filesdoneproc (callerdat, err, repository, update_dir, entries)
+ void *callerdat;
+ int err;
+ const char *repository;
+ const char *update_dir;
+ List *entries;
+{
+ /* if this directory has an ignore list, process it then free it */
+ if (ignlist)
+ {
+ ignore_files (ignlist, entries, update_dir, send_ignproc);
+ dellist (&ignlist);
+ }
+
+ return (err);
+}
+
+static Dtype send_dirent_proc PROTO ((void *, const char *, const char *,
+ const char *, List *));
+
+/*
+ * send_dirent_proc () is called back by the recursion processor before a
+ * sub-directory is processed for update.
+ * A return code of 0 indicates the directory should be
+ * processed by the recursion code. A return of non-zero indicates the
+ * recursion code should skip this directory.
+ *
+ */
+static Dtype
+send_dirent_proc (callerdat, dir, repository, update_dir, entries)
+ void *callerdat;
+ const char *dir;
+ const char *repository;
+ const char *update_dir;
+ List *entries;
+{
+ struct send_data *args = (struct send_data *) callerdat;
+ int dir_exists;
+ char *cvsadm_name;
+
+ if (ignore_directory (update_dir))
+ {
+ /* print the warm fuzzy message */
+ if (!quiet)
+ error (0, 0, "Ignoring %s", update_dir);
+ return (R_SKIP_ALL);
+ }
+
+ /*
+ * If the directory does not exist yet (e.g. "cvs update -d foo"),
+ * no need to send any files from it. If the directory does not
+ * have a CVS directory, then we pretend that it does not exist.
+ * Otherwise, we will fail when trying to open the Entries file.
+ * This case will happen when checking out a module defined as
+ * ``-a .''.
+ */
+ cvsadm_name = xmalloc (strlen (dir) + sizeof (CVSADM) + 10);
+ sprintf (cvsadm_name, "%s/%s", dir, CVSADM);
+ dir_exists = isdir (cvsadm_name);
+ free (cvsadm_name);
+
+ /*
+ * If there is an empty directory (e.g. we are doing `cvs add' on a
+ * newly-created directory), the server still needs to know about it.
+ */
+
+ if (dir_exists)
+ {
+ /*
+ * Get the repository from a CVS/Repository file whenever possible.
+ * The repository variable is wrong if the names in the local
+ * directory don't match the names in the repository.
+ */
+ char *repos = Name_Repository (dir, update_dir);
+ send_a_repository (dir, repos, update_dir);
+ free (repos);
+
+ /* initialize the ignore list for this directory */
+ ignlist = getlist ();
+ }
+ else
+ {
+ /* It doesn't make sense to send a non-existent directory,
+ because there is no way to get the correct value for
+ the repository (I suppose maybe via the expand-modules
+ request). In the case where the "obvious" choice for
+ repository is correct, the server can figure out whether
+ to recreate the directory; in the case where it is wrong
+ (that is, does not match what modules give us), we might as
+ well just fail to recreate it.
+
+ Checking for noexec is a kludge for "cvs -n add dir". */
+ /* Don't send a non-existent directory unless we are building
+ new directories (build_dirs is true). Otherwise, CVS may
+ see a D line in an Entries file, and recreate a directory
+ which the user removed by hand. */
+ if (args->build_dirs && noexec)
+ send_a_repository (dir, repository, update_dir);
+ }
+
+ return (dir_exists ? R_PROCESS : R_SKIP_ALL);
+}
+
+
+
+static int send_dirleave_proc PROTO ((void *, const char *, int, const char *,
+ List *));
+
+/*
+ * send_dirleave_proc () is called back by the recursion code upon leaving
+ * a directory. All it does is delete the ignore list if it hasn't already
+ * been done (by send_filesdone_proc).
+ */
+/* ARGSUSED */
+static int
+send_dirleave_proc (callerdat, dir, err, update_dir, entries)
+ void *callerdat;
+ const char *dir;
+ int err;
+ const char *update_dir;
+ List *entries;
+{
+
+ /* Delete the ignore list if it hasn't already been done. */
+ if (ignlist)
+ dellist (&ignlist);
+ return err;
+}
+
+/*
+ * Send each option in an array to the server, one by one.
+ * argv might be "--foo=bar", "-C", "5", "-y".
+ */
+void
+send_options (int argc, char *const *argv)
+{
+ int i;
+ for (i = 0; i < argc; i++)
+ send_arg (argv[i]);
+}
+
+
+
+/* Send the names of all the argument files to the server. */
+void
+send_file_names (argc, argv, flags)
+ int argc;
+ char **argv;
+ unsigned int flags;
+{
+ int i;
+
+ /* The fact that we do this here as well as start_recursion is a bit
+ of a performance hit. Perhaps worth cleaning up someday. */
+ if (flags & SEND_EXPAND_WILD)
+ expand_wild (argc, argv, &argc, &argv);
+
+ for (i = 0; i < argc; ++i)
+ {
+ char buf[1];
+ char *p;
+#ifdef FILENAMES_CASE_INSENSITIVE
+ char *line = xmalloc (1);
+ *line = '\0';
+#endif /* FILENAMES_CASE_INSENSITIVE */
+
+ if (arg_should_not_be_sent_to_server (argv[i]))
+ continue;
+
+#ifdef FILENAMES_CASE_INSENSITIVE
+ /* We want to send the path as it appears in the
+ CVS/Entries files. We put this inside an ifdef
+ to avoid doing all these system calls in
+ cases where fncmp is just strcmp anyway. */
+ /* The isdir (CVSADM) check could more gracefully be replaced
+ with a way of having Entries_Open report back the
+ error to us and letting us ignore existence_error.
+ Or some such. */
+ {
+ List *stack;
+ size_t line_len = 0;
+ char *q, *r;
+ struct saved_cwd sdir;
+
+ /* Split the argument onto the stack. */
+ stack = getlist();
+ r = xstrdup (argv[i]);
+ /* It's okay to discard the const from the last_component return
+ * below since we know we passed in an arg that was not const.
+ */
+ while ((q = (char *)last_component (r)) != r)
+ {
+ push (stack, xstrdup (q));
+ *--q = '\0';
+ }
+ push (stack, r);
+
+ /* Normalize the path into outstr. */
+ save_cwd (&sdir);
+ while (q = pop (stack))
+ {
+ Node *node = NULL;
+ if (isdir (CVSADM))
+ {
+ List *entries;
+
+ /* Note that if we are adding a directory,
+ the following will read the entry
+ that we just wrote there, that is, we
+ will get the case specified on the
+ command line, not the case of the
+ directory in the filesystem. This
+ is correct behavior. */
+ entries = Entries_Open (0, NULL);
+ node = findnode_fn (entries, q);
+ if (node != NULL)
+ {
+ /* Add the slash unless this is our first element. */
+ if (line_len)
+ xrealloc_and_strcat (&line, &line_len, "/");
+ xrealloc_and_strcat (&line, &line_len, node->key);
+ delnode (node);
+ }
+ Entries_Close (entries);
+ }
+
+ /* If node is still NULL then we either didn't find CVSADM or
+ * we didn't find an entry there.
+ */
+ if (node == NULL)
+ {
+ /* Add the slash unless this is our first element. */
+ if (line_len)
+ xrealloc_and_strcat (&line, &line_len, "/");
+ xrealloc_and_strcat (&line, &line_len, q);
+ break;
+ }
+
+ /* And descend the tree. */
+ if (isdir (q))
+ CVS_CHDIR (q);
+ free (q);
+ }
+ restore_cwd (&sdir, NULL);
+ free_cwd (&sdir);
+
+ /* Now put everything we didn't find entries for back on. */
+ while (q = pop (stack))
+ {
+ if (line_len)
+ xrealloc_and_strcat (&line, &line_len, "/");
+ xrealloc_and_strcat (&line, &line_len, q);
+ free (q);
+ }
+
+ p = line;
+
+ dellist (&stack);
+ }
+#else /* !FILENAMES_CASE_INSENSITIVE */
+ p = argv[i];
+#endif /* FILENAMES_CASE_INSENSITIVE */
+
+ send_to_server ("Argument ", 0);
+
+ while (*p)
+ {
+ if (*p == '\n')
+ {
+ send_to_server ("\012Argumentx ", 0);
+ }
+ else if (ISDIRSEP (*p))
+ {
+ buf[0] = '/';
+ send_to_server (buf, 1);
+ }
+ else
+ {
+ buf[0] = *p;
+ send_to_server (buf, 1);
+ }
+ ++p;
+ }
+ send_to_server ("\012", 1);
+#ifdef FILENAMES_CASE_INSENSITIVE
+ free (line);
+#endif /* FILENAMES_CASE_INSENSITIVE */
+ }
+
+ if (flags & SEND_EXPAND_WILD)
+ {
+ int i;
+ for (i = 0; i < argc; ++i)
+ free (argv[i]);
+ free (argv);
+ }
+}
+
+
+
+/* Calculate and send max-dotdot to the server */
+static void
+send_max_dotdot (argc, argv)
+ int argc;
+ char **argv;
+{
+ int i;
+ int level = 0;
+ int max_level = 0;
+
+ /* Send Max-dotdot if needed. */
+ for (i = 0; i < argc; ++i)
+ {
+ level = pathname_levels (argv[i]);
+ if (level > 0)
+ {
+ if (uppaths == NULL) uppaths = getlist();
+ push_string (uppaths, xstrdup (argv[i]));
+ }
+ if (level > max_level)
+ max_level = level;
+ }
+
+ if (max_level > 0)
+ {
+ if (supported_request ("Max-dotdot"))
+ {
+ char buf[10];
+ sprintf (buf, "%d", max_level);
+
+ send_to_server ("Max-dotdot ", 0);
+ send_to_server (buf, 0);
+ send_to_server ("\012", 1);
+ }
+ else
+ {
+ error (1, 0,
+"backreference in path (`..') not supported by old (pre-Max-dotdot) servers");
+ }
+ }
+}
+
+
+
+/* Send Repository, Modified and Entry. argc and argv contain only
+ the files to operate on (or empty for everything), not options.
+ local is nonzero if we should not recurse (-l option). flags &
+ SEND_BUILD_DIRS is nonzero if nonexistent directories should be
+ sent. flags & SEND_FORCE is nonzero if we should send unmodified
+ files to the server as though they were modified. flags &
+ SEND_NO_CONTENTS means that this command only needs to know
+ _whether_ a file is modified, not the contents. Also sends Argument
+ lines for argc and argv, so should be called after options are sent. */
+void
+send_files (argc, argv, local, aflag, flags)
+ int argc;
+ char **argv;
+ int local;
+ int aflag;
+ unsigned int flags;
+{
+ struct send_data args;
+ int err;
+
+ send_max_dotdot (argc, argv);
+
+ /*
+ * aflag controls whether the tag/date is copied into the vers_ts.
+ * But we don't actually use it, so I don't think it matters what we pass
+ * for aflag here.
+ */
+ args.build_dirs = flags & SEND_BUILD_DIRS;
+ args.force = flags & SEND_FORCE;
+ args.no_contents = flags & SEND_NO_CONTENTS;
+ args.backup_modified = flags & BACKUP_MODIFIED_FILES;
+ err = start_recursion
+ (send_fileproc, send_filesdoneproc,
+ send_dirent_proc, send_dirleave_proc, (void *) &args,
+ argc, argv, local, W_LOCAL, aflag, CVS_LOCK_NONE, (char *) NULL, 0,
+ (char *) NULL);
+ if (err)
+ error_exit ();
+ if (toplevel_repos == NULL)
+ /*
+ * This happens if we are not processing any files,
+ * or for checkouts in directories without any existing stuff
+ * checked out. The following assignment is correct for the
+ * latter case; I don't think toplevel_repos matters for the
+ * former.
+ */
+ toplevel_repos = xstrdup (current_parsed_root->directory);
+ send_repository ("", toplevel_repos, ".");
+}
+
+void
+client_import_setup (repository)
+ char *repository;
+{
+ if (toplevel_repos == NULL) /* should always be true */
+ send_a_repository ("", repository, "");
+}
+
+/*
+ * Process the argument import file.
+ */
+int
+client_process_import_file (message, vfile, vtag, targc, targv, repository,
+ all_files_binary, modtime)
+ char *message;
+ char *vfile;
+ char *vtag;
+ int targc;
+ char *targv[];
+ char *repository;
+ int all_files_binary;
+
+ /* Nonzero for "import -d". */
+ int modtime;
+{
+ char *update_dir;
+ char *fullname;
+ Vers_TS vers;
+
+ assert (toplevel_repos != NULL);
+
+ if (strncmp (repository, toplevel_repos, strlen (toplevel_repos)) != 0)
+ error (1, 0,
+ "internal error: pathname `%s' doesn't specify file in `%s'",
+ repository, toplevel_repos);
+
+ if (strcmp (repository, toplevel_repos) == 0)
+ {
+ update_dir = "";
+ fullname = xstrdup (vfile);
+ }
+ else
+ {
+ update_dir = repository + strlen (toplevel_repos) + 1;
+
+ fullname = xmalloc (strlen (vfile) + strlen (update_dir) + 10);
+ strcpy (fullname, update_dir);
+ strcat (fullname, "/");
+ strcat (fullname, vfile);
+ }
+
+ send_a_repository ("", repository, update_dir);
+ if (all_files_binary)
+ {
+ vers.options = xmalloc (4); /* strlen("-kb") + 1 */
+ strcpy (vers.options, "-kb");
+ }
+ else
+ {
+ vers.options = wrap_rcsoption (vfile, 1);
+ }
+ if (vers.options != NULL)
+ {
+ if (supported_request ("Kopt"))
+ {
+ send_to_server ("Kopt ", 0);
+ send_to_server (vers.options, 0);
+ send_to_server ("\012", 1);
+ }
+ else
+ error (0, 0,
+ "warning: ignoring -k options due to server limitations");
+ }
+ if (modtime)
+ {
+ if (supported_request ("Checkin-time"))
+ {
+ struct stat sb;
+ char *rcsdate;
+ char netdate[MAXDATELEN];
+
+ if (CVS_STAT (vfile, &sb) < 0)
+ error (1, errno, "cannot stat %s", fullname);
+ rcsdate = date_from_time_t (sb.st_mtime);
+ date_to_internet (netdate, rcsdate);
+ free (rcsdate);
+
+ send_to_server ("Checkin-time ", 0);
+ send_to_server (netdate, 0);
+ send_to_server ("\012", 1);
+ }
+ else
+ error (0, 0,
+ "warning: ignoring -d option due to server limitations");
+ }
+ send_modified (vfile, fullname, &vers);
+ if (vers.options != NULL)
+ free (vers.options);
+ free (fullname);
+ return 0;
+}
+
+void
+client_import_done ()
+{
+ if (toplevel_repos == NULL)
+ /*
+ * This happens if we are not processing any files,
+ * or for checkouts in directories without any existing stuff
+ * checked out. The following assignment is correct for the
+ * latter case; I don't think toplevel_repos matters for the
+ * former.
+ */
+ /* FIXME: "can't happen" now that we call client_import_setup
+ at the beginning. */
+ toplevel_repos = xstrdup (current_parsed_root->directory);
+ send_repository ("", toplevel_repos, ".");
+}
+
+
+
+static void
+notified_a_file (data, ent_list, short_pathname, filename)
+ char *data;
+ List *ent_list;
+ char *short_pathname;
+ char *filename;
+{
+ FILE *fp;
+ FILE *newf;
+ size_t line_len = 8192;
+ char *line = xmalloc (line_len);
+ char *cp;
+ int nread;
+ int nwritten;
+ char *p;
+
+ fp = open_file (CVSADM_NOTIFY, "r");
+ if (getline (&line, &line_len, fp) < 0)
+ {
+ if (feof (fp))
+ error (0, 0, "cannot read %s: end of file", CVSADM_NOTIFY);
+ else
+ error (0, errno, "cannot read %s", CVSADM_NOTIFY);
+ goto error_exit;
+ }
+ cp = strchr (line, '\t');
+ if (cp == NULL)
+ {
+ error (0, 0, "malformed %s file", CVSADM_NOTIFY);
+ goto error_exit;
+ }
+ *cp = '\0';
+ if (strcmp (filename, line + 1) != 0)
+ {
+ error (0, 0, "protocol error: notified %s, expected %s", filename,
+ line + 1);
+ }
+
+ if (getline (&line, &line_len, fp) < 0)
+ {
+ if (feof (fp))
+ {
+ free (line);
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", CVSADM_NOTIFY);
+ if ( CVS_UNLINK (CVSADM_NOTIFY) < 0)
+ error (0, errno, "cannot remove %s", CVSADM_NOTIFY);
+ return;
+ }
+ else
+ {
+ error (0, errno, "cannot read %s", CVSADM_NOTIFY);
+ goto error_exit;
+ }
+ }
+ newf = open_file (CVSADM_NOTIFYTMP, "w");
+ if (fputs (line, newf) < 0)
+ {
+ error (0, errno, "cannot write %s", CVSADM_NOTIFYTMP);
+ goto error2;
+ }
+ while ((nread = fread (line, 1, line_len, fp)) > 0)
+ {
+ p = line;
+ while ((nwritten = fwrite (p, 1, nread, newf)) > 0)
+ {
+ nread -= nwritten;
+ p += nwritten;
+ }
+ if (ferror (newf))
+ {
+ error (0, errno, "cannot write %s", CVSADM_NOTIFYTMP);
+ goto error2;
+ }
+ }
+ if (ferror (fp))
+ {
+ error (0, errno, "cannot read %s", CVSADM_NOTIFY);
+ goto error2;
+ }
+ if (fclose (newf) < 0)
+ {
+ error (0, errno, "cannot close %s", CVSADM_NOTIFYTMP);
+ goto error_exit;
+ }
+ free (line);
+ if (fclose (fp) < 0)
+ {
+ error (0, errno, "cannot close %s", CVSADM_NOTIFY);
+ return;
+ }
+
+ {
+ /* In this case, we want rename_file() to ignore noexec. */
+ int saved_noexec = noexec;
+ noexec = 0;
+ rename_file (CVSADM_NOTIFYTMP, CVSADM_NOTIFY);
+ noexec = saved_noexec;
+ }
+
+ return;
+ error2:
+ (void) fclose (newf);
+ error_exit:
+ free (line);
+ (void) fclose (fp);
+}
+
+static void
+handle_notified (args, len)
+ char *args;
+ int len;
+{
+ call_in_directory (args, notified_a_file, NULL);
+}
+
+void
+client_notify (repository, update_dir, filename, notif_type, val)
+ const char *repository;
+ const char *update_dir;
+ const char *filename;
+ int notif_type;
+ const char *val;
+{
+ char buf[2];
+
+ send_a_repository ("", repository, update_dir);
+ send_to_server ("Notify ", 0);
+ send_to_server (filename, 0);
+ send_to_server ("\012", 1);
+ buf[0] = notif_type;
+ buf[1] = '\0';
+ send_to_server (buf, 1);
+ send_to_server ("\t", 1);
+ send_to_server (val, 0);
+}
+
+/*
+ * Send an option with an argument, dealing correctly with newlines in
+ * the argument. If ARG is NULL, forget the whole thing.
+ */
+void
+option_with_arg (option, arg)
+ char *option;
+ char *arg;
+{
+ if (arg == NULL)
+ return;
+
+ send_to_server ("Argument ", 0);
+ send_to_server (option, 0);
+ send_to_server ("\012", 1);
+
+ send_arg (arg);
+}
+
+/* Send a date to the server. The input DATE is in RCS format.
+ The time will be GMT.
+
+ We then convert that to the format required in the protocol
+ (including the "-D" option) and send it. According to
+ cvsclient.texi, RFC 822/1123 format is preferred. */
+
+void
+client_senddate (date)
+ const char *date;
+{
+ char buf[MAXDATELEN];
+
+ date_to_internet (buf, (char *)date);
+ option_with_arg ("-D", buf);
+}
+
+void
+send_init_command ()
+{
+ /* This is here because we need the current_parsed_root->directory variable. */
+ send_to_server ("init ", 0);
+ send_to_server (current_parsed_root->directory, 0);
+ send_to_server ("\012", 0);
+}
+
+#endif /* CLIENT_SUPPORT */
OpenPOWER on IntegriCloud