summaryrefslogtreecommitdiff
path: root/usr.bin/cvs/client.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/cvs/client.c')
-rw-r--r--usr.bin/cvs/client.c585
1 files changed, 585 insertions, 0 deletions
diff --git a/usr.bin/cvs/client.c b/usr.bin/cvs/client.c
new file mode 100644
index 00000000000..0ef1f5fb4ad
--- /dev/null
+++ b/usr.bin/cvs/client.c
@@ -0,0 +1,585 @@
+/* $OpenBSD: client.c,v 1.9 2006/07/07 17:37:17 joris Exp $ */
+/*
+ * Copyright (c) 2006 Joris Vink <joris@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "includes.h"
+
+#include "cvs.h"
+#include "log.h"
+#include "diff.h"
+#include "remote.h"
+
+struct cvs_req cvs_requests[] = {
+ /* this is what our client will use, the server should support it */
+ { "Root", 1, cvs_server_root, REQ_NEEDED },
+ { "Valid-responses", 1, cvs_server_validresp, REQ_NEEDED },
+ { "valid-requests", 1, cvs_server_validreq, REQ_NEEDED },
+ { "Global_option", 0, cvs_server_globalopt, REQ_NEEDED },
+ { "Directory", 0, cvs_server_directory, REQ_NEEDED },
+ { "Entry", 0, cvs_server_entry, REQ_NEEDED },
+ { "Modified", 0, cvs_server_modified, REQ_NEEDED },
+ { "UseUnchanged", 0, cvs_server_useunchanged, REQ_NEEDED },
+ { "Unchanged", 0, cvs_server_unchanged, REQ_NEEDED },
+ { "Questionable", 0, cvs_server_questionable, REQ_NEEDED },
+ { "Argument", 0, cvs_server_argument, REQ_NEEDED },
+
+ /*
+ * used to tell the server what is going on in our
+ * working copy, unsupported until we are told otherwise
+ */
+ { "Max-dotdot", 0, NULL, 0 },
+ { "Static-directory", 0, NULL, 0 },
+ { "Sticky", 0, NULL, 0 },
+ { "Checkin-prog", 0, NULL, 0 },
+ { "Update-prog", 0, NULL, 0 },
+ { "Kopt", 0, NULL, 0 },
+ { "Checkin-time", 0, NULL, 0 },
+ { "Is-modified", 0, NULL, 0 },
+ { "Notify", 0, NULL, 0 },
+ { "Case", 0, NULL, 0 },
+ { "Argumentx", 0, NULL, 0 },
+ { "Gzip-stream", 0, NULL, 0 },
+ { "Kerberos-encrypt", 0, NULL, 0 },
+ { "Gssapi-encrypt", 0, NULL, 0 },
+ { "Gssapi-authenticate", 0, NULL, 0 },
+ { "Set", 0, NULL, 0 },
+ { "expand-modules", 0, NULL, 0 },
+
+ /* commands that might be supported */
+ { "ci", 0, cvs_server_commit, 0 },
+ { "co", 0, NULL, 0 },
+ { "diff", 0, cvs_server_diff, 0 },
+ { "tag", 0, NULL, 0 },
+ { "status", 0, cvs_server_status, 0 },
+ { "admin", 0, NULL, 0 },
+ { "history", 0, NULL, 0 },
+ { "watchers", 0, NULL, 0 },
+ { "editors", 0, NULL, 0 },
+ { "annotate", 0, NULL, 0 },
+ { "log", 0, cvs_server_log, 0 },
+ { "export", 0, NULL, 0 },
+ { "rdiff", 0, NULL, 0 },
+ { "rtag", 0, NULL, 0 },
+ { "init", 0, NULL, 0 },
+ { "update", 0, cvs_server_update, 0 },
+ { "import", 0, NULL, 0 },
+ { "add", 0, NULL, 0 },
+ { "remove", 0, NULL, 0 },
+ { "watch-on", 0, NULL, 0 },
+ { "watch-off", 0, NULL, 0 },
+ { "watch-add", 0, NULL, 0 },
+ { "watch-remove", 0, NULL, 0 },
+ { "release", 0, NULL, 0 },
+ { "noop", 0, NULL, 0 },
+ { "update-patches", 0, NULL, 0 },
+ { "gzip-file-contents", 0, NULL, 0 },
+ { "wrapper-sendme-rcsOptions", 0, NULL, 0 },
+ { "", -1, NULL, 0 }
+};
+
+static char *client_get_supported_responses(void);
+static char *lastdir = NULL;
+static int end_of_response = 0;
+
+static char *
+client_get_supported_responses(void)
+{
+ BUF *bp;
+ char *d;
+ int i, first;
+
+ first = 0;
+ bp = cvs_buf_alloc(512, BUF_AUTOEXT);
+ for (i = 0; cvs_responses[i].supported != -1; i++) {
+ if (cvs_responses[i].hdlr == NULL)
+ continue;
+
+ if (first != 0)
+ cvs_buf_append(bp, " ", 1);
+ else
+ first++;
+ cvs_buf_append(bp, cvs_responses[i].name,
+ strlen(cvs_responses[i].name));
+ }
+
+ cvs_buf_putc(bp, '\0');
+ d = cvs_buf_release(bp);
+ return (d);
+}
+
+void
+cvs_client_connect_to_server(void)
+{
+ char *cmd, *argv[9], *resp;
+ int ifd[2], ofd[2], argc;
+
+ switch (current_cvsroot->cr_method) {
+ case CVS_METHOD_PSERVER:
+ case CVS_METHOD_KSERVER:
+ case CVS_METHOD_GSERVER:
+ case CVS_METHOD_FORK:
+ case CVS_METHOD_EXT:
+ fatal("the specified connection method is not support");
+ default:
+ break;
+ }
+
+ if (pipe(ifd) == -1)
+ fatal("cvs_client_connect: %s", strerror(errno));
+ if (pipe(ofd) == -1)
+ fatal("cvs_client_connect: %s", strerror(errno));
+
+ switch (fork()) {
+ case -1:
+ fatal("cvs_client_connect: fork failed: %s", strerror(errno));
+ case 0:
+ if (dup2(ifd[0], STDIN_FILENO) == -1)
+ fatal("cvs_client_connect: %s", strerror(errno));
+ if (dup2(ofd[1], STDOUT_FILENO) == -1)
+ fatal("cvs_client_connect: %s", strerror(errno));
+
+ close(ifd[1]);
+ close(ofd[0]);
+
+ if ((cmd = getenv("CVS_SERVER")) == NULL)
+ cmd = CVS_SERVER_DEFAULT;
+
+ argc = 0;
+ argv[argc++] = cvs_rsh;
+
+ if (current_cvsroot->cr_user != NULL) {
+ argv[argc++] = "-l";
+ argv[argc++] = current_cvsroot->cr_user;
+ }
+
+ argv[argc++] = current_cvsroot->cr_host;
+ argv[argc++] = cmd;
+ argv[argc++] = "server";
+ argv[argc] = NULL;
+
+ cvs_log(LP_TRACE, "connecting to server %s",
+ current_cvsroot->cr_host);
+
+ execvp(argv[0], argv);
+ fatal("cvs_client_connect: failed to execute cvs server");
+ default:
+ break;
+ }
+
+ close(ifd[0]);
+ close(ofd[1]);
+
+ if ((current_cvsroot->cr_srvin = fdopen(ifd[1], "w")) == NULL)
+ fatal("cvs_client_connect: %s", strerror(errno));
+ if ((current_cvsroot->cr_srvout = fdopen(ofd[0], "r")) == NULL)
+ fatal("cvs_client_connect: %s", strerror(errno));
+
+ setvbuf(current_cvsroot->cr_srvin, NULL,_IOLBF, 0);
+ setvbuf(current_cvsroot->cr_srvout, NULL, _IOLBF, 0);
+
+ cvs_client_send_request("Root %s", current_cvsroot->cr_dir);
+
+ resp = client_get_supported_responses();
+ cvs_client_send_request("Valid-responses %s", resp);
+ xfree(resp);
+
+ cvs_client_send_request("valid-requests");
+ cvs_client_get_responses();
+
+ if (cvs_trace)
+ cvs_client_send_request("Global_option -t");
+
+ if (verbosity == 2)
+ cvs_client_send_request("Global_option -V");
+
+ cvs_client_send_request("UseUnchanged");
+}
+
+void
+cvs_client_send_request(char *fmt, ...)
+{
+ va_list ap;
+ char *data, *s;
+ struct cvs_req *req;
+
+ va_start(ap, fmt);
+ vasprintf(&data, fmt, ap);
+ va_end(ap);
+
+ if ((s = strchr(data, ' ')) != NULL)
+ *s = '\0';
+
+ req = cvs_remote_get_request_info(data);
+ if (req == NULL)
+ fatal("'%s' is an unknown request", data);
+
+ if (req->supported != 1)
+ fatal("remote cvs server does not support '%s'", data);
+
+ if (s != NULL)
+ *s = ' ';
+
+ cvs_remote_output(data);
+ xfree(data);
+}
+
+void
+cvs_client_read_response(void)
+{
+ char *cmd, *data;
+ struct cvs_resp *resp;
+
+ cmd = cvs_remote_input();
+ if ((data = strchr(cmd, ' ')) != NULL)
+ (*data++) = '\0';
+
+ resp = cvs_remote_get_response_info(cmd);
+ if (resp == NULL)
+ fatal("response '%s' is not supported by our client", cmd);
+
+ if (resp->hdlr == NULL)
+ fatal("opencvs client does not support '%s'", cmd);
+
+ (*resp->hdlr)(data);
+ xfree(cmd);
+}
+
+void
+cvs_client_get_responses(void)
+{
+ while (end_of_response != 1)
+ cvs_client_read_response();
+
+ end_of_response = 0;
+}
+
+void
+cvs_client_senddir(const char *dir)
+{
+ char *repo;
+
+ cvs_log(LP_TRACE, "cvs_client_senddir(%s)", dir);
+
+ if (lastdir != NULL && !strcmp(dir, lastdir))
+ return;
+
+ cvs_client_send_request("Directory %s", dir);
+
+ repo = xmalloc(MAXPATHLEN);
+ cvs_get_repository_path(dir, repo, MAXPATHLEN);
+ cvs_remote_output(repo);
+ xfree(repo);
+
+ if (lastdir != NULL)
+ xfree(lastdir);
+ lastdir = xstrdup(dir);
+}
+
+void
+cvs_client_sendfile(struct cvs_file *cf)
+{
+ int l;
+ size_t len;
+ char rev[16], timebuf[64], sticky[32];
+
+ if (cf->file_type != CVS_FILE)
+ return;
+
+ cvs_client_senddir(cf->file_wd);
+
+ cvs_log(LP_TRACE, "cvs_client_sendfile(%s)", cf->file_path);
+ cvs_remote_classify_file(cf);
+
+ if (cf->file_ent != NULL) {
+ if (cf->file_status == FILE_ADDED) {
+ len = strlcpy(rev, "0", sizeof(rev));
+ if (len >= sizeof(rev))
+ fatal("cvs_client_sendfile: truncation");
+
+ len = strlcpy(timebuf, "Initial ", sizeof(timebuf));
+ if (len >= sizeof(timebuf))
+ fatal("cvs_client_sendfile: truncation");
+
+ len = strlcat(timebuf, cf->file_name, sizeof(timebuf));
+ if (len >= sizeof(timebuf))
+ fatal("cvs_client_sendfile: truncation");
+ } else {
+ rcsnum_tostr(cf->file_ent->ce_rev, rev, sizeof(rev));
+ ctime_r(&cf->file_ent->ce_mtime, timebuf);
+ }
+
+ if (cf->file_ent->ce_conflict == NULL) {
+ if (timebuf[strlen(timebuf) - 1] == '\n')
+ timebuf[strlen(timebuf) - 1] = '\0';
+ } else {
+ len = strlcpy(timebuf, cf->file_ent->ce_conflict,
+ sizeof(timebuf));
+ if (len >= sizeof(timebuf))
+ fatal("cvs_client_sendfile: truncation");
+ }
+
+ sticky[0] = '\0';
+ if (cf->file_ent->ce_tag != NULL) {
+ l = snprintf(sticky, sizeof(sticky), "T%s",
+ cf->file_ent->ce_tag);
+ if (l == -1 || l >= (int)sizeof(sticky))
+ fatal("cvs_client_sendfile: overflow");
+ }
+
+ cvs_client_send_request("Entry /%s/%s%s/%s/%s/",
+ cf->file_name, (cf->file_status == FILE_REMOVED) ? "-" : "",
+ rev, timebuf, sticky);
+ }
+
+ switch (cf->file_status) {
+ case FILE_UNKNOWN:
+ if (cf->fd != -1)
+ cvs_client_send_request("Questionable %s",
+ cf->file_name);
+ break;
+ case FILE_ADDED:
+ case FILE_MODIFIED:
+ cvs_client_send_request("Modified %s", cf->file_name);
+ cvs_remote_send_file(cf->file_path);
+ break;
+ case FILE_UPTODATE:
+ cvs_client_send_request("Unchanged %s", cf->file_name);
+ break;
+ }
+}
+
+void
+cvs_client_send_files(char **argv, int argc)
+{
+ int i;
+
+ for (i = 0; i < argc; i++)
+ cvs_client_send_request("Argument %s", argv[i]);
+}
+
+void
+cvs_client_ok(char *data)
+{
+ cvs_log(LP_TRACE, "cvs_client_ok()");
+ end_of_response = 1;
+}
+
+void
+cvs_client_error(char *data)
+{
+ cvs_log(LP_TRACE, "cvs_client_error()");
+ end_of_response = 1;
+}
+
+void
+cvs_client_validreq(char *data)
+{
+ int i;
+ char *sp, *ep;
+ struct cvs_req *req;
+
+ sp = data;
+ do {
+ if ((ep = strchr(sp, ' ')) != NULL)
+ *ep = '\0';
+
+ req = cvs_remote_get_request_info(sp);
+ if (req != NULL)
+ req->supported = 1;
+
+ if (ep != NULL)
+ sp = ep + 1;
+ } while (ep != NULL);
+
+ for (i = 0; cvs_requests[i].supported != -1; i++) {
+ req = &cvs_requests[i];
+ if ((req->flags & REQ_NEEDED) &&
+ req->supported != 1) {
+ fatal("server does not support required '%s'",
+ req->name);
+ }
+ }
+}
+
+void
+cvs_client_e(char *data)
+{
+ cvs_printf("%s\n", data);
+}
+
+void
+cvs_client_m(char *data)
+{
+ cvs_printf("%s\n", data);
+}
+
+void
+cvs_client_checkedin(char *data)
+{
+ int l;
+ CVSENTRIES *entlist;
+ struct cvs_ent *ent, *newent;
+ char *dir, *entry, rev[16], timebuf[64], sticky[16];
+
+ cvs_log(LP_TRACE, "cvs_client_checkedin(%s)", data);
+
+ dir = cvs_remote_input();
+ entry = cvs_remote_input();
+ xfree(dir);
+
+ entlist = cvs_ent_open(data);
+ newent = cvs_ent_parse(entry);
+ ent = cvs_ent_get(entlist, newent->ce_name);
+ xfree(entry);
+
+ entry = xmalloc(CVS_ENT_MAXLINELEN);
+
+ rcsnum_tostr(newent->ce_rev, rev, sizeof(rev));
+ ctime_r(&ent->ce_mtime, timebuf);
+ if (timebuf[strlen(timebuf) - 1] == '\n')
+ timebuf[strlen(timebuf) - 1] = '\0';
+
+ sticky[0] = '\0';
+ if (ent->ce_tag != NULL) {
+ l = snprintf(sticky, sizeof(sticky), "T%s", ent->ce_tag);
+ if (l == -1 || l >= (int)sizeof(sticky))
+ fatal("cvs_client_checkedin: overflow");
+ }
+
+ l = snprintf(entry, CVS_ENT_MAXLINELEN, "/%s/%s/%s//%s/",
+ newent->ce_name, rev, timebuf, sticky);
+ if (l == -1 || l >= CVS_ENT_MAXLINELEN)
+ fatal("cvs_client_checkedin: overflow");
+
+ cvs_ent_free(ent);
+ cvs_ent_free(newent);
+ cvs_ent_add(entlist, entry);
+ cvs_ent_close(entlist, ENT_SYNC);
+
+ xfree(entry);
+}
+
+void
+cvs_client_updated(char *data)
+{
+ BUF *bp;
+ int l, fd;
+ time_t now;
+ mode_t fmode;
+ size_t flen;
+ CVSENTRIES *ent;
+ struct cvs_ent *e;
+ const char *errstr;
+ struct timeval tv[2];
+ char timebuf[32], *repo, *rpath, *entry, *mode;
+ char revbuf[32], *len, *fpath, *wdir;
+
+ cvs_log(LP_TRACE, "cvs_client_updated(%s)", data);
+
+ rpath = cvs_remote_input();
+ entry = cvs_remote_input();
+ mode = cvs_remote_input();
+ len = cvs_remote_input();
+
+ repo = xmalloc(MAXPATHLEN);
+ cvs_get_repository_path(".", repo, MAXPATHLEN);
+
+ if (strlen(repo) + 1 > strlen(rpath))
+ fatal("received a repository path that is too short");
+
+ fpath = rpath + strlen(repo) + 1;
+ if ((wdir = dirname(fpath)) == NULL)
+ fatal("cvs_client_updated: dirname: %s", strerror(errno));
+ xfree(repo);
+
+ flen = strtonum(len, 0, INT_MAX, &errstr);
+ if (errstr != NULL)
+ fatal("cvs_client_updated: %s: %s", len, errstr);
+ xfree(len);
+
+ cvs_strtomode(mode, &fmode);
+ xfree(mode);
+
+ time(&now);
+ now = cvs_hack_time(now, 0);
+ ctime_r(&now, timebuf);
+ if (timebuf[strlen(timebuf) - 1] == '\n')
+ timebuf[strlen(timebuf) - 1] = '\0';
+
+ e = cvs_ent_parse(entry);
+ xfree(entry);
+ rcsnum_tostr(e->ce_rev, revbuf, sizeof(revbuf));
+ entry = xmalloc(CVS_ENT_MAXLINELEN);
+ l = snprintf(entry, CVS_ENT_MAXLINELEN, "/%s/%s/%s//", e->ce_name,
+ revbuf, timebuf);
+ if (l == -1 || l >= CVS_ENT_MAXLINELEN)
+ fatal("cvs_client_updated: overflow");
+
+ cvs_ent_free(e);
+ ent = cvs_ent_open(wdir);
+ cvs_ent_add(ent, entry);
+ cvs_ent_close(ent, ENT_SYNC);
+ xfree(entry);
+
+ bp = cvs_remote_receive_file(flen);
+ if ((fd = open(fpath, O_CREAT | O_WRONLY | O_TRUNC)) == -1)
+ fatal("cvs_client_updated: open: %s: %s",
+ fpath, strerror(errno));
+
+ if (cvs_buf_write_fd(bp, fd) == -1)
+ fatal("cvs_client_updated: cvs_buf_write_fd failed for %s",
+ fpath);
+
+ cvs_buf_free(bp);
+
+ now = cvs_hack_time(now, 0);
+ tv[0].tv_sec = now;
+ tv[0].tv_usec = 0;
+ tv[1] = tv[0];
+
+ if (futimes(fd, tv) == -1)
+ fatal("cvs_client_updated: futimes: %s", strerror(errno));
+
+ if (fchmod(fd, fmode) == -1)
+ fatal("cvs_client_updated: fchmod: %s", strerror(errno));
+
+ (void)close(fd);
+
+ xfree(rpath);
+}
+
+void
+cvs_client_merged(char *data)
+{
+ cvs_log(LP_TRACE, "cvs_client_merged(%s)", data);
+}
+
+void
+cvs_client_removed(char *data)
+{
+ cvs_log(LP_TRACE, "cvs_client_removed(%s)", data);
+}
+
+void
+cvs_client_remove_entry(char *data)
+{
+ char *dir;
+
+ cvs_log(LP_TRACE, "cvs_client_remove_entry(%s)", data);
+
+ dir = cvs_remote_input();
+ xfree(dir);
+}