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