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