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