Annotation of src/usr.bin/cvs/util.c, Revision 1.30
1.30 ! jfb 1: /* $OpenBSD: util.c,v 1.29 2005/05/26 22:25:31 jfb 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:
43: /* letter -> mode type map */
44: static const int cvs_modetypes[26] = {
45: -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, -1,
46: -1, 2, -1, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1,
47: };
48:
49: /* letter -> mode map */
50: static const mode_t cvs_modes[3][26] = {
51: {
52: 0, 0, 0, 0, 0, 0, 0, /* a - g */
53: 0, 0, 0, 0, 0, 0, 0, /* h - m */
54: 0, 0, 0, S_IRUSR, 0, 0, 0, /* n - u */
55: 0, S_IWUSR, S_IXUSR, 0, 0 /* v - z */
56: },
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_IRGRP, 0, 0, 0, /* n - u */
61: 0, S_IWGRP, S_IXGRP, 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_IROTH, 0, 0, 0, /* n - u */
67: 0, S_IWOTH, S_IXOTH, 0, 0 /* v - z */
68: }
69: };
70:
71:
72: /* octal -> string */
73: static const char *cvs_modestr[8] = {
74: "", "x", "w", "wx", "r", "rx", "rw", "rwx"
75: };
76:
77:
1.11 krapht 78: pid_t cvs_exec_pid;
79:
1.1 jfb 80:
81: /*
82: * cvs_readrepo()
83: *
84: * Read the path stored in the `Repository' CVS file for a given directory
85: * <dir>, and store that path into the buffer pointed to by <dst>, whose size
86: * is <len>.
87: */
88: int
89: cvs_readrepo(const char *dir, char *dst, size_t len)
90: {
1.21 xsa 91: int l;
1.1 jfb 92: size_t dlen;
93: FILE *fp;
94: char repo_path[MAXPATHLEN];
95:
1.21 xsa 96: l = snprintf(repo_path, sizeof(repo_path), "%s/CVS/Repository", dir);
97: if (l == -1 || l >= (int)sizeof(repo_path)) {
98: errno = ENAMETOOLONG;
99: cvs_log(LP_ERRNO, "%s", repo_path);
100: return (NULL);
101: }
102:
1.1 jfb 103: fp = fopen(repo_path, "r");
1.21 xsa 104: if (fp == NULL)
1.1 jfb 105: return (-1);
106:
107: if (fgets(dst, (int)len, fp) == NULL) {
108: if (ferror(fp)) {
109: cvs_log(LP_ERRNO, "failed to read from `%s'",
110: repo_path);
111: }
112: (void)fclose(fp);
113: return (-1);
114: }
115: dlen = strlen(dst);
116: if ((dlen > 0) && (dst[dlen - 1] == '\n'))
117: dst[--dlen] = '\0';
118:
119: (void)fclose(fp);
120: return (0);
1.9 jfb 121: }
122:
123:
124: /*
1.1 jfb 125: * cvs_strtomode()
126: *
127: * Read the contents of the string <str> and generate a permission mode from
128: * the contents of <str>, which is assumed to have the mode format of CVS.
129: * The CVS protocol specification states that any modes or mode types that are
130: * not recognized should be silently ignored. This function does not return
131: * an error in such cases, but will issue warnings.
132: * Returns 0 on success, or -1 on failure.
133: */
134: int
135: cvs_strtomode(const char *str, mode_t *mode)
136: {
1.2 jfb 137: char type;
1.1 jfb 138: mode_t m;
139: char buf[32], ms[4], *sp, *ep;
140:
141: m = 0;
1.30 ! jfb 142: if (strlcpy(buf, str, sizeof(buf)) >= sizeof(buf)) {
! 143: return (-1);
! 144: }
1.1 jfb 145: sp = buf;
146: ep = sp;
147:
148: for (sp = buf; ep != NULL; sp = ep + 1) {
149: ep = strchr(sp, ',');
150: if (ep != NULL)
1.2 jfb 151: *ep = '\0';
1.1 jfb 152:
1.14 weingart 153: memset(ms, 0, sizeof ms);
154: if (sscanf(sp, "%c=%3s", &type, ms) != 2 &&
155: sscanf(sp, "%c=", &type) != 1) {
1.1 jfb 156: cvs_log(LP_WARN, "failed to scan mode string `%s'", sp);
157: continue;
158: }
159:
160: if ((type <= 'a') || (type >= 'z') ||
161: (cvs_modetypes[type - 'a'] == -1)) {
162: cvs_log(LP_WARN,
163: "invalid mode type `%c'"
164: " (`u', `g' or `o' expected), ignoring", type);
165: continue;
166: }
167:
168: /* make type contain the actual mode index */
169: type = cvs_modetypes[type - 'a'];
170:
171: for (sp = ms; *sp != '\0'; sp++) {
172: if ((*sp <= 'a') || (*sp >= 'z') ||
1.5 jfb 173: (cvs_modes[(int)type][*sp - 'a'] == 0)) {
1.1 jfb 174: cvs_log(LP_WARN,
175: "invalid permission bit `%c'", *sp);
1.15 deraadt 176: } else
1.5 jfb 177: m |= cvs_modes[(int)type][*sp - 'a'];
1.1 jfb 178: }
179: }
180:
181: *mode = m;
182:
183: return (0);
184: }
185:
186:
187: /*
188: * cvs_modetostr()
189: *
1.30 ! jfb 190: * Generate a CVS-format string to represent the permissions mask on a file
! 191: * from the mode <mode> and store the result in <buf>, which can accept up to
! 192: * <len> bytes (including the terminating NUL byte). The result is guaranteed
! 193: * to be NUL-terminated.
1.1 jfb 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: * cvs_cksum()
233: *
234: * Calculate the MD5 checksum of the file whose path is <file> and generate
235: * a CVS-format 32 hex-digit string, which is stored in <dst>, whose size is
236: * given in <len> and must be at least 33.
237: * Returns 0 on success, or -1 on failure.
238: */
239: int
240: cvs_cksum(const char *file, char *dst, size_t len)
241: {
242: if (len < CVS_CKSUM_LEN) {
243: cvs_log(LP_WARN, "buffer too small for checksum");
244: return (-1);
245: }
246: if (MD5File(file, dst) == NULL) {
1.19 jfb 247: cvs_log(LP_ERRNO, "failed to generate checksum for %s", file);
1.1 jfb 248: return (-1);
249: }
250:
251: return (0);
252: }
253:
254: /*
255: * cvs_splitpath()
256: *
1.7 jfb 257: * Split a path <path> into the base portion and the filename portion.
258: * The path is copied in <base> and the last delimiter is replaced by a NUL
259: * byte. The <file> pointer is set to point to the first character after
260: * that delimiter.
1.1 jfb 261: * Returns 0 on success, or -1 on failure.
262: */
263: int
1.7 jfb 264: cvs_splitpath(const char *path, char *base, size_t blen, char **file)
1.1 jfb 265: {
266: size_t rlen;
1.7 jfb 267: char *sp;
1.1 jfb 268:
1.7 jfb 269: if ((rlen = strlcpy(base, path, blen)) >= blen)
270: return (-1);
1.6 jfb 271:
1.7 jfb 272: while ((rlen > 0) && (base[rlen - 1] == '/'))
273: base[--rlen] = '\0';
274:
275: sp = strrchr(base, '/');
1.1 jfb 276: if (sp == NULL) {
1.7 jfb 277: strlcpy(base, "./", blen);
278: strlcat(base, path, blen);
279: sp = base + 1;
1.1 jfb 280: }
281:
1.7 jfb 282: *sp = '\0';
283: if (file != NULL)
284: *file = sp + 1;
1.1 jfb 285:
286: return (0);
287: }
288:
289:
290: /*
291: * cvs_getargv()
292: *
293: * Parse a line contained in <line> and generate an argument vector by
294: * splitting the line on spaces and tabs. The resulting vector is stored in
295: * <argv>, which can accept up to <argvlen> entries.
1.20 david 296: * Returns the number of arguments in the vector, or -1 if an error occurred.
1.1 jfb 297: */
298: int
299: cvs_getargv(const char *line, char **argv, int argvlen)
300: {
301: u_int i;
302: int argc, err;
303: char linebuf[256], qbuf[128], *lp, *cp, *arg;
304:
305: strlcpy(linebuf, line, sizeof(linebuf));
1.27 pat 306: memset(argv, 0, argvlen * sizeof(char *));
1.1 jfb 307: argc = 0;
308:
309: /* build the argument vector */
310: err = 0;
311: for (lp = linebuf; lp != NULL;) {
312: if (*lp == '"') {
313: /* double-quoted string */
314: lp++;
315: i = 0;
316: memset(qbuf, 0, sizeof(qbuf));
317: while (*lp != '"') {
1.16 jfb 318: if (*lp == '\\')
319: lp++;
1.1 jfb 320: if (*lp == '\0') {
321: cvs_log(LP_ERR, "no terminating quote");
322: err++;
323: break;
1.16 jfb 324: }
1.1 jfb 325:
1.9 jfb 326: qbuf[i++] = *lp++;
1.1 jfb 327: if (i == sizeof(qbuf)) {
328: err++;
329: break;
330: }
331: }
332:
333: arg = qbuf;
1.15 deraadt 334: } else {
1.1 jfb 335: cp = strsep(&lp, " \t");
336: if (cp == NULL)
337: break;
338: else if (*cp == '\0')
339: continue;
340:
341: arg = cp;
342: }
343:
1.16 jfb 344: if (argc == argvlen) {
345: err++;
346: break;
347: }
348:
1.1 jfb 349: argv[argc] = strdup(arg);
350: if (argv[argc] == NULL) {
351: cvs_log(LP_ERRNO, "failed to copy argument");
352: err++;
353: break;
354: }
355: argc++;
356: }
357:
358: if (err) {
359: /* ditch the argument vector */
360: for (i = 0; i < (u_int)argc; i++)
361: free(argv[i]);
362: argc = -1;
363: }
364:
365: return (argc);
1.17 jfb 366: }
367:
368:
369: /*
370: * cvs_makeargv()
371: *
1.20 david 372: * Allocate an argument vector large enough to accommodate for all the
1.17 jfb 373: * arguments found in <line> and return it.
374: */
375: char**
376: cvs_makeargv(const char *line, int *argc)
377: {
378: int i, ret;
379: char *argv[1024], **copy;
1.27 pat 380: size_t size;
1.17 jfb 381:
382: ret = cvs_getargv(line, argv, 1024);
383: if (ret == -1)
384: return (NULL);
385:
1.27 pat 386: size = (ret + 1) * sizeof(char *);
387: copy = (char **)malloc(size);
1.17 jfb 388: if (copy == NULL) {
389: cvs_log(LP_ERRNO, "failed to allocate argument vector");
1.27 pat 390: cvs_freeargv(argv, ret);
1.17 jfb 391: return (NULL);
392: }
1.27 pat 393: memset(copy, 0, size);
1.17 jfb 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.28 joris 428: cvs_mkadmin(const char *dpath, const char *rootpath, const char *repopath)
1.2 jfb 429: {
1.21 xsa 430: int l;
1.28 joris 431: char path[MAXPATHLEN];
1.2 jfb 432: FILE *fp;
433: CVSENTRIES *ef;
1.5 jfb 434: struct stat st;
1.13 jfb 435:
1.21 xsa 436: l = snprintf(path, sizeof(path), "%s/" CVS_PATH_CVSDIR, dpath);
437: if (l == -1 || l >= (int)sizeof(path)) {
438: errno = ENAMETOOLONG;
439: cvs_log(LP_ERRNO, "%s", path);
440: return (-1);
441: }
442:
1.28 joris 443: if ((mkdir(path, 0755) == -1) && (errno != EEXIST)) {
1.2 jfb 444: cvs_log(LP_ERRNO, "failed to create directory %s", path);
445: return (-1);
446: }
447:
1.5 jfb 448: /* just create an empty Entries file */
1.13 jfb 449: ef = cvs_ent_open(dpath, O_WRONLY);
1.2 jfb 450: (void)cvs_ent_close(ef);
451:
1.21 xsa 452: l = snprintf(path, sizeof(path), "%s/" CVS_PATH_ROOTSPEC, dpath);
453: if (l == -1 || l >= (int)sizeof(path)) {
454: errno = ENAMETOOLONG;
455: cvs_log(LP_ERRNO, "%s", path);
456: return (-1);
457: }
458:
1.28 joris 459: if ((stat(path, &st) != 0) && (errno == ENOENT)) {
1.5 jfb 460: fp = fopen(path, "w");
461: if (fp == NULL) {
462: cvs_log(LP_ERRNO, "failed to open %s", path);
463: return (-1);
464: }
1.28 joris 465: if (rootpath != NULL)
466: fprintf(fp, "%s\n", rootpath);
1.5 jfb 467: (void)fclose(fp);
1.2 jfb 468: }
469:
1.22 xsa 470: l = snprintf(path, sizeof(path), "%s/" CVS_PATH_REPOSITORY, dpath);
1.21 xsa 471: if (l == -1 || l >= (int)sizeof(path)) {
472: errno = ENAMETOOLONG;
473: cvs_log(LP_ERRNO, "%s", path);
474: return (-1);
475: }
476:
1.28 joris 477: if ((stat(path, &st) != 0) && (errno == ENOENT)) {
1.2 jfb 478: fp = fopen(path, "w");
479: if (fp == NULL) {
480: cvs_log(LP_ERRNO, "failed to open %s", path);
481: return (-1);
482: }
1.28 joris 483: if (repopath != NULL)
484: fprintf(fp, "%s\n", repopath);
1.2 jfb 485: (void)fclose(fp);
486: }
487:
488: return (0);
1.11 krapht 489: }
490:
491:
492: /*
493: * cvs_exec()
494: */
495: int
496: cvs_exec(int argc, char **argv, int fds[3])
497: {
498: int ret;
499: pid_t pid;
500:
501: if ((pid = fork()) == -1) {
502: cvs_log(LP_ERRNO, "failed to fork");
503: return (-1);
504: } else if (pid == 0) {
505: execvp(argv[0], argv);
1.13 jfb 506: cvs_log(LP_ERRNO, "failed to exec %s", argv[0]);
507: exit(1);
1.11 krapht 508: }
509:
510: if (waitpid(pid, &ret, 0) == -1)
1.13 jfb 511: cvs_log(LP_ERRNO, "failed to waitpid");
1.11 krapht 512:
513: return (ret);
1.1 jfb 514: }
1.24 joris 515:
516: /*
1.30 ! jfb 517: * cvs_remove_dir()
! 518: *
! 519: * Remove a directory tree from disk.
! 520: * Returns 0 on success, or -1 on failure.
1.24 joris 521: */
522: int
523: cvs_remove_dir(const char *path)
524: {
1.30 ! jfb 525: size_t len;
1.24 joris 526: DIR *dirp;
527: struct dirent *ent;
528: char fpath[MAXPATHLEN];
529:
530: if ((dirp = opendir(path)) == NULL) {
531: cvs_log(LP_ERRNO, "failed to open '%s'", path);
1.30 ! jfb 532: return (-1);
1.24 joris 533: }
534:
535: while ((ent = readdir(dirp)) != NULL) {
536: if (!strcmp(ent->d_name, ".") ||
537: !strcmp(ent->d_name, ".."))
538: continue;
539:
1.30 ! jfb 540: len = cvs_path_cat(path, ent->d_name, fpath, sizeof(fpath));
! 541: if (len >= sizeof(fpath)) {
1.24 joris 542: errno = ENAMETOOLONG;
543: cvs_log(LP_ERRNO, "%s", fpath);
544: closedir(dirp);
1.30 ! jfb 545: return (-1);
1.24 joris 546: }
547:
548: if (ent->d_type == DT_DIR) {
1.30 ! jfb 549: if (cvs_remove_dir(fpath) == -1) {
1.24 joris 550: closedir(dirp);
1.30 ! jfb 551: return (-1);
1.24 joris 552: }
1.30 ! jfb 553: } else if ((unlink(fpath) == -1) && (errno != ENOENT)) {
! 554: cvs_log(LP_ERRNO, "failed to remove '%s'", fpath);
! 555: return (-1);
1.24 joris 556: }
557: }
558:
559: closedir(dirp);
560:
1.30 ! jfb 561: if ((rmdir(path) == -1) && (errno != ENOENT)) {
1.24 joris 562: cvs_log(LP_ERRNO, "failed to remove '%s'", path);
1.30 ! jfb 563: return (-1);
! 564: }
1.24 joris 565:
1.30 ! jfb 566: return (0);
1.24 joris 567: }
568:
1.30 ! jfb 569: /*
! 570: * cvs_path_cat()
! 571: *
! 572: * Concatenate the two paths <base> and <end> and store the generated path
! 573: * into the buffer <dst>, which can accept up to <dlen> bytes, including the
! 574: * NUL byte. The result is guaranteed to be NUL-terminated.
! 575: * Returns the number of bytes necessary to store the full resulting path,
! 576: * not including the NUL byte (a value equal to or larger than <dlen>
! 577: * indicates truncation).
! 578: */
1.29 jfb 579: size_t
580: cvs_path_cat(const char *base, const char *end, char *dst, size_t dlen)
581: {
582: size_t len;
583:
584: len = strlcpy(dst, base, dlen);
585: if (len >= dlen - 1) {
586: errno = ENAMETOOLONG;
587: cvs_log(LP_ERRNO, "%s", dst);
588: } else {
589: dst[len] = '/';
590: dst[len + 1] = '\0';
591: len = strlcat(dst, end, dlen);
592: if (len >= dlen) {
593: errno = ENAMETOOLONG;
594: cvs_log(LP_ERRNO, "%s", dst);
595: }
596: }
597:
598: return (len);
599: }