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, 0 insertions, 5948 deletions
diff --git a/contrib/cvs/src/client.c b/contrib/cvs/src/client.c
deleted file mode 100644
index 1c8a5d0..0000000
--- a/contrib/cvs/src/client.c
+++ /dev/null
@@ -1,5948 +0,0 @@
-/* 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