summaryrefslogtreecommitdiff
path: root/usr.bin/cvs/proto.c
diff options
context:
space:
mode:
authorJean-Francois Brousseau <jfb@cvs.openbsd.org>2004-07-13 22:02:41 +0000
committerJean-Francois Brousseau <jfb@cvs.openbsd.org>2004-07-13 22:02:41 +0000
commit00e6351a497dcb54dcf25371825dd7cfb67f8b93 (patch)
tree60a31f1eb4b0c0b32b1b87d455a7bbe51e7c210f /usr.bin/cvs/proto.c
parent0f7b1f29354d9c9700943e8e98c88574f7ec3bfd (diff)
* initial import from the cvs-tools module
Diffstat (limited to 'usr.bin/cvs/proto.c')
-rw-r--r--usr.bin/cvs/proto.c821
1 files changed, 821 insertions, 0 deletions
diff --git a/usr.bin/cvs/proto.c b/usr.bin/cvs/proto.c
new file mode 100644
index 00000000000..e5e23cdc6e2
--- /dev/null
+++ b/usr.bin/cvs/proto.c
@@ -0,0 +1,821 @@
+/* $OpenBSD: proto.c,v 1.1 2004/07/13 22:02:40 jfb Exp $ */
+/*
+ * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+/*
+ * CVS client/server protocol
+ * ==========================
+ *
+ * The following code implements the CVS client/server protocol, which is
+ * documented at the following URL:
+ * http://www.loria.fr/~molli/cvs/doc/cvsclient_toc.html
+ *
+ * The protocol is split up into two parts; the first part is the client side
+ * of things and is composed of all the response handlers, which are all named
+ * with a prefix of "cvs_resp_". The second part is the set of request
+ * handlers used by the server. These handlers process the request and
+ * generate the appropriate response to send back. The prefix for request
+ * handlers is "cvs_req_".
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <sysexits.h>
+#ifdef CVS_ZLIB
+#include <zlib.h>
+#endif
+
+#include "buf.h"
+#include "cvs.h"
+#include "log.h"
+
+
+
+extern int verbosity;
+extern int cvs_compress;
+extern char *cvs_rsh;
+extern int cvs_trace;
+extern int cvs_nolog;
+extern int cvs_readonly;
+
+extern struct cvsroot *cvs_root;
+
+
+
+/*
+ * Local and remote directory used by the `Directory' request.
+ */
+char cvs_ldir[MAXPATHLEN];
+char cvs_rdir[MAXPATHLEN];
+
+
+
+char *cvs_fcksum = NULL;
+
+mode_t cvs_lastmode = 0;
+
+
+static int cvs_resp_validreq (int, char *);
+static int cvs_resp_cksum (int, char *);
+static int cvs_resp_m (int, char *);
+static int cvs_resp_ok (int, char *);
+static int cvs_resp_error (int, char *);
+static int cvs_resp_statdir (int, char *);
+static int cvs_resp_newentry (int, char *);
+static int cvs_resp_updated (int, char *);
+static int cvs_resp_removed (int, char *);
+static int cvs_resp_mode (int, char *);
+
+
+
+
+struct cvs_req {
+ int req_id;
+ char req_str[32];
+ u_int req_flags;
+ int (*req_hdlr)(int, char *);
+} cvs_requests[] = {
+ { CVS_REQ_DIRECTORY, "Directory", 0, NULL },
+ { CVS_REQ_MAXDOTDOT, "Max-dotdot", 0, NULL },
+ { CVS_REQ_STATICDIR, "Static-directory", 0, NULL },
+ { CVS_REQ_STICKY, "Sticky", 0, NULL },
+ { CVS_REQ_ENTRY, "Entry", 0, NULL },
+ { CVS_REQ_ENTRYEXTRA, "EntryExtra", 0, NULL },
+ { CVS_REQ_CHECKINTIME, "Checkin-time", 0, NULL },
+ { CVS_REQ_MODIFIED, "Modified", 0, NULL },
+ { CVS_REQ_ISMODIFIED, "Is-modified", 0, NULL },
+ { CVS_REQ_UNCHANGED, "Unchanged", 0, NULL },
+ { CVS_REQ_USEUNCHANGED, "UseUnchanged", 0, NULL },
+ { CVS_REQ_NOTIFY, "Notify", 0, NULL },
+ { CVS_REQ_NOTIFYUSER, "NotifyUser", 0, NULL },
+ { CVS_REQ_QUESTIONABLE, "Questionable", 0, NULL },
+ { CVS_REQ_CASE, "Case", 0, NULL },
+ { CVS_REQ_UTF8, "Utf8", 0, NULL },
+ { CVS_REQ_ARGUMENT, "Argument", 0, NULL },
+ { CVS_REQ_ARGUMENTX, "Argumentx", 0, NULL },
+ { CVS_REQ_GLOBALOPT, "Global_option", 0, NULL },
+ { CVS_REQ_GZIPSTREAM, "Gzip-stream", 0, NULL },
+ { CVS_REQ_READCVSRC2, "read-cvsrc2", 0, NULL },
+ { CVS_REQ_READWRAP, "read-cvswrappers", 0, NULL },
+ { CVS_REQ_READIGNORE, "read-cvsignore", 0, NULL },
+ { CVS_REQ_ERRIFREADER, "Error-If-Reader", 0, NULL },
+ { CVS_REQ_VALIDRCSOPT, "Valid-RcsOptions", 0, NULL },
+ { CVS_REQ_SET, "Set", 0, NULL },
+ { CVS_REQ_XPANDMOD, "expand-modules", 0, NULL },
+ { CVS_REQ_LOG, "log", 0, NULL },
+ { CVS_REQ_CO, "co", 0, NULL },
+ { CVS_REQ_EXPORT, "export", 0, NULL },
+ { CVS_REQ_RANNOTATE, "rannotate", 0, NULL },
+ { CVS_REQ_RDIFF, "rdiff", 0, NULL },
+ { CVS_REQ_RLOG, "rlog", 0, NULL },
+ { CVS_REQ_RTAG, "rtag", 0, NULL },
+ { CVS_REQ_INIT, "init", 0, NULL },
+ { CVS_REQ_UPDATE, "update", 0, NULL },
+ { CVS_REQ_HISTORY, "history", 0, NULL },
+ { CVS_REQ_IMPORT, "import", 0, NULL },
+ { CVS_REQ_ADD, "add", 0, NULL },
+ { CVS_REQ_REMOVE, "remove", 0, NULL },
+ { CVS_REQ_RELEASE, "release", 0, NULL },
+ { CVS_REQ_ROOT, "Root", 0, NULL },
+ { CVS_REQ_VALIDRESP, "Valid-responses", 0, NULL },
+ { CVS_REQ_VALIDREQ, "valid-requests", 0, NULL },
+ { CVS_REQ_VERSION, "version", 0, NULL },
+ { CVS_REQ_NOOP, "noop", 0, NULL },
+ { CVS_REQ_DIFF, "diff", 0, NULL },
+};
+
+
+struct cvs_resp {
+ u_int resp_id;
+ char resp_str[32];
+ int (*resp_hdlr)(int, char *);
+} cvs_responses[] = {
+ { CVS_RESP_OK, "ok", cvs_resp_ok },
+ { CVS_RESP_ERROR, "error", cvs_resp_error },
+ { CVS_RESP_VALIDREQ, "Valid-requests", cvs_resp_validreq },
+ { CVS_RESP_M, "M", cvs_resp_m },
+ { CVS_RESP_MBINARY, "Mbinary", cvs_resp_m },
+ { CVS_RESP_MT, "MT", cvs_resp_m },
+ { CVS_RESP_E, "E", cvs_resp_m },
+ { CVS_RESP_F, "F", cvs_resp_m },
+ { CVS_RESP_CREATED, "Created", cvs_resp_updated },
+ { CVS_RESP_UPDATED, "Updated", cvs_resp_updated },
+ { CVS_RESP_UPDEXIST, "Update-existing", cvs_resp_updated },
+ { CVS_RESP_REMOVED, "Removed", cvs_resp_removed },
+ { CVS_RESP_MERGED, "Merged", NULL },
+ { CVS_RESP_CKSUM, "Checksum", cvs_resp_cksum },
+ { CVS_RESP_CLRSTATDIR, "Clear-static-directory", cvs_resp_statdir },
+ { CVS_RESP_SETSTATDIR, "Set-static-directory", cvs_resp_statdir },
+ { CVS_RESP_NEWENTRY, "New-entry", cvs_resp_newentry },
+ { CVS_RESP_CHECKEDIN, "Checked-in", cvs_resp_newentry },
+ { CVS_RESP_MODE, "Mode", cvs_resp_mode },
+};
+
+
+#define CVS_NBREQ (sizeof(cvs_requests)/sizeof(cvs_requests[0]))
+#define CVS_NBRESP (sizeof(cvs_responses)/sizeof(cvs_responses[0]))
+
+/* mask of requets supported by server */
+static u_char cvs_server_validreq[CVS_REQ_MAX + 1];
+
+
+/*
+ * cvs_req_getbyid()
+ *
+ */
+
+const char*
+cvs_req_getbyid(int reqid)
+{
+ u_int i;
+
+ for (i = 0; i < CVS_NBREQ; i++)
+ if (cvs_requests[i].req_id == reqid)
+ return (cvs_requests[i].req_str);
+ return (NULL);
+}
+
+
+/*
+ * cvs_req_getbyname()
+ */
+
+int
+cvs_req_getbyname(const char *rname)
+{
+ u_int i;
+
+ for (i = 0; i < CVS_NBREQ; i++)
+ if (strcmp(cvs_requests[i].req_str, rname) == 0)
+ return (cvs_requests[i].req_id);
+
+ return (-1);
+}
+
+
+/*
+ * cvs_req_getvalid()
+ *
+ * Build a space-separated list of all the requests that this protocol
+ * implementation supports.
+ */
+
+char*
+cvs_req_getvalid(void)
+{
+ u_int i;
+ size_t len;
+ char *vrstr;
+ BUF *buf;
+
+ buf = cvs_buf_alloc(512, BUF_AUTOEXT);
+ if (buf == NULL)
+ return (NULL);
+
+ cvs_buf_set(buf, cvs_requests[0].req_str,
+ strlen(cvs_requests[0].req_str), 0);
+
+ for (i = 1; i < CVS_NBREQ; i++) {
+ if ((cvs_buf_putc(buf, ' ') < 0) ||
+ (cvs_buf_append(buf, cvs_requests[i].req_str,
+ strlen(cvs_requests[i].req_str)) < 0)) {
+ cvs_buf_free(buf);
+ return (NULL);
+ }
+ }
+
+ /* NUL-terminate */
+ if (cvs_buf_putc(buf, '\0') < 0) {
+ cvs_buf_free(buf);
+ return (NULL);
+ }
+
+ len = cvs_buf_size(buf);
+ vrstr = (char *)malloc(len);
+
+ cvs_buf_copy(buf, 0, vrstr, len);
+
+ cvs_buf_free(buf);
+
+ return (vrstr);
+}
+
+
+/*
+ * cvs_req_handle()
+ *
+ * Generic request handler dispatcher.
+ */
+
+int
+cvs_req_handle(char *line)
+{
+ return (0);
+}
+
+
+/*
+ * cvs_resp_getbyid()
+ *
+ */
+
+const char*
+cvs_resp_getbyid(int respid)
+{
+ u_int i;
+
+ for (i = 0; i < CVS_NBREQ; i++)
+ if (cvs_responses[i].resp_id == respid)
+ return (cvs_responses[i].resp_str);
+ return (NULL);
+}
+
+
+/*
+ * cvs_resp_getbyname()
+ */
+
+int
+cvs_resp_getbyname(const char *rname)
+{
+ u_int i;
+
+ for (i = 0; i < CVS_NBREQ; i++)
+ if (strcmp(cvs_responses[i].resp_str, rname) == 0)
+ return (cvs_responses[i].resp_id);
+
+ return (-1);
+}
+
+
+/*
+ * cvs_resp_getvalid()
+ *
+ * Build a space-separated list of all the responses that this protocol
+ * implementation supports.
+ */
+
+char*
+cvs_resp_getvalid(void)
+{
+ u_int i;
+ size_t len;
+ char *vrstr;
+ BUF *buf;
+
+ buf = cvs_buf_alloc(512, BUF_AUTOEXT);
+ if (buf == NULL)
+ return (NULL);
+
+ cvs_buf_set(buf, cvs_responses[0].resp_str,
+ strlen(cvs_responses[0].resp_str), 0);
+
+ for (i = 1; i < CVS_NBRESP; i++) {
+ if ((cvs_buf_putc(buf, ' ') < 0) ||
+ (cvs_buf_append(buf, cvs_responses[i].resp_str,
+ strlen(cvs_responses[i].resp_str)) < 0)) {
+ cvs_buf_free(buf);
+ return (NULL);
+ }
+ }
+
+ /* NUL-terminate */
+ if (cvs_buf_putc(buf, '\0') < 0) {
+ cvs_buf_free(buf);
+ return (NULL);
+ }
+
+ len = cvs_buf_size(buf);
+ vrstr = (char *)malloc(len);
+
+ cvs_buf_copy(buf, 0, vrstr, len);
+ cvs_buf_free(buf);
+
+ return (vrstr);
+}
+
+
+/*
+ * cvs_resp_handle()
+ *
+ * Generic response handler dispatcher. The handler expects the first line
+ * of the command as single argument.
+ * Returns the return value of the command on success, or -1 on failure.
+ */
+
+int
+cvs_resp_handle(char *line)
+{
+ u_int i;
+ size_t len;
+ char *cp, *cmd;
+
+ cmd = line;
+
+ cp = strchr(cmd, ' ');
+ if (cp != NULL)
+ *(cp++) = '\0';
+
+ for (i = 0; i < CVS_NBRESP; i++) {
+ if (strcmp(cvs_responses[i].resp_str, cmd) == 0)
+ return (*cvs_responses[i].resp_hdlr)
+ (cvs_responses[i].resp_id, cp);
+ }
+
+ /* unhandled */
+ return (-1);
+}
+
+
+/*
+ * cvs_client_sendinfo()
+ *
+ * Initialize the connection status by first requesting the list of
+ * supported requests from the server. Then, we send the CVSROOT variable
+ * with the `Root' request.
+ * Returns 0 on success, or -1 on failure.
+ */
+
+static int
+cvs_client_sendinfo(void)
+{
+ /* first things first, get list of valid requests from server */
+ if (cvs_client_sendreq(CVS_REQ_VALIDREQ, NULL, 1) < 0) {
+ cvs_log(LP_ERR, "failed to get valid requests from server");
+ return (-1);
+ }
+
+ /* now share our global options with the server */
+ if (verbosity == 1)
+ cvs_client_sendreq(CVS_REQ_GLOBALOPT, "-q", 0);
+ else if (verbosity == 0)
+ cvs_client_sendreq(CVS_REQ_GLOBALOPT, "-Q", 0);
+
+ if (cvs_nolog)
+ cvs_client_sendreq(CVS_REQ_GLOBALOPT, "-l", 0);
+ if (cvs_readonly)
+ cvs_client_sendreq(CVS_REQ_GLOBALOPT, "-r", 0);
+ if (cvs_trace)
+ cvs_client_sendreq(CVS_REQ_GLOBALOPT, "-t", 0);
+
+ /* now send the CVSROOT to the server */
+ if (cvs_client_sendreq(CVS_REQ_ROOT, cvs_root->cr_dir, 0) < 0)
+ return (-1);
+
+ /* not sure why, but we have to send this */
+ if (cvs_client_sendreq(CVS_REQ_USEUNCHANGED, NULL, 0) < 0)
+ return (-1);
+
+ return (0);
+}
+
+
+/*
+ * cvs_resp_validreq()
+ *
+ * Handler for the `Valid-requests' response. The list of valid requests is
+ * split on spaces and each request's entry in the valid request array is set
+ * to 1 to indicate the validity.
+ * Returns 0 on success, or -1 on failure.
+ */
+
+static int
+cvs_resp_validreq(int type, char *line)
+{
+ int i;
+ char *sp, *ep;
+
+ /* parse the requests */
+ sp = line;
+ do {
+ ep = strchr(sp, ' ');
+ if (ep != NULL)
+ *ep = '\0';
+
+ i = cvs_req_getbyname(sp);
+ if (i != -1)
+ cvs_server_validreq[i] = 1;
+
+ if (ep != NULL)
+ sp = ep + 1;
+ } while (ep != NULL);
+
+ return (0);
+}
+
+
+/*
+ * cvs_resp_m()
+ *
+ * Handler for the `M', `F' and `E' responses.
+ */
+
+static int
+cvs_resp_m(int type, char *line)
+{
+ FILE *stream;
+
+ stream = NULL;
+
+ switch (type) {
+ case CVS_RESP_F:
+ fflush(stderr);
+ return (0);
+ case CVS_RESP_M:
+ stream = stdout;
+ break;
+ case CVS_RESP_E:
+ stream = stderr;
+ break;
+ case CVS_RESP_MT:
+ case CVS_RESP_MBINARY:
+ cvs_log(LP_WARN, "MT and Mbinary not supported in client yet");
+ break;
+ }
+
+ fputs(line, stream);
+ fputc('\n', stream);
+
+ return (0);
+}
+
+
+/*
+ * cvs_resp_ok()
+ *
+ * Handler for the `ok' response. This handler's job is to
+ */
+
+static int
+cvs_resp_ok(int type, char *line)
+{
+ return (1);
+}
+
+
+/*
+ * cvs_resp_error()
+ *
+ * Handler for the `error' response. This handler's job is to
+ */
+
+static int
+cvs_resp_error(int type, char *line)
+{
+ return (1);
+}
+
+
+/*
+ * cvs_resp_statdir()
+ *
+ * Handler for the `Clear-static-directory' and `Set-static-directory'
+ * responses.
+ */
+
+static int
+cvs_resp_statdir(int type, char *line)
+{
+ int fd;
+ char statpath[MAXPATHLEN];
+
+ snprintf(statpath, sizeof(statpath), "%s/%s", line,
+ CVS_PATH_STATICENTRIES);
+
+ if ((type == CVS_RESP_CLRSTATDIR) &&
+ (unlink(statpath) == -1)) {
+ cvs_log(LP_ERRNO, "failed to unlink %s file",
+ CVS_PATH_STATICENTRIES);
+ return (-1);
+ }
+ else if (type == CVS_RESP_SETSTATDIR) {
+ fd = open(statpath, O_CREAT|O_TRUNC|O_WRONLY, 0400);
+ if (fd == -1) {
+ cvs_log(LP_ERRNO, "failed to create %s file",
+ CVS_PATH_STATICENTRIES);
+ return (-1);
+ }
+ (void)close(fd);
+ }
+
+ return (0);
+}
+
+
+/*
+ * cvs_resp_newentry()
+ *
+ * Handler for the `New-entry' response and `Checked-in' responses.
+ */
+
+static int
+cvs_resp_newentry(int type, char *line)
+{
+ char entbuf[128], path[MAXPATHLEN];
+ struct cvs_ent *entp;
+ CVSENTRIES *entfile;
+
+ if (cvs_splitpath(line, entbuf, sizeof(entbuf), NULL, 0) < 0)
+ return (-1);
+
+ snprintf(path, sizeof(path), "%s/" CVS_PATH_ENTRIES, entbuf);
+
+ /* get the remote path */
+ cvs_client_getln(entbuf, sizeof(entbuf));
+
+ /* get the new Entries line */
+ if (cvs_client_getln(entbuf, sizeof(entbuf)) < 0)
+ return (-1);
+
+ entp = cvs_ent_parse(entbuf);
+ if (entp == NULL)
+ return (-1);
+
+ entfile = cvs_ent_open(path);
+ if (entfile == NULL)
+ return (-1);
+
+ cvs_ent_add(entfile, entp);
+
+ cvs_ent_close(entfile);
+
+ return (0);
+}
+
+
+/*
+ * cvs_resp_cksum()
+ *
+ * Handler for the `Checksum' response. We store the checksum received for
+ * the next file in a dynamically-allocated buffer pointed to by <cvs_fcksum>.
+ * Upon next file reception, the handler checks to see if there is a stored
+ * checksum.
+ * The file handler must make sure that the checksums match and free the
+ * checksum buffer once it's done to indicate there is no further checksum.
+ */
+
+static int
+cvs_resp_cksum(int type, char *line)
+{
+ if (cvs_fcksum != NULL) {
+ cvs_log(LP_WARN, "unused checksum");
+ free(cvs_fcksum);
+ }
+
+ cvs_fcksum = strdup(line);
+ if (cvs_fcksum == NULL) {
+ cvs_log(LP_ERRNO, "failed to copy checksum string");
+ return (-1);
+ }
+
+ return (0);
+}
+
+
+/*
+ * cvs_resp_updated()
+ *
+ * Handler for the `Updated' response.
+ */
+
+static int
+cvs_resp_updated(int type, char *line)
+{
+ char cksum_buf[CVS_CKSUM_LEN];
+
+ if (type == CVS_RESP_CREATED) {
+ }
+ else if (type == CVS_RESP_UPDEXIST) {
+ }
+ else if (type == CVS_RESP_UPDATED) {
+ }
+
+ if (cvs_recvfile(line) < 0) {
+ return (-1);
+ }
+
+ /* now see if there is a checksum */
+ if (cvs_cksum(line, cksum_buf, sizeof(cksum_buf)) < 0) {
+ }
+
+ if (strcmp(cksum_buf, cvs_fcksum) != 0) {
+ cvs_log(LP_ERR, "checksum error on received file");
+ (void)unlink(line);
+ }
+
+ free(cvs_fcksum);
+ cvs_fcksum = NULL;
+
+ return (0);
+}
+
+
+/*
+ * cvs_resp_removed()
+ *
+ * Handler for the `Updated' response.
+ */
+
+static int
+cvs_resp_removed(int type, char *line)
+{
+ return (0);
+}
+
+
+/*
+ * cvs_resp_mode()
+ *
+ * Handler for the `Mode' response.
+ */
+
+static int
+cvs_resp_mode(int type, char *line)
+{
+ if (cvs_strtomode(line, &cvs_lastmode) < 0) {
+ return (-1);
+ }
+ return (0);
+}
+
+
+/*
+ * cvs_sendfile()
+ *
+ * Send the mode and size of a file followed by the file's contents.
+ * Returns 0 on success, or -1 on failure.
+ */
+
+int
+cvs_sendfile(const char *path)
+{
+ int fd;
+ ssize_t ret;
+ char buf[4096];
+ struct stat st;
+
+ if (stat(path, &st) == -1) {
+ cvs_log(LP_ERRNO, "failed to stat `%s'", path);
+ return (-1);
+ }
+
+ fd = open(path, O_RDONLY, 0);
+ if (fd == -1) {
+ return (-1);
+ }
+
+ if (cvs_modetostr(st.st_mode, buf, sizeof(buf)) < 0)
+ return (-1);
+
+ cvs_client_sendln(buf);
+ snprintf(buf, sizeof(buf), "%lld\n", st.st_size);
+ cvs_client_sendln(buf);
+
+ while ((ret = read(fd, buf, sizeof(buf))) != 0) {
+ if (ret == -1) {
+ cvs_log(LP_ERRNO, "failed to read file `%s'", path);
+ return (-1);
+ }
+
+ cvs_client_sendraw(buf, (size_t)ret);
+
+ }
+
+ (void)close(fd);
+
+ return (0);
+}
+
+
+/*
+ * cvs_recvfile()
+ *
+ * Receive the mode and size of a file followed the file's contents and
+ * create or update the file whose path is <path> with the received
+ * information.
+ */
+
+int
+cvs_recvfile(const char *path)
+{
+ int fd;
+ mode_t mode;
+ size_t len;
+ ssize_t ret;
+ off_t fsz, cnt;
+ char buf[4096], *ep;
+
+ if ((cvs_client_getln(buf, sizeof(buf)) < 0) ||
+ (cvs_strtomode(buf, &mode) < 0)) {
+ return (-1);
+ }
+
+ cvs_client_getln(buf, sizeof(buf));
+
+ fsz = (off_t)strtol(buf, &ep, 10);
+ if (*ep != '\0') {
+ cvs_log(LP_ERR, "parse error in file size transmission");
+ return (-1);
+ }
+
+ fd = open(path, O_RDONLY, mode);
+ if (fd == -1) {
+ cvs_log(LP_ERRNO, "failed to open `%s'", path);
+ return (-1);
+ }
+
+ cnt = 0;
+ do {
+ len = MIN(sizeof(buf), (size_t)(fsz - cnt));
+ ret = cvs_client_recvraw(buf, len);
+ if (ret == -1) {
+ (void)close(fd);
+ (void)unlink(path);
+ return (-1);
+ }
+
+ if (write(fd, buf, (size_t)ret) == -1) {
+ cvs_log(LP_ERRNO,
+ "failed to write contents to file `%s'", path);
+ (void)close(fd);
+ (void)unlink(path);
+ return (-1);
+ }
+
+ cnt += (off_t)ret;
+ } while (cnt < fsz);
+
+ (void)close(fd);
+
+ return (0);
+}