Annotation of src/usr.bin/cvs/util.c, Revision 1.13
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.2 jfb 232: if (sscanf(sp, "%c=%3s", &type, ms) != 2) {
1.1 jfb 233: cvs_log(LP_WARN, "failed to scan mode string `%s'", sp);
234: continue;
235: }
236:
237: if ((type <= 'a') || (type >= 'z') ||
238: (cvs_modetypes[type - 'a'] == -1)) {
239: cvs_log(LP_WARN,
240: "invalid mode type `%c'"
241: " (`u', `g' or `o' expected), ignoring", type);
242: continue;
243: }
244:
245: /* make type contain the actual mode index */
246: type = cvs_modetypes[type - 'a'];
247:
248: for (sp = ms; *sp != '\0'; sp++) {
249: if ((*sp <= 'a') || (*sp >= 'z') ||
1.5 jfb 250: (cvs_modes[(int)type][*sp - 'a'] == 0)) {
1.1 jfb 251: cvs_log(LP_WARN,
252: "invalid permission bit `%c'", *sp);
253: }
254: else
1.5 jfb 255: m |= cvs_modes[(int)type][*sp - 'a'];
1.1 jfb 256: }
257: }
258:
259: *mode = m;
260:
261: return (0);
262: }
263:
264:
265: /*
266: * cvs_modetostr()
267: *
268: * Returns 0 on success, or -1 on failure.
269: */
270:
271: int
272: cvs_modetostr(mode_t mode, char *buf, size_t len)
273: {
274: size_t l;
275: char tmp[16], *bp;
276: mode_t um, gm, om;
277:
278: um = (mode & S_IRWXU) >> 6;
279: gm = (mode & S_IRWXG) >> 3;
280: om = mode & S_IRWXO;
281:
282: bp = buf;
283: *bp = '\0';
284: l = 0;
285:
286: if (um) {
287: snprintf(tmp, sizeof(tmp), "u=%s", cvs_modestr[um]);
1.9 jfb 288: l = strlcat(buf, tmp, len);
1.1 jfb 289: }
290: if (gm) {
291: if (um)
292: strlcat(buf, ",", len);
293: snprintf(tmp, sizeof(tmp), "g=%s", cvs_modestr[gm]);
1.9 jfb 294: strlcat(buf, tmp, len);
1.1 jfb 295: }
296: if (om) {
297: if (um || gm)
298: strlcat(buf, ",", len);
299: snprintf(tmp, sizeof(tmp), "o=%s", cvs_modestr[gm]);
1.9 jfb 300: strlcat(buf, tmp, len);
1.1 jfb 301: }
302:
303: return (0);
304: }
305:
306:
307: /*
308: * cvs_cksum()
309: *
310: * Calculate the MD5 checksum of the file whose path is <file> and generate
311: * a CVS-format 32 hex-digit string, which is stored in <dst>, whose size is
312: * given in <len> and must be at least 33.
313: * Returns 0 on success, or -1 on failure.
314: */
315:
316: int
317: cvs_cksum(const char *file, char *dst, size_t len)
318: {
319: if (len < CVS_CKSUM_LEN) {
320: cvs_log(LP_WARN, "buffer too small for checksum");
321: return (-1);
322: }
323: if (MD5File(file, dst) == NULL) {
324: cvs_log(LP_ERRNO, "failed to generate file checksum");
325: return (-1);
326: }
327:
328: return (0);
329: }
330:
331:
332: /*
333: * cvs_splitpath()
334: *
1.7 jfb 335: * Split a path <path> into the base portion and the filename portion.
336: * The path is copied in <base> and the last delimiter is replaced by a NUL
337: * byte. The <file> pointer is set to point to the first character after
338: * that delimiter.
1.1 jfb 339: * Returns 0 on success, or -1 on failure.
340: */
341:
342: int
1.7 jfb 343: cvs_splitpath(const char *path, char *base, size_t blen, char **file)
1.1 jfb 344: {
345: size_t rlen;
1.7 jfb 346: char *sp;
1.1 jfb 347:
1.7 jfb 348: if ((rlen = strlcpy(base, path, blen)) >= blen)
349: return (-1);
1.6 jfb 350:
1.7 jfb 351: while ((rlen > 0) && (base[rlen - 1] == '/'))
352: base[--rlen] = '\0';
353:
354: sp = strrchr(base, '/');
1.1 jfb 355: if (sp == NULL) {
1.7 jfb 356: strlcpy(base, "./", blen);
357: strlcat(base, path, blen);
358: sp = base + 1;
1.1 jfb 359: }
360:
1.7 jfb 361: *sp = '\0';
362: if (file != NULL)
363: *file = sp + 1;
1.1 jfb 364:
365: return (0);
366: }
367:
368:
369: /*
370: * cvs_getargv()
371: *
372: * Parse a line contained in <line> and generate an argument vector by
373: * splitting the line on spaces and tabs. The resulting vector is stored in
374: * <argv>, which can accept up to <argvlen> entries.
375: * Returns the number of arguments in the vector, or -1 if an error occured.
376: */
377:
378: int
379: cvs_getargv(const char *line, char **argv, int argvlen)
380: {
381: u_int i;
382: int argc, err;
383: char linebuf[256], qbuf[128], *lp, *cp, *arg;
384:
385: strlcpy(linebuf, line, sizeof(linebuf));
386: memset(argv, 0, sizeof(argv));
387: argc = 0;
388:
389: /* build the argument vector */
390: err = 0;
391: for (lp = linebuf; lp != NULL;) {
392: if (*lp == '"') {
393: /* double-quoted string */
394: lp++;
395: i = 0;
396: memset(qbuf, 0, sizeof(qbuf));
397: while (*lp != '"') {
398: if (*lp == '\0') {
399: cvs_log(LP_ERR, "no terminating quote");
400: err++;
401: break;
402: }
403: else if (*lp == '\\')
404: lp++;
405:
1.9 jfb 406: qbuf[i++] = *lp++;
1.1 jfb 407: if (i == sizeof(qbuf)) {
408: err++;
409: break;
410: }
411: }
412:
413: arg = qbuf;
414: }
415: else {
416: cp = strsep(&lp, " \t");
417: if (cp == NULL)
418: break;
419: else if (*cp == '\0')
420: continue;
421:
422: arg = cp;
423: }
424:
425: argv[argc] = strdup(arg);
426: if (argv[argc] == NULL) {
427: cvs_log(LP_ERRNO, "failed to copy argument");
428: err++;
429: break;
430: }
431: argc++;
432: }
433:
434: if (err) {
435: /* ditch the argument vector */
436: for (i = 0; i < (u_int)argc; i++)
437: free(argv[i]);
438: argc = -1;
439: }
440:
441: return (argc);
442: }
443:
444:
445: /*
446: * cvs_freeargv()
447: *
448: * Free an argument vector previously generated by cvs_getargv().
449: */
450:
451: void
452: cvs_freeargv(char **argv, int argc)
453: {
454: int i;
455:
456: for (i = 0; i < argc; i++)
457: free(argv[i]);
1.2 jfb 458: }
459:
460:
461: /*
462: * cvs_mkadmin()
463: *
1.5 jfb 464: * Create the CVS administrative files within the directory <cdir>. If the
465: * files already exist, they are kept as is.
1.2 jfb 466: * Returns 0 on success, or -1 on failure.
467: */
468:
469: int
1.13 ! jfb 470: cvs_mkadmin(CVSFILE *cdir, mode_t mode)
1.2 jfb 471: {
1.13 ! jfb 472: char dpath[MAXPATHLEN], path[MAXPATHLEN];
1.2 jfb 473: FILE *fp;
474: CVSENTRIES *ef;
1.5 jfb 475: struct stat st;
1.2 jfb 476: struct cvsroot *root;
477:
1.13 ! jfb 478: cvs_file_getpath(cdir, dpath, sizeof(dpath));
! 479:
! 480: snprintf(path, sizeof(path), "%s/" CVS_PATH_CVSDIR, dpath);
1.5 jfb 481: if ((mkdir(path, mode) == -1) && (errno != EEXIST)) {
1.2 jfb 482: cvs_log(LP_ERRNO, "failed to create directory %s", path);
483: return (-1);
484: }
485:
1.5 jfb 486: /* just create an empty Entries file */
1.13 ! jfb 487: ef = cvs_ent_open(dpath, O_WRONLY);
1.2 jfb 488: (void)cvs_ent_close(ef);
489:
1.5 jfb 490: root = cdir->cf_ddat->cd_root;
1.13 ! jfb 491: snprintf(path, sizeof(path), "%s/" CVS_PATH_ROOTSPEC, dpath);
1.8 jfb 492: if ((root != NULL) && (stat(path, &st) != 0) && (errno == ENOENT)) {
1.5 jfb 493: fp = fopen(path, "w");
494: if (fp == NULL) {
495: cvs_log(LP_ERRNO, "failed to open %s", path);
496: return (-1);
497: }
498: if (root->cr_user != NULL) {
499: fprintf(fp, "%s", root->cr_user);
500: if (root->cr_pass != NULL)
501: fprintf(fp, ":%s", root->cr_pass);
502: if (root->cr_host != NULL)
503: putc('@', fp);
504: }
505:
506: if (root->cr_host != NULL) {
507: fprintf(fp, "%s", root->cr_host);
508: if (root->cr_dir != NULL)
509: putc(':', fp);
510: }
511: if (root->cr_dir)
512: fprintf(fp, "%s", root->cr_dir);
513: putc('\n', fp);
514: (void)fclose(fp);
1.2 jfb 515: }
516:
1.13 ! jfb 517: snprintf(path, sizeof(path), "%s/" CVS_PATH_REPOSITORY, dpath);
1.5 jfb 518: if ((stat(path, &st) != 0) && (errno == ENOENT) &&
519: (cdir->cf_ddat->cd_repo != NULL)) {
1.2 jfb 520: fp = fopen(path, "w");
521: if (fp == NULL) {
522: cvs_log(LP_ERRNO, "failed to open %s", path);
523: return (-1);
524: }
1.3 jfb 525: fprintf(fp, "%s\n", cdir->cf_ddat->cd_repo);
1.2 jfb 526: (void)fclose(fp);
527: }
528:
529: return (0);
1.11 krapht 530: }
531:
532:
533: /*
534: * cvs_exec()
535: */
536:
537: int
538: cvs_exec(int argc, char **argv, int fds[3])
539: {
540: int ret;
541: pid_t pid;
542:
543: if ((pid = fork()) == -1) {
544: cvs_log(LP_ERRNO, "failed to fork");
545: return (-1);
546: } else if (pid == 0) {
547: execvp(argv[0], argv);
1.13 ! jfb 548: cvs_log(LP_ERRNO, "failed to exec %s", argv[0]);
! 549: exit(1);
1.11 krapht 550: }
551:
552: if (waitpid(pid, &ret, 0) == -1)
1.13 ! jfb 553: cvs_log(LP_ERRNO, "failed to waitpid");
1.11 krapht 554:
555: return (ret);
1.1 jfb 556: }