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