Annotation of src/usr.bin/cvs/util.c, Revision 1.20
1.20 ! david 1: /* $OpenBSD: util.c,v 1.19 2004/12/13 16:59:50 jfb Exp $ */
1.1 jfb 2: /*
3: * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
1.9 jfb 4: * All rights reserved.
1.1 jfb 5: *
1.9 jfb 6: * Redistribution and use in source and binary forms, with or without
7: * modification, are permitted provided that the following conditions
8: * are met:
1.1 jfb 9: *
1.9 jfb 10: * 1. Redistributions of source code must retain the above copyright
11: * notice, this list of conditions and the following disclaimer.
1.1 jfb 12: * 2. The name of the author may not be used to endorse or promote products
1.9 jfb 13: * derived from this software without specific prior written permission.
1.1 jfb 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
1.9 jfb 24: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1.1 jfb 25: */
26:
27: #include <sys/types.h>
28: #include <sys/stat.h>
1.12 krapht 29: #include <sys/wait.h>
1.1 jfb 30:
31: #include <md5.h>
32: #include <errno.h>
1.2 jfb 33: #include <fcntl.h>
1.1 jfb 34: #include <stdio.h>
35: #include <stdlib.h>
36: #include <unistd.h>
37: #include <string.h>
38:
39: #include "cvs.h"
40: #include "log.h"
1.5 jfb 41: #include "file.h"
1.1 jfb 42:
1.9 jfb 43: static const char *cvs_months[] = {
44: "Jan", "Feb", "Mar", "Apr", "May", "Jun",
45: "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
46: };
47:
1.1 jfb 48:
49: /* letter -> mode type map */
50: static const int cvs_modetypes[26] = {
51: -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, -1,
52: -1, 2, -1, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1,
53: };
54:
55: /* letter -> mode map */
56: static const mode_t cvs_modes[3][26] = {
57: {
58: 0, 0, 0, 0, 0, 0, 0, /* a - g */
59: 0, 0, 0, 0, 0, 0, 0, /* h - m */
60: 0, 0, 0, S_IRUSR, 0, 0, 0, /* n - u */
61: 0, S_IWUSR, S_IXUSR, 0, 0 /* v - z */
62: },
63: {
64: 0, 0, 0, 0, 0, 0, 0, /* a - g */
65: 0, 0, 0, 0, 0, 0, 0, /* h - m */
66: 0, 0, 0, S_IRGRP, 0, 0, 0, /* n - u */
67: 0, S_IWGRP, S_IXGRP, 0, 0 /* v - z */
68: },
69: {
70: 0, 0, 0, 0, 0, 0, 0, /* a - g */
71: 0, 0, 0, 0, 0, 0, 0, /* h - m */
72: 0, 0, 0, S_IROTH, 0, 0, 0, /* n - u */
73: 0, S_IWOTH, S_IXOTH, 0, 0 /* v - z */
74: }
75: };
76:
77:
78: /* octal -> string */
79: static const char *cvs_modestr[8] = {
80: "", "x", "w", "wx", "r", "rx", "rw", "rwx"
81: };
82:
83:
1.11 krapht 84: pid_t cvs_exec_pid;
85:
1.1 jfb 86:
87: /*
88: * cvs_readrepo()
89: *
90: * Read the path stored in the `Repository' CVS file for a given directory
91: * <dir>, and store that path into the buffer pointed to by <dst>, whose size
92: * is <len>.
93: */
94: int
95: cvs_readrepo(const char *dir, char *dst, size_t len)
96: {
97: size_t dlen;
98: FILE *fp;
99: char repo_path[MAXPATHLEN];
100:
101: snprintf(repo_path, sizeof(repo_path), "%s/CVS/Repository", dir);
102: fp = fopen(repo_path, "r");
103: if (fp == NULL) {
104: return (-1);
105: }
106:
107: if (fgets(dst, (int)len, fp) == NULL) {
108: if (ferror(fp)) {
109: cvs_log(LP_ERRNO, "failed to read from `%s'",
110: repo_path);
111: }
112: (void)fclose(fp);
113: return (-1);
114: }
115: dlen = strlen(dst);
116: if ((dlen > 0) && (dst[dlen - 1] == '\n'))
117: dst[--dlen] = '\0';
118:
119: (void)fclose(fp);
120: return (0);
121: }
122:
123:
124: /*
1.9 jfb 125: * cvs_datesec()
126: *
1.10 jfb 127: * Take a date string and transform it into the number of seconds since the
128: * Epoch. The <type> argument specifies whether the timestamp is in ctime(3)
129: * format or RFC 822 format (as CVS uses in its protocol). If the <adj>
130: * parameter is not 0, the returned time will be adjusted according to the
131: * machine's local timezone.
1.9 jfb 132: */
133: time_t
1.10 jfb 134: cvs_datesec(const char *date, int type, int adj)
1.9 jfb 135: {
136: int i;
137: long off;
138: char sign, mon[8], gmt[8], hr[4], min[4], *ep;
139: struct tm cvs_tm;
140:
141: memset(&cvs_tm, 0, sizeof(cvs_tm));
142: cvs_tm.tm_isdst = -1;
143:
1.10 jfb 144: if (type == CVS_DATE_RFC822) {
145: if (sscanf(date, "%d %3s %d %2d:%2d:%2d %5s", &cvs_tm.tm_mday,
146: mon, &cvs_tm.tm_year, &cvs_tm.tm_hour, &cvs_tm.tm_min,
147: &cvs_tm.tm_sec, gmt) < 7)
148: return (-1);
149: cvs_tm.tm_year -= 1900;
1.9 jfb 150:
1.10 jfb 151: if (*gmt == '-') {
152: sscanf(gmt, "%c%2s%2s", &sign, hr, min);
153: cvs_tm.tm_gmtoff = strtol(hr, &ep, 10);
1.9 jfb 154: if ((cvs_tm.tm_gmtoff == LONG_MIN) ||
155: (cvs_tm.tm_gmtoff == LONG_MAX) ||
156: (*ep != '\0')) {
157: cvs_log(LP_ERR,
1.10 jfb 158: "parse error in GMT hours specification `%s'", hr);
159: cvs_tm.tm_gmtoff = 0;
1.15 deraadt 160: } else {
1.10 jfb 161: /* get seconds */
162: cvs_tm.tm_gmtoff *= 3600;
163:
164: /* add the minutes */
165: off = strtol(min, &ep, 10);
166: if ((cvs_tm.tm_gmtoff == LONG_MIN) ||
167: (cvs_tm.tm_gmtoff == LONG_MAX) ||
168: (*ep != '\0')) {
169: cvs_log(LP_ERR,
170: "parse error in GMT minutes "
171: "specification `%s'", min);
1.15 deraadt 172: } else
1.10 jfb 173: cvs_tm.tm_gmtoff += off * 60;
1.9 jfb 174: }
175: }
1.10 jfb 176: if (sign == '-')
177: cvs_tm.tm_gmtoff = -cvs_tm.tm_gmtoff;
1.15 deraadt 178: } else if (type == CVS_DATE_CTIME) {
1.10 jfb 179: /* gmt is used for the weekday */
180: sscanf(date, "%3s %3s %d %2d:%2d:%2d %d", gmt, mon,
181: &cvs_tm.tm_mday, &cvs_tm.tm_hour, &cvs_tm.tm_min,
182: &cvs_tm.tm_sec, &cvs_tm.tm_year);
183: cvs_tm.tm_year -= 1900;
1.18 tedu 184: cvs_tm.tm_gmtoff = 0;
1.9 jfb 185: }
186:
187: for (i = 0; i < (int)(sizeof(cvs_months)/sizeof(cvs_months[0])); i++) {
188: if (strcmp(cvs_months[i], mon) == 0) {
189: cvs_tm.tm_mon = i;
190: break;
191: }
192: }
193:
194: return mktime(&cvs_tm);
195: }
196:
197:
198: /*
1.1 jfb 199: * cvs_strtomode()
200: *
201: * Read the contents of the string <str> and generate a permission mode from
202: * the contents of <str>, which is assumed to have the mode format of CVS.
203: * The CVS protocol specification states that any modes or mode types that are
204: * not recognized should be silently ignored. This function does not return
205: * an error in such cases, but will issue warnings.
206: * Returns 0 on success, or -1 on failure.
207: */
208: int
209: cvs_strtomode(const char *str, mode_t *mode)
210: {
1.2 jfb 211: char type;
1.1 jfb 212: mode_t m;
213: char buf[32], ms[4], *sp, *ep;
214:
215: m = 0;
216: strlcpy(buf, str, sizeof(buf));
217: sp = buf;
218: ep = sp;
219:
220: for (sp = buf; ep != NULL; sp = ep + 1) {
221: ep = strchr(sp, ',');
222: if (ep != NULL)
1.2 jfb 223: *ep = '\0';
1.1 jfb 224:
1.14 weingart 225: memset(ms, 0, sizeof ms);
226: if (sscanf(sp, "%c=%3s", &type, ms) != 2 &&
227: sscanf(sp, "%c=", &type) != 1) {
1.1 jfb 228: cvs_log(LP_WARN, "failed to scan mode string `%s'", sp);
229: continue;
230: }
231:
232: if ((type <= 'a') || (type >= 'z') ||
233: (cvs_modetypes[type - 'a'] == -1)) {
234: cvs_log(LP_WARN,
235: "invalid mode type `%c'"
236: " (`u', `g' or `o' expected), ignoring", type);
237: continue;
238: }
239:
240: /* make type contain the actual mode index */
241: type = cvs_modetypes[type - 'a'];
242:
243: for (sp = ms; *sp != '\0'; sp++) {
244: if ((*sp <= 'a') || (*sp >= 'z') ||
1.5 jfb 245: (cvs_modes[(int)type][*sp - 'a'] == 0)) {
1.1 jfb 246: cvs_log(LP_WARN,
247: "invalid permission bit `%c'", *sp);
1.15 deraadt 248: } else
1.5 jfb 249: m |= cvs_modes[(int)type][*sp - 'a'];
1.1 jfb 250: }
251: }
252:
253: *mode = m;
254:
255: return (0);
256: }
257:
258:
259: /*
260: * cvs_modetostr()
261: *
262: * Returns 0 on success, or -1 on failure.
263: */
264: int
265: cvs_modetostr(mode_t mode, char *buf, size_t len)
266: {
267: size_t l;
268: char tmp[16], *bp;
269: mode_t um, gm, om;
270:
271: um = (mode & S_IRWXU) >> 6;
272: gm = (mode & S_IRWXG) >> 3;
273: om = mode & S_IRWXO;
274:
275: bp = buf;
276: *bp = '\0';
277: l = 0;
278:
279: if (um) {
280: snprintf(tmp, sizeof(tmp), "u=%s", cvs_modestr[um]);
1.9 jfb 281: l = strlcat(buf, tmp, len);
1.1 jfb 282: }
283: if (gm) {
284: if (um)
285: strlcat(buf, ",", len);
286: snprintf(tmp, sizeof(tmp), "g=%s", cvs_modestr[gm]);
1.9 jfb 287: strlcat(buf, tmp, len);
1.1 jfb 288: }
289: if (om) {
290: if (um || gm)
291: strlcat(buf, ",", len);
292: snprintf(tmp, sizeof(tmp), "o=%s", cvs_modestr[gm]);
1.9 jfb 293: strlcat(buf, tmp, len);
1.1 jfb 294: }
295:
296: return (0);
297: }
298:
299:
300: /*
301: * cvs_cksum()
302: *
303: * Calculate the MD5 checksum of the file whose path is <file> and generate
304: * a CVS-format 32 hex-digit string, which is stored in <dst>, whose size is
305: * given in <len> and must be at least 33.
306: * Returns 0 on success, or -1 on failure.
307: */
308: int
309: cvs_cksum(const char *file, char *dst, size_t len)
310: {
311: if (len < CVS_CKSUM_LEN) {
312: cvs_log(LP_WARN, "buffer too small for checksum");
313: return (-1);
314: }
315: if (MD5File(file, dst) == NULL) {
1.19 jfb 316: cvs_log(LP_ERRNO, "failed to generate checksum for %s", file);
1.1 jfb 317: return (-1);
318: }
319:
320: return (0);
321: }
322:
323:
324: /*
325: * cvs_splitpath()
326: *
1.7 jfb 327: * Split a path <path> into the base portion and the filename portion.
328: * The path is copied in <base> and the last delimiter is replaced by a NUL
329: * byte. The <file> pointer is set to point to the first character after
330: * that delimiter.
1.1 jfb 331: * Returns 0 on success, or -1 on failure.
332: */
333: int
1.7 jfb 334: cvs_splitpath(const char *path, char *base, size_t blen, char **file)
1.1 jfb 335: {
336: size_t rlen;
1.7 jfb 337: char *sp;
1.1 jfb 338:
1.7 jfb 339: if ((rlen = strlcpy(base, path, blen)) >= blen)
340: return (-1);
1.6 jfb 341:
1.7 jfb 342: while ((rlen > 0) && (base[rlen - 1] == '/'))
343: base[--rlen] = '\0';
344:
345: sp = strrchr(base, '/');
1.1 jfb 346: if (sp == NULL) {
1.7 jfb 347: strlcpy(base, "./", blen);
348: strlcat(base, path, blen);
349: sp = base + 1;
1.1 jfb 350: }
351:
1.7 jfb 352: *sp = '\0';
353: if (file != NULL)
354: *file = sp + 1;
1.1 jfb 355:
356: return (0);
357: }
358:
359:
360: /*
361: * cvs_getargv()
362: *
363: * Parse a line contained in <line> and generate an argument vector by
364: * splitting the line on spaces and tabs. The resulting vector is stored in
365: * <argv>, which can accept up to <argvlen> entries.
1.20 ! david 366: * Returns the number of arguments in the vector, or -1 if an error occurred.
1.1 jfb 367: */
368: int
369: cvs_getargv(const char *line, char **argv, int argvlen)
370: {
371: u_int i;
372: int argc, err;
373: char linebuf[256], qbuf[128], *lp, *cp, *arg;
374:
375: strlcpy(linebuf, line, sizeof(linebuf));
376: memset(argv, 0, sizeof(argv));
377: argc = 0;
378:
379: /* build the argument vector */
380: err = 0;
381: for (lp = linebuf; lp != NULL;) {
382: if (*lp == '"') {
383: /* double-quoted string */
384: lp++;
385: i = 0;
386: memset(qbuf, 0, sizeof(qbuf));
387: while (*lp != '"') {
1.16 jfb 388: if (*lp == '\\')
389: lp++;
1.1 jfb 390: if (*lp == '\0') {
391: cvs_log(LP_ERR, "no terminating quote");
392: err++;
393: break;
1.16 jfb 394: }
1.1 jfb 395:
1.9 jfb 396: qbuf[i++] = *lp++;
1.1 jfb 397: if (i == sizeof(qbuf)) {
398: err++;
399: break;
400: }
401: }
402:
403: arg = qbuf;
1.15 deraadt 404: } else {
1.1 jfb 405: cp = strsep(&lp, " \t");
406: if (cp == NULL)
407: break;
408: else if (*cp == '\0')
409: continue;
410:
411: arg = cp;
412: }
413:
1.16 jfb 414: if (argc == argvlen) {
415: err++;
416: break;
417: }
418:
1.1 jfb 419: argv[argc] = strdup(arg);
420: if (argv[argc] == NULL) {
421: cvs_log(LP_ERRNO, "failed to copy argument");
422: err++;
423: break;
424: }
425: argc++;
426: }
427:
428: if (err) {
429: /* ditch the argument vector */
430: for (i = 0; i < (u_int)argc; i++)
431: free(argv[i]);
432: argc = -1;
433: }
434:
435: return (argc);
1.17 jfb 436: }
437:
438:
439: /*
440: * cvs_makeargv()
441: *
1.20 ! david 442: * Allocate an argument vector large enough to accommodate for all the
1.17 jfb 443: * arguments found in <line> and return it.
444: */
445:
446: char**
447: cvs_makeargv(const char *line, int *argc)
448: {
449: int i, ret;
450: char *argv[1024], **copy;
451:
452: ret = cvs_getargv(line, argv, 1024);
453: if (ret == -1)
454: return (NULL);
455:
456: copy = (char **)malloc((ret + 1) * sizeof(char *));
457: if (copy == NULL) {
458: cvs_log(LP_ERRNO, "failed to allocate argument vector");
459: return (NULL);
460: }
461: memset(copy, 0, sizeof(copy));
462:
463: for (i = 0; i < ret; i++)
464: copy[i] = argv[i];
465: copy[ret] = NULL;
466:
467: *argc = ret;
468: return (copy);
1.1 jfb 469: }
470:
471:
472: /*
473: * cvs_freeargv()
474: *
475: * Free an argument vector previously generated by cvs_getargv().
476: */
477: void
478: cvs_freeargv(char **argv, int argc)
479: {
480: int i;
481:
482: for (i = 0; i < argc; i++)
1.16 jfb 483: if (argv[i] != NULL)
484: free(argv[i]);
1.2 jfb 485: }
486:
487:
488: /*
489: * cvs_mkadmin()
490: *
1.5 jfb 491: * Create the CVS administrative files within the directory <cdir>. If the
492: * files already exist, they are kept as is.
1.2 jfb 493: * Returns 0 on success, or -1 on failure.
494: */
495: int
1.13 jfb 496: cvs_mkadmin(CVSFILE *cdir, mode_t mode)
1.2 jfb 497: {
1.13 jfb 498: char dpath[MAXPATHLEN], path[MAXPATHLEN];
1.2 jfb 499: FILE *fp;
500: CVSENTRIES *ef;
1.5 jfb 501: struct stat st;
1.2 jfb 502: struct cvsroot *root;
503:
1.13 jfb 504: cvs_file_getpath(cdir, dpath, sizeof(dpath));
505:
506: snprintf(path, sizeof(path), "%s/" CVS_PATH_CVSDIR, dpath);
1.5 jfb 507: if ((mkdir(path, mode) == -1) && (errno != EEXIST)) {
1.2 jfb 508: cvs_log(LP_ERRNO, "failed to create directory %s", path);
509: return (-1);
510: }
511:
1.5 jfb 512: /* just create an empty Entries file */
1.13 jfb 513: ef = cvs_ent_open(dpath, O_WRONLY);
1.2 jfb 514: (void)cvs_ent_close(ef);
515:
1.5 jfb 516: root = cdir->cf_ddat->cd_root;
1.13 jfb 517: snprintf(path, sizeof(path), "%s/" CVS_PATH_ROOTSPEC, dpath);
1.8 jfb 518: if ((root != NULL) && (stat(path, &st) != 0) && (errno == ENOENT)) {
1.5 jfb 519: fp = fopen(path, "w");
520: if (fp == NULL) {
521: cvs_log(LP_ERRNO, "failed to open %s", path);
522: return (-1);
523: }
524: if (root->cr_user != NULL) {
525: fprintf(fp, "%s", root->cr_user);
526: if (root->cr_pass != NULL)
527: fprintf(fp, ":%s", root->cr_pass);
528: if (root->cr_host != NULL)
529: putc('@', fp);
530: }
531:
532: if (root->cr_host != NULL) {
533: fprintf(fp, "%s", root->cr_host);
534: if (root->cr_dir != NULL)
535: putc(':', fp);
536: }
537: if (root->cr_dir)
538: fprintf(fp, "%s", root->cr_dir);
539: putc('\n', fp);
540: (void)fclose(fp);
1.2 jfb 541: }
542:
1.13 jfb 543: snprintf(path, sizeof(path), "%s/" CVS_PATH_REPOSITORY, dpath);
1.5 jfb 544: if ((stat(path, &st) != 0) && (errno == ENOENT) &&
545: (cdir->cf_ddat->cd_repo != NULL)) {
1.2 jfb 546: fp = fopen(path, "w");
547: if (fp == NULL) {
548: cvs_log(LP_ERRNO, "failed to open %s", path);
549: return (-1);
550: }
1.3 jfb 551: fprintf(fp, "%s\n", cdir->cf_ddat->cd_repo);
1.2 jfb 552: (void)fclose(fp);
553: }
554:
555: return (0);
1.11 krapht 556: }
557:
558:
559: /*
560: * cvs_exec()
561: */
562: int
563: cvs_exec(int argc, char **argv, int fds[3])
564: {
565: int ret;
566: pid_t pid;
567:
568: if ((pid = fork()) == -1) {
569: cvs_log(LP_ERRNO, "failed to fork");
570: return (-1);
571: } else if (pid == 0) {
572: execvp(argv[0], argv);
1.13 jfb 573: cvs_log(LP_ERRNO, "failed to exec %s", argv[0]);
574: exit(1);
1.11 krapht 575: }
576:
577: if (waitpid(pid, &ret, 0) == -1)
1.13 jfb 578: cvs_log(LP_ERRNO, "failed to waitpid");
1.11 krapht 579:
580: return (ret);
1.1 jfb 581: }