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