/contrib/cvs/src/client.c
https://bitbucket.org/freebsd/freebsd-head/ · C · 5948 lines · 4003 code · 769 blank · 1176 comment · 924 complexity · 9511b64d9b8ee98d0142435b9235f1e7 MD5 · raw file
Large files are truncated click here to view the full file
- /* 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,…