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