Annotation of src/usr.bin/cvs/client.c, Revision 1.1
1.1 ! jfb 1: /* $OpenBSD$ */
! 2: /*
! 3: * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
! 4: * All rights reserved.
! 5: *
! 6: * Redistribution and use in source and binary forms, with or without
! 7: * modification, are permitted provided that the following conditions
! 8: * are met:
! 9: *
! 10: * 1. Redistributions of source code must retain the above copyright
! 11: * notice, this list of conditions and the following disclaimer.
! 12: * 2. The name of the author may not be used to endorse or promote products
! 13: * derived from this software without specific prior written permission.
! 14: *
! 15: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
! 16: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
! 17: * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
! 18: * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
! 19: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
! 20: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
! 21: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
! 22: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
! 23: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
! 24: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
! 25: */
! 26:
! 27: #include <sys/types.h>
! 28: #include <sys/stat.h>
! 29:
! 30: #include <fcntl.h>
! 31: #include <stdio.h>
! 32: #include <errno.h>
! 33: #include <stdlib.h>
! 34: #include <unistd.h>
! 35: #include <signal.h>
! 36: #include <string.h>
! 37: #include <sysexits.h>
! 38: #ifdef CVS_ZLIB
! 39: #include <zlib.h>
! 40: #endif
! 41:
! 42: #include "cvs.h"
! 43: #include "log.h"
! 44:
! 45:
! 46:
! 47: extern int verbosity;
! 48: extern int cvs_compress;
! 49: extern char *cvs_rsh;
! 50: extern int cvs_trace;
! 51: extern int cvs_nolog;
! 52: extern int cvs_readonly;
! 53:
! 54: extern struct cvsroot *cvs_root;
! 55:
! 56:
! 57:
! 58:
! 59: static int cvs_client_sendinfo (void);
! 60: static int cvs_client_initlog (void);
! 61:
! 62:
! 63:
! 64: static int cvs_server_infd = -1;
! 65: static int cvs_server_outfd = -1;
! 66: static FILE *cvs_server_in;
! 67: static FILE *cvs_server_out;
! 68:
! 69: /* protocol log files, if the CVS_CLIENT_LOG environment variable is used */
! 70: static FILE *cvs_server_inlog;
! 71: static FILE *cvs_server_outlog;
! 72:
! 73: static char cvs_client_buf[4096];
! 74:
! 75: /* last directory sent with `Directory' */
! 76: static char cvs_lastdir[MAXPATHLEN] = "";
! 77:
! 78:
! 79:
! 80: /*
! 81: * cvs_client_connect()
! 82: *
! 83: * Open a client connection to the cvs server whose address is given in
! 84: * the global <cvs_root> variable. The method used to connect depends on the
! 85: * setting of the CVS_RSH variable.
! 86: */
! 87:
! 88: int
! 89: cvs_client_connect(void)
! 90: {
! 91: int argc, infd[2], outfd[2];
! 92: pid_t pid;
! 93: char *argv[16], *cvs_server_cmd;
! 94:
! 95: if (pipe(infd) == -1) {
! 96: cvs_log(LP_ERRNO,
! 97: "failed to create input pipe for client connection");
! 98: return (-1);
! 99: }
! 100:
! 101: if (pipe(outfd) == -1) {
! 102: cvs_log(LP_ERRNO,
! 103: "failed to create output pipe for client connection");
! 104: (void)close(infd[0]);
! 105: (void)close(infd[1]);
! 106: return (-1);
! 107: }
! 108:
! 109: pid = fork();
! 110: if (pid == -1) {
! 111: cvs_log(LP_ERRNO, "failed to fork for cvs server connection");
! 112: return (-1);
! 113: }
! 114: if (pid == 0) {
! 115: if ((dup2(infd[0], STDIN_FILENO) == -1) ||
! 116: (dup2(outfd[1], STDOUT_FILENO) == -1)) {
! 117: cvs_log(LP_ERRNO,
! 118: "failed to setup standard streams for cvs server");
! 119: return (-1);
! 120: }
! 121: (void)close(infd[1]);
! 122: (void)close(outfd[0]);
! 123:
! 124: argc = 0;
! 125: argv[argc++] = cvs_rsh;
! 126:
! 127: if (cvs_root->cr_user != NULL) {
! 128: argv[argc++] = "-l";
! 129: argv[argc++] = cvs_root->cr_user;
! 130: }
! 131:
! 132:
! 133: cvs_server_cmd = getenv("CVS_SERVER");
! 134: if (cvs_server_cmd == NULL)
! 135: cvs_server_cmd = "cvs";
! 136:
! 137: argv[argc++] = cvs_root->cr_host;
! 138: argv[argc++] = cvs_server_cmd;
! 139: argv[argc++] = "server";
! 140: argv[argc] = NULL;
! 141:
! 142: execvp(argv[0], argv);
! 143: cvs_log(LP_ERRNO, "failed to exec");
! 144: exit(EX_OSERR);
! 145: }
! 146:
! 147: /* we are the parent */
! 148: cvs_server_infd = infd[1];
! 149: cvs_server_outfd = outfd[0];
! 150:
! 151: cvs_server_in = fdopen(cvs_server_infd, "w");
! 152: if (cvs_server_in == NULL) {
! 153: cvs_log(LP_ERRNO, "failed to create pipe stream");
! 154: return (-1);
! 155: }
! 156:
! 157: cvs_server_out = fdopen(cvs_server_outfd, "r");
! 158: if (cvs_server_out == NULL) {
! 159: cvs_log(LP_ERRNO, "failed to create pipe stream");
! 160: return (-1);
! 161: }
! 162:
! 163: /* make the streams line-buffered */
! 164: setvbuf(cvs_server_in, NULL, _IOLBF, 0);
! 165: setvbuf(cvs_server_out, NULL, _IOLBF, 0);
! 166:
! 167: (void)close(infd[0]);
! 168: (void)close(outfd[1]);
! 169:
! 170: cvs_client_initlog();
! 171:
! 172: cvs_client_sendinfo();
! 173:
! 174: #ifdef CVS_ZLIB
! 175: /* if compression was requested, initialize it */
! 176: #endif
! 177:
! 178: return (0);
! 179: }
! 180:
! 181:
! 182: /*
! 183: * cvs_client_disconnect()
! 184: *
! 185: * Disconnect from the cvs server.
! 186: */
! 187:
! 188: void
! 189: cvs_client_disconnect(void)
! 190: {
! 191: cvs_log(LP_DEBUG, "closing client connection");
! 192: (void)fclose(cvs_server_in);
! 193: (void)fclose(cvs_server_out);
! 194: cvs_server_in = NULL;
! 195: cvs_server_out = NULL;
! 196: cvs_server_infd = -1;
! 197: cvs_server_outfd = -1;
! 198:
! 199: if (cvs_server_inlog != NULL)
! 200: fclose(cvs_server_inlog);
! 201: if (cvs_server_outlog != NULL)
! 202: fclose(cvs_server_outlog);
! 203: }
! 204:
! 205:
! 206: /*
! 207: * cvs_client_sendreq()
! 208: *
! 209: * Send a request to the server of type <rid>, with optional arguments
! 210: * contained in <arg>, which should not be terminated by a newline.
! 211: * The <resp> argument is 0 if no response is expected, or any other value if
! 212: * a response is expected.
! 213: * Returns 0 on success, or -1 on failure.
! 214: */
! 215:
! 216: int
! 217: cvs_client_sendreq(u_int rid, const char *arg, int resp)
! 218: {
! 219: int ret;
! 220: size_t len;
! 221: char *rbp;
! 222: const char *reqp;
! 223:
! 224: if (cvs_server_in == NULL) {
! 225: cvs_log(LP_ERR, "cannot send request: Not connected");
! 226: return (-1);
! 227: }
! 228:
! 229: reqp = cvs_req_getbyid(rid);
! 230: if (reqp == NULL) {
! 231: cvs_log(LP_ERR, "unsupported request type %u", rid);
! 232: return (-1);
! 233: }
! 234:
! 235: snprintf(cvs_client_buf, sizeof(cvs_client_buf), "%s %s\n", reqp,
! 236: (arg == NULL) ? "" : arg);
! 237:
! 238: rbp = cvs_client_buf;
! 239:
! 240: if (cvs_server_inlog != NULL)
! 241: fputs(cvs_client_buf, cvs_server_inlog);
! 242:
! 243: ret = fputs(cvs_client_buf, cvs_server_in);
! 244: if (ret == EOF) {
! 245: cvs_log(LP_ERRNO, "failed to send request to server");
! 246: return (-1);
! 247: }
! 248:
! 249: if (resp) {
! 250: do {
! 251: /* wait for incoming data */
! 252: if (fgets(cvs_client_buf, sizeof(cvs_client_buf),
! 253: cvs_server_out) == NULL) {
! 254: if (feof(cvs_server_out))
! 255: return (0);
! 256: cvs_log(LP_ERRNO,
! 257: "failed to read response from server");
! 258: return (-1);
! 259: }
! 260:
! 261: if (cvs_server_outlog != NULL)
! 262: fputs(cvs_client_buf, cvs_server_outlog);
! 263:
! 264: if ((len = strlen(cvs_client_buf)) != 0) {
! 265: if (cvs_client_buf[len - 1] != '\n') {
! 266: /* truncated line */
! 267: }
! 268: else
! 269: cvs_client_buf[--len] = '\0';
! 270: }
! 271:
! 272: ret = cvs_resp_handle(cvs_client_buf);
! 273: } while (ret == 0);
! 274: }
! 275:
! 276: return (0);
! 277: }
! 278:
! 279:
! 280: /*
! 281: * cvs_client_sendln()
! 282: *
! 283: * Send a single line <line> string to the server. The line is sent as is,
! 284: * without any modifications.
! 285: * Returns 0 on success, or -1 on failure.
! 286: */
! 287:
! 288: int
! 289: cvs_client_sendln(const char *line)
! 290: {
! 291: int nl;
! 292: size_t len;
! 293:
! 294: nl = 0;
! 295: len = strlen(line);
! 296:
! 297: if ((len > 0) && (line[len - 1] != '\n'))
! 298: nl = 1;
! 299:
! 300: if (cvs_server_inlog != NULL) {
! 301: fputs(line, cvs_server_inlog);
! 302: if (nl)
! 303: fputc('\n', cvs_server_inlog);
! 304: }
! 305: fputs(line, cvs_server_in);
! 306: if (nl)
! 307: fputc('\n', cvs_server_in);
! 308:
! 309: return (0);
! 310: }
! 311:
! 312:
! 313: /*
! 314: * cvs_client_sendraw()
! 315: *
! 316: * Send the first <len> bytes from the buffer <src> to the server.
! 317: */
! 318:
! 319: int
! 320: cvs_client_sendraw(const void *src, size_t len)
! 321: {
! 322: if (cvs_server_inlog != NULL)
! 323: fwrite(src, sizeof(char), len, cvs_server_inlog);
! 324: if (fwrite(src, sizeof(char), len, cvs_server_in) < len) {
! 325: return (-1);
! 326: }
! 327:
! 328: return (0);
! 329: }
! 330:
! 331:
! 332: /*
! 333: * cvs_client_recvraw()
! 334: *
! 335: * Receive the first <len> bytes from the buffer <src> to the server.
! 336: */
! 337:
! 338: ssize_t
! 339: cvs_client_recvraw(void *dst, size_t len)
! 340: {
! 341: size_t ret;
! 342:
! 343: ret = fread(dst, sizeof(char), len, cvs_server_out);
! 344: if (ret == 0)
! 345: return (-1);
! 346: if (cvs_server_outlog != NULL)
! 347: fwrite(dst, sizeof(char), len, cvs_server_outlog);
! 348: return (ssize_t)ret;
! 349: }
! 350:
! 351:
! 352: /*
! 353: * cvs_client_getln()
! 354: *
! 355: * Get a line from the server's output and store it in <lbuf>. The terminating
! 356: * newline character is stripped from the result.
! 357: */
! 358:
! 359: int
! 360: cvs_client_getln(char *lbuf, size_t len)
! 361: {
! 362: size_t rlen;
! 363:
! 364: if (fgets(lbuf, len, cvs_server_out) == NULL) {
! 365: if (ferror(cvs_server_out)) {
! 366: cvs_log(LP_ERRNO, "failed to read line from server");
! 367: return (-1);
! 368: }
! 369:
! 370: if (feof(cvs_server_out))
! 371: *lbuf = '\0';
! 372: }
! 373:
! 374: if (cvs_server_outlog != NULL)
! 375: fputs(lbuf, cvs_server_outlog);
! 376:
! 377: rlen = strlen(lbuf);
! 378: if ((rlen > 0) && (lbuf[rlen - 1] == '\n'))
! 379: lbuf[--rlen] = '\0';
! 380:
! 381: return (0);
! 382: }
! 383:
! 384:
! 385: /*
! 386: * cvs_client_sendinfo()
! 387: *
! 388: * Initialize the connection status by first requesting the list of
! 389: * supported requests from the server. Then, we send the CVSROOT variable
! 390: * with the `Root' request.
! 391: * Returns 0 on success, or -1 on failure.
! 392: */
! 393:
! 394: static int
! 395: cvs_client_sendinfo(void)
! 396: {
! 397: char *vresp;
! 398: /*
! 399: * First, send the server the list of valid responses, then ask
! 400: * for valid requests
! 401: */
! 402:
! 403: vresp = cvs_resp_getvalid();
! 404: if (vresp == NULL) {
! 405: cvs_log(LP_ERR, "can't generate list of valid responses");
! 406: return (-1);
! 407: }
! 408:
! 409: if (cvs_client_sendreq(CVS_REQ_VALIDRESP, vresp, 0) < 0) {
! 410: }
! 411: free(vresp);
! 412:
! 413: if (cvs_client_sendreq(CVS_REQ_VALIDREQ, NULL, 1) < 0) {
! 414: cvs_log(LP_ERR, "failed to get valid requests from server");
! 415: return (-1);
! 416: }
! 417:
! 418: /* now share our global options with the server */
! 419: if (verbosity == 1)
! 420: cvs_client_sendreq(CVS_REQ_GLOBALOPT, "-q", 0);
! 421: else if (verbosity == 0)
! 422: cvs_client_sendreq(CVS_REQ_GLOBALOPT, "-Q", 0);
! 423:
! 424: if (cvs_nolog)
! 425: cvs_client_sendreq(CVS_REQ_GLOBALOPT, "-l", 0);
! 426: if (cvs_readonly)
! 427: cvs_client_sendreq(CVS_REQ_GLOBALOPT, "-r", 0);
! 428: if (cvs_trace)
! 429: cvs_client_sendreq(CVS_REQ_GLOBALOPT, "-t", 0);
! 430:
! 431: /* now send the CVSROOT to the server */
! 432: if (cvs_client_sendreq(CVS_REQ_ROOT, cvs_root->cr_dir, 0) < 0)
! 433: return (-1);
! 434:
! 435: /* not sure why, but we have to send this */
! 436: if (cvs_client_sendreq(CVS_REQ_USEUNCHANGED, NULL, 0) < 0)
! 437: return (-1);
! 438:
! 439: return (0);
! 440: }
! 441:
! 442:
! 443: /*
! 444: * cvs_client_senddir()
! 445: *
! 446: * Send a `Directory' request along with the 2 paths that follow it.
! 447: */
! 448:
! 449: int
! 450: cvs_client_senddir(const char *dir)
! 451: {
! 452: char repo[MAXPATHLEN], buf[MAXPATHLEN];
! 453:
! 454: /* don't bother sending if it's the same as the last Directory sent */
! 455: if (strcmp(dir, cvs_lastdir) == 0)
! 456: return (0);
! 457:
! 458: if (cvs_readrepo(dir, repo, sizeof(repo)) < 0)
! 459: return (-1);
! 460:
! 461: snprintf(buf, sizeof(buf), "%s/%s", cvs_root->cr_dir, repo);
! 462:
! 463: if ((cvs_client_sendreq(CVS_REQ_DIRECTORY, dir, 0) < 0) ||
! 464: (cvs_client_sendln(buf) < 0))
! 465: return (-1);
! 466: strlcpy(cvs_lastdir, dir, sizeof(cvs_lastdir));
! 467:
! 468: return (0);
! 469: }
! 470:
! 471:
! 472: /*
! 473: * cvs_client_sendarg()
! 474: *
! 475: * Send the argument <arg> to the server. The argument <append> is used to
! 476: * determine if the argument should be simply appended to the last argument
! 477: * sent or if it should be created as a new argument (0).
! 478: */
! 479:
! 480: int
! 481: cvs_client_sendarg(const char *arg, int append)
! 482: {
! 483: return cvs_client_sendreq(((append == 0) ?
! 484: CVS_REQ_ARGUMENT : CVS_REQ_ARGUMENTX), arg, 0);
! 485: }
! 486:
! 487:
! 488: /*
! 489: * cvs_client_sendentry()
! 490: *
! 491: * Send an `Entry' request to the server along with the mandatory fields from
! 492: * the CVS entry <ent> (which are the name and revision).
! 493: */
! 494:
! 495: int
! 496: cvs_client_sendentry(const struct cvs_ent *ent)
! 497: {
! 498: char ebuf[128], numbuf[64];
! 499:
! 500: snprintf(ebuf, sizeof(ebuf), "/%s/%s///", ent->ce_name,
! 501: rcsnum_tostr(ent->ce_rev, numbuf, sizeof(numbuf)));
! 502:
! 503: return cvs_client_sendreq(CVS_REQ_ENTRY, ebuf, 0);
! 504: }
! 505:
! 506:
! 507: /*
! 508: * cvs_client_initlog()
! 509: *
! 510: * Initialize protocol logging if the CVS_CLIENT_LOG environment variable is
! 511: * set. In this case, the variable's value is used as a path to which the
! 512: * appropriate suffix is added (".in" for server input and ".out" for server
! 513: * output.
! 514: * Returns 0 on success, or -1 on failure.
! 515: */
! 516:
! 517: static int
! 518: cvs_client_initlog(void)
! 519: {
! 520: char *env, fpath[MAXPATHLEN];
! 521:
! 522: env = getenv("CVS_CLIENT_LOG");
! 523: if (env == NULL)
! 524: return (0);
! 525:
! 526: strlcpy(fpath, env, sizeof(fpath));
! 527: strlcat(fpath, ".in", sizeof(fpath));
! 528: cvs_server_inlog = fopen(fpath, "w");
! 529: if (cvs_server_inlog == NULL) {
! 530: cvs_log(LP_ERRNO, "failed to open server input log `%s'",
! 531: fpath);
! 532: return (-1);
! 533: }
! 534:
! 535: strlcpy(fpath, env, sizeof(fpath));
! 536: strlcat(fpath, ".out", sizeof(fpath));
! 537: cvs_server_outlog = fopen(fpath, "w");
! 538: if (cvs_server_outlog == NULL) {
! 539: cvs_log(LP_ERRNO, "failed to open server output log `%s'",
! 540: fpath);
! 541: return (-1);
! 542: }
! 543:
! 544: /* make the streams line-buffered */
! 545: setvbuf(cvs_server_inlog, NULL, _IOLBF, 0);
! 546: setvbuf(cvs_server_outlog, NULL, _IOLBF, 0);
! 547:
! 548: return (0);
! 549: }
! 550:
! 551:
! 552: /*
! 553: * cvs_client_sendfile()
! 554: *
! 555: */
! 556:
! 557: int
! 558: cvs_client_sendfile(const char *path)
! 559: {
! 560: int fd;
! 561: ssize_t ret;
! 562: char buf[4096];
! 563: struct stat st;
! 564:
! 565: if (stat(path, &st) == -1) {
! 566: cvs_log(LP_ERRNO, "failed to stat `%s'", path);
! 567: return (-1);
! 568: }
! 569:
! 570: fd = open(path, O_RDONLY, 0);
! 571: if (fd == -1) {
! 572: return (-1);
! 573: }
! 574:
! 575: if (cvs_modetostr(st.st_mode, buf, sizeof(buf)) < 0)
! 576: return (-1);
! 577:
! 578: cvs_client_sendln(buf);
! 579: snprintf(buf, sizeof(buf), "%lld\n", st.st_size);
! 580: cvs_client_sendln(buf);
! 581:
! 582: while ((ret = read(fd, buf, sizeof(buf))) != 0) {
! 583: if (ret == -1) {
! 584: cvs_log(LP_ERRNO, "failed to read file `%s'", path);
! 585: return (-1);
! 586: }
! 587:
! 588: cvs_client_sendraw(buf, (size_t)ret);
! 589:
! 590: }
! 591:
! 592: (void)close(fd);
! 593:
! 594: return (0);
! 595: }