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