Annotation of src/usr.bin/cvs/req.c, Revision 1.22
1.22 ! joris 1: /* $OpenBSD: req.c,v 1.21 2005/06/10 21:14:47 joris Exp $ */
1.1 jfb 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:
28: #include <sys/types.h>
29: #include <sys/stat.h>
30:
1.20 xsa 31: #include <errno.h>
1.1 jfb 32: #include <fcntl.h>
1.22 ! joris 33: #include <libgen.h>
1.1 jfb 34: #include <stdio.h>
35: #include <stdlib.h>
1.20 xsa 36: #include <string.h>
1.1 jfb 37: #include <unistd.h>
38:
39: #include "buf.h"
40: #include "cvs.h"
41: #include "log.h"
42: #include "proto.h"
43:
44:
1.22 ! joris 45: extern char *cvs_rootstr;
1.1 jfb 46: extern int verbosity;
47: extern int cvs_compress;
48: extern char *cvs_rsh;
49: extern int cvs_trace;
50: extern int cvs_nolog;
51: extern int cvs_readonly;
52:
53:
1.11 jfb 54: static int cvs_req_set (int, char *);
1.12 jfb 55: static int cvs_req_noop (int, char *);
1.11 jfb 56: static int cvs_req_root (int, char *);
57: static int cvs_req_validreq (int, char *);
58: static int cvs_req_validresp (int, char *);
1.12 jfb 59: static int cvs_req_expandmod (int, char *);
1.11 jfb 60: static int cvs_req_directory (int, char *);
61: static int cvs_req_useunchanged (int, char *);
62: static int cvs_req_case (int, char *);
63: static int cvs_req_argument (int, char *);
64: static int cvs_req_globalopt (int, char *);
65: static int cvs_req_gzipstream (int, char *);
1.13 jfb 66: static int cvs_req_entry (int, char *);
67: static int cvs_req_filestate (int, char *);
1.11 jfb 68:
69: static int cvs_req_command (int, char *);
1.1 jfb 70:
71:
72: struct cvs_reqhdlr {
73: int (*hdlr)(int, char *);
74: } cvs_req_swtab[CVS_REQ_MAX + 1] = {
1.11 jfb 75: { NULL },
76: { cvs_req_root },
77: { cvs_req_validreq },
78: { cvs_req_validresp },
79: { cvs_req_directory },
80: { NULL },
81: { NULL },
82: { NULL },
1.13 jfb 83: { cvs_req_entry },
1.11 jfb 84: { NULL },
85: { NULL }, /* 10 */
1.13 jfb 86: { cvs_req_filestate },
87: { cvs_req_filestate },
88: { cvs_req_filestate },
1.11 jfb 89: { cvs_req_useunchanged },
90: { NULL },
91: { NULL },
1.13 jfb 92: { cvs_req_filestate },
1.11 jfb 93: { cvs_req_case },
94: { NULL },
95: { cvs_req_argument }, /* 20 */
96: { cvs_req_argument },
97: { cvs_req_globalopt },
98: { cvs_req_gzipstream },
99: { NULL },
100: { NULL },
101: { NULL },
102: { NULL },
103: { NULL },
104: { NULL },
105: { NULL }, /* 30 */
106: { NULL },
107: { NULL },
108: { NULL },
109: { cvs_req_set },
1.12 jfb 110: { cvs_req_expandmod },
1.11 jfb 111: { cvs_req_command },
112: { NULL },
113: { NULL },
114: { NULL },
115: { NULL }, /* 40 */
116: { NULL },
117: { NULL },
118: { NULL },
119: { NULL },
1.17 joris 120: { cvs_req_command },
1.11 jfb 121: { cvs_req_command },
122: { NULL },
123: { cvs_req_command },
1.22 ! joris 124: { cvs_req_command },
1.11 jfb 125: { NULL }, /* 50 */
126: { NULL },
127: { NULL },
128: { NULL },
129: { cvs_req_command },
130: { cvs_req_command },
131: { cvs_req_command },
132: { NULL },
133: { NULL },
134: { NULL },
135: { cvs_req_command }, /* 60 */
136: { NULL },
137: { cvs_req_command },
138: { cvs_req_command },
1.12 jfb 139: { cvs_req_noop },
1.11 jfb 140: { NULL },
141: { NULL },
142: { NULL },
143: { NULL },
144: { cvs_req_command },
1.1 jfb 145: };
146:
147:
148:
149: /*
1.2 jfb 150: * Argument array built by `Argument' and `Argumentx' requests.
151: */
1.22 ! joris 152: static char *cvs_req_args[CVS_PROTO_MAXARG];
! 153:
! 154: /* start at 1, because 0 will be the command name */
! 155: static int cvs_req_nargs = 1;
1.2 jfb 156:
1.22 ! joris 157: static char *cvs_req_modulename;
1.11 jfb 158: static char *cvs_req_rootpath;
1.14 joris 159: static char *cvs_req_currentdir;
160: extern char cvs_server_tmpdir[MAXPATHLEN];
1.22 ! joris 161: static CVSENTRIES *cvs_req_entf;
1.2 jfb 162:
163: /*
1.1 jfb 164: * cvs_req_handle()
165: *
166: * Generic request handler dispatcher. The handler expects the first line
167: * of the command as single argument.
168: * Returns the return value of the command on success, or -1 on failure.
169: */
170: int
171: cvs_req_handle(char *line)
172: {
173: char *cp, *cmd;
174: struct cvs_req *req;
175:
176: cmd = line;
177:
178: cp = strchr(cmd, ' ');
179: if (cp != NULL)
180: *(cp++) = '\0';
181:
182: req = cvs_req_getbyname(cmd);
183: if (req == NULL)
184: return (-1);
185: else if (cvs_req_swtab[req->req_id].hdlr == NULL) {
1.10 jfb 186: cvs_log(LP_ERR, "handler for `%s' not implemented", cmd);
1.1 jfb 187: return (-1);
188: }
189:
190: return (*cvs_req_swtab[req->req_id].hdlr)(req->req_id, cp);
191: }
192:
1.12 jfb 193: /*
194: * cvs_req_noop()
195: */
196: static int
197: cvs_req_noop(int reqid, char *line)
198: {
199: int ret;
200:
201: ret = cvs_sendresp(CVS_RESP_OK, NULL);
202: if (ret < 0)
203: return (-1);
204: return (0);
205: }
206:
1.1 jfb 207:
208: static int
209: cvs_req_root(int reqid, char *line)
210: {
1.11 jfb 211: if (cvs_req_rootpath != NULL) {
212: cvs_log(LP_ERR, "duplicate Root request received");
1.13 jfb 213: cvs_printf("Protocol error: Duplicate Root request");
1.11 jfb 214: return (-1);
215: }
1.9 jfb 216:
1.11 jfb 217: cvs_req_rootpath = strdup(line);
218: if (cvs_req_rootpath == NULL) {
219: cvs_log(LP_ERRNO, "failed to copy Root path");
1.9 jfb 220: return (-1);
221: }
222:
1.22 ! joris 223: cvs_rootstr = cvs_req_rootpath;
! 224:
1.4 jfb 225: return (0);
226: }
227:
228:
229: static int
230: cvs_req_validreq(int reqid, char *line)
231: {
232: char *vreq;
233:
234: vreq = cvs_req_getvalid();
235: if (vreq == NULL)
236: return (-1);
237:
1.11 jfb 238: if ((cvs_sendresp(CVS_RESP_VALIDREQ, vreq) < 0) ||
239: (cvs_sendresp(CVS_RESP_OK, NULL) < 0))
240: return (-1);
1.4 jfb 241:
242: return (0);
243: }
244:
245: static int
246: cvs_req_validresp(int reqid, char *line)
247: {
248: char *sp, *ep;
249: struct cvs_resp *resp;
1.1 jfb 250:
1.4 jfb 251: sp = line;
252: do {
253: ep = strchr(sp, ' ');
254: if (ep != NULL)
255: *(ep++) = '\0';
256:
257: resp = cvs_resp_getbyname(sp);
258: if (resp != NULL)
259: ;
260:
261: if (ep != NULL)
262: sp = ep + 1;
263: } while (ep != NULL);
1.1 jfb 264:
265: return (0);
266: }
267:
268: static int
269: cvs_req_directory(int reqid, char *line)
270: {
1.22 ! joris 271: int pwd;
! 272: size_t dirlen;
1.11 jfb 273: char rdir[MAXPATHLEN];
1.22 ! joris 274: char *repo, *s, *p;
! 275:
! 276: pwd = (!strcmp(line, "."));
1.1 jfb 277:
1.11 jfb 278: if (cvs_getln(NULL, rdir, sizeof(rdir)) < 0)
279: return (-1);
280:
1.14 joris 281: if (cvs_req_currentdir != NULL)
282: free(cvs_req_currentdir);
283:
284: cvs_req_currentdir = strdup(rdir);
285: if (cvs_req_currentdir == NULL) {
1.19 xsa 286: cvs_log(LP_ERR, "failed to duplicate directory");
1.14 joris 287: return (-1);
288: }
289:
1.22 ! joris 290: dirlen = strlen(cvs_req_currentdir);
1.14 joris 291:
1.22 ! joris 292: /*
! 293: * Lets make sure we always start at the correct
! 294: * directory.
! 295: */
! 296: if (chdir(cvs_server_tmpdir) == -1) {
! 297: cvs_log(LP_ERRNO, "failed to change to top directory");
1.14 joris 298: return (-1);
299: }
300:
1.22 ! joris 301: /*
! 302: * Set repository path.
! 303: */
! 304: s = cvs_req_currentdir + strlen(cvs_req_rootpath) + 1;
! 305: if (s >= (cvs_req_currentdir + dirlen)) {
! 306: cvs_log(LP_ERR, "you're bad, go away");
1.14 joris 307: return (-1);
308: }
309:
1.22 ! joris 310: if ((repo = strdup(s)) == NULL) {
! 311: cvs_log(LP_ERR, "failed to save repository path");
! 312: return (-1);
! 313: }
! 314:
! 315: /*
! 316: * Skip back "foo/bar" part, so we can feed the repo
! 317: * as a startpoint for cvs_create_dir().
! 318: */
! 319: if (!pwd) {
! 320: s = repo + strlen(repo) - strlen(line) - 1;
! 321: if (*s != '/') {
! 322: cvs_log(LP_ERR, "malformed directory");
! 323: free(repo);
! 324: return (-1);
! 325: }
! 326:
! 327: *s = '\0';
! 328: }
! 329:
! 330: /*
! 331: * Obtain the modulename, we only need to do this at
! 332: * the very first time we get a Directory request.
! 333: */
! 334: if (cvs_req_modulename == NULL) {
! 335: if ((p = strchr(repo, '/')) != NULL)
! 336: *p = '\0';
! 337:
! 338: if ((cvs_req_modulename = strdup(repo)) == NULL) {
! 339: cvs_log(LP_ERR, "failed to save modulename");
! 340: free(repo);
! 341: return (-1);
! 342: }
! 343:
! 344: if (p != NULL)
! 345: *p = '/';
! 346:
! 347: /*
! 348: * Now, create the admin files in the top-level
! 349: * directory for the temp repo.
! 350: */
! 351: if (cvs_mkadmin(cvs_server_tmpdir, cvs_rootstr, repo) < 0) {
! 352: cvs_log(LP_ERR, "failed to create admin files");
! 353: free(repo);
! 354: return (-1);
! 355: }
! 356: }
! 357:
! 358: /*
! 359: * create the directory plus the administrative files.
! 360: */
! 361: if (cvs_create_dir(line, 1, cvs_rootstr, repo) < 0) {
! 362: free(repo);
! 363: return (-1);
! 364: }
1.14 joris 365:
1.22 ! joris 366: /*
! 367: * cvs_create_dir() has already put us in the correct directory
! 368: * so now open it's Entry file for incoming files.
! 369: */
! 370: if (cvs_req_entf != NULL)
! 371: cvs_ent_close(cvs_req_entf);
! 372: cvs_req_entf = cvs_ent_open(".", O_RDWR);
! 373: if (cvs_req_entf == NULL) {
! 374: cvs_log(LP_ERR, "failed to open Entry file for %s", line);
! 375: free(repo);
! 376: return (-1);
! 377: }
! 378:
! 379: free(repo);
1.11 jfb 380: return (0);
381: }
382:
1.13 jfb 383: static int
384: cvs_req_entry(int reqid, char *line)
385: {
386: struct cvs_ent *ent;
387:
1.22 ! joris 388: /* parse received entry */
1.13 jfb 389: if ((ent = cvs_ent_parse(line)) == NULL)
390: return (-1);
391:
1.22 ! joris 392: /* add it to the entry file and done */
! 393: if (cvs_ent_add(cvs_req_entf, ent) < 0) {
! 394: cvs_log(LP_ERR, "failed to add '%s' to the Entry file",
! 395: ent->ce_name);
! 396: return (-1);
! 397: }
! 398:
! 399: /* XXX */
! 400: cvs_ent_write(cvs_req_entf);
1.15 jfb 401:
1.13 jfb 402: return (0);
403: }
404:
405: /*
406: * cvs_req_filestate()
407: *
408: * Handler for the `Modified', `Is-Modified', `Unchanged' and `Questionable'
409: * requests, which are all used to report the assumed state of a file from the
410: * client.
411: */
412: static int
413: cvs_req_filestate(int reqid, char *line)
414: {
1.22 ! joris 415: int ret;
1.13 jfb 416: mode_t fmode;
417: BUF *fdata;
1.22 ! joris 418: struct cvs_ent *ent;
1.13 jfb 419:
1.22 ! joris 420: ret = 0;
! 421: switch (reqid) {
! 422: case CVS_REQ_MODIFIED:
1.13 jfb 423: fdata = cvs_recvfile(NULL, &fmode);
424: if (fdata == NULL)
425: return (-1);
1.14 joris 426:
427: /* write the file */
1.22 ! joris 428: if (cvs_buf_write(fdata, line, fmode) < 0) {
! 429: cvs_log(LP_ERR, "failed to create file %s", line);
1.14 joris 430: cvs_buf_free(fdata);
431: return (-1);
432: }
433:
434: cvs_buf_free(fdata);
1.22 ! joris 435: break;
! 436: case CVS_REQ_ISMODIFIED:
! 437: break;
! 438: case CVS_REQ_UNCHANGED:
! 439: ent = cvs_ent_get(cvs_req_entf, line);
! 440: if (ent == NULL) {
! 441: cvs_log(LP_ERR,
! 442: "received Unchanged request for a non-existing file");
! 443: ret = -1;
! 444: } else {
! 445: ent->ce_status = CVS_ENT_UPTODATE;
! 446: }
! 447: break;
! 448: case CVS_REQ_QUESTIONABLE:
! 449: break;
! 450: default:
! 451: cvs_log(LP_ERR, "wrong request id type");
! 452: ret = -1;
! 453: break;
1.13 jfb 454: }
455:
1.22 ! joris 456: /* XXX */
! 457: cvs_req_entf->cef_flags &= ~CVS_ENTF_SYNC;
! 458: cvs_ent_write(cvs_req_entf);
! 459:
! 460: return (ret);
1.13 jfb 461: }
1.12 jfb 462:
463: /*
464: * cvs_req_expandmod()
465: *
466: */
467: static int
468: cvs_req_expandmod(int reqid, char *line)
469: {
470: int ret;
471:
472: ret = cvs_sendresp(CVS_RESP_OK, NULL);
473: if (ret < 0)
474: return (-1);
475: return (0);
476: }
477:
478:
1.11 jfb 479: /*
480: * cvs_req_useunchanged()
481: *
482: * Handler for the `UseUnchanged' requests. The protocol documentation
483: * specifies that this request must be supported by the server and must be
484: * sent by the client, though it gives no clue regarding its use.
485: */
486: static int
487: cvs_req_useunchanged(int reqid, char *line)
488: {
1.5 jfb 489: return (0);
490: }
491:
1.11 jfb 492:
1.5 jfb 493: /*
494: * cvs_req_case()
495: *
496: * Handler for the `Case' requests, which toggles case sensitivity ON or OFF
497: */
498: static int
499: cvs_req_case(int reqid, char *line)
500: {
501: cvs_nocase = 1;
1.2 jfb 502: return (0);
503: }
504:
505:
506: static int
1.11 jfb 507: cvs_req_set(int reqid, char *line)
508: {
509: char *cp, *lp;
510:
511: if ((lp = strdup(line)) == NULL) {
512: cvs_log(LP_ERRNO, "failed to copy Set argument");
513: return (-1);
514: }
515:
516: if ((cp = strchr(lp, '=')) == NULL) {
517: cvs_log(LP_ERR, "error in Set request "
518: "(no = in variable assignment)");
519: free(lp);
520: return (-1);
521: }
522: *(cp++) = '\0';
523:
524: if (cvs_var_set(lp, cp) < 0) {
525: free(lp);
526: return (-1);
527: }
528:
529: free(lp);
530:
531: return (0);
532: }
533:
534:
535: static int
1.2 jfb 536: cvs_req_argument(int reqid, char *line)
537: {
538: char *nap;
539:
540: if (cvs_req_nargs == CVS_PROTO_MAXARG) {
541: cvs_log(LP_ERR, "too many arguments");
542: return (-1);
543: }
544:
545: if (reqid == CVS_REQ_ARGUMENT) {
546: cvs_req_args[cvs_req_nargs] = strdup(line);
547: if (cvs_req_args[cvs_req_nargs] == NULL) {
548: cvs_log(LP_ERRNO, "failed to copy argument");
549: return (-1);
550: }
551: cvs_req_nargs++;
1.6 deraadt 552: } else if (reqid == CVS_REQ_ARGUMENTX) {
1.2 jfb 553: if (cvs_req_nargs == 0)
554: cvs_log(LP_WARN, "no argument to append to");
555: else {
556: asprintf(&nap, "%s%s", cvs_req_args[cvs_req_nargs - 1],
557: line);
558: if (nap == NULL) {
559: cvs_log(LP_ERRNO,
560: "failed to append to argument");
561: return (-1);
562: }
563: free(cvs_req_args[cvs_req_nargs - 1]);
564: cvs_req_args[cvs_req_nargs - 1] = nap;
565: }
1.3 jfb 566: }
567:
568: return (0);
569: }
570:
571:
572: static int
573: cvs_req_globalopt(int reqid, char *line)
574: {
575: if ((*line != '-') || (*(line + 2) != '\0')) {
576: cvs_log(LP_ERR,
577: "invalid `Global_option' request format");
578: return (-1);
579: }
580:
581: switch (*(line + 1)) {
582: case 'l':
583: cvs_nolog = 1;
584: break;
585: case 'n':
586: break;
587: case 'Q':
588: verbosity = 0;
589: break;
590: case 'q':
591: if (verbosity > 1)
592: verbosity = 1;
593: break;
594: case 'r':
595: cvs_readonly = 1;
596: break;
597: case 't':
598: cvs_trace = 1;
599: break;
600: default:
601: cvs_log(LP_ERR, "unknown global option `%s'", line);
602: return (-1);
1.2 jfb 603: }
1.8 jfb 604:
605: return (0);
606: }
607:
608:
609: /*
610: * cvs_req_gzipstream()
611: *
612: * Handler for the `Gzip-stream' request, which enables compression at the
613: * level given along with the request. After this request has been processed,
614: * all further connection data should be compressed.
615: */
616: static int
617: cvs_req_gzipstream(int reqid, char *line)
618: {
619: char *ep;
620: long val;
621:
622: val = strtol(line, &ep, 10);
623: if ((line[0] == '\0') || (*ep != '\0')) {
624: cvs_log(LP_ERR, "invalid Gzip-stream level `%s'", line);
625: return (-1);
626: } else if ((errno == ERANGE) && ((val < 0) || (val > 9))) {
627: cvs_log(LP_ERR, "Gzip-stream level %ld out of range", val);
628: return (-1);
629: }
630:
631: cvs_compress = (int)val;
1.1 jfb 632:
633: return (0);
634: }
635:
636:
1.11 jfb 637: /*
638: * cvs_req_command()
639: *
640: * Generic request handler for CVS command requests (i.e. diff, update, tag).
641: */
1.1 jfb 642: static int
1.11 jfb 643: cvs_req_command(int reqid, char *line)
1.1 jfb 644: {
1.16 jfb 645: int ret = 0;
1.15 jfb 646: struct cvs_cmd *cmdp;
1.11 jfb 647:
1.15 jfb 648: cmdp = cvs_findcmdbyreq(reqid);
649: if (cmdp == NULL) {
1.11 jfb 650: cvs_sendresp(CVS_RESP_ERROR, NULL);
1.15 jfb 651: return (-1);
1.11 jfb 652: }
1.15 jfb 653:
1.22 ! joris 654: /* close the Entry file if it's still open */
! 655: if (cvs_req_entf != NULL)
! 656: cvs_ent_close(cvs_req_entf);
! 657:
! 658: /* fill in the command name */
! 659: cvs_req_args[0] = cmdp->cmd_name;
! 660:
! 661: /* switch to the correct directory */
! 662: if (cmdp->cmd_op != CVS_OP_VERSION) {
! 663: if (chdir(cvs_server_tmpdir) == -1) {
! 664: cvs_log(LP_ERRNO, "failed to change dir");
! 665: return (-1);
! 666: }
! 667: }
! 668:
1.16 jfb 669: ret = cvs_startcmd(cmdp, cvs_req_nargs, cvs_req_args);
1.22 ! joris 670:
1.11 jfb 671: if (ret == 0)
672: ret = cvs_sendresp(CVS_RESP_OK, NULL);
673:
674: return (ret);
1.1 jfb 675: }