Annotation of src/usr.bin/cvs/util.c, Revision 1.75
1.74 ray 1: /* $OpenBSD: util.c,v 1.73 2006/03/27 06:13:51 pat Exp $ */
1.1 jfb 2: /*
3: * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
1.71 xsa 4: * Copyright (c) 2005, 2006 Joris Vink <joris@openbsd.org>
5: * Copyright (c) 2005, 2006 Xavier Santolaria <xsa@openbsd.org>
1.9 jfb 6: * All rights reserved.
1.1 jfb 7: *
1.9 jfb 8: * Redistribution and use in source and binary forms, with or without
9: * modification, are permitted provided that the following conditions
10: * are met:
1.1 jfb 11: *
1.9 jfb 12: * 1. Redistributions of source code must retain the above copyright
13: * notice, this list of conditions and the following disclaimer.
1.1 jfb 14: * 2. The name of the author may not be used to endorse or promote products
1.9 jfb 15: * derived from this software without specific prior written permission.
1.1 jfb 16: *
17: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
18: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
19: * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
20: * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
23: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
24: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
25: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
1.9 jfb 26: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1.1 jfb 27: */
28:
1.66 xsa 29: #include "includes.h"
1.1 jfb 30:
31: #include "cvs.h"
32: #include "log.h"
1.70 niallo 33: #include "util.h"
1.1 jfb 34:
1.54 joris 35: #if !defined(RCSPROG)
1.1 jfb 36: /* letter -> mode type map */
37: static const int cvs_modetypes[26] = {
38: -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, -1,
39: -1, 2, -1, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1,
40: };
41:
42: /* letter -> mode map */
43: static const mode_t cvs_modes[3][26] = {
44: {
45: 0, 0, 0, 0, 0, 0, 0, /* a - g */
46: 0, 0, 0, 0, 0, 0, 0, /* h - m */
47: 0, 0, 0, S_IRUSR, 0, 0, 0, /* n - u */
48: 0, S_IWUSR, S_IXUSR, 0, 0 /* v - z */
49: },
50: {
51: 0, 0, 0, 0, 0, 0, 0, /* a - g */
52: 0, 0, 0, 0, 0, 0, 0, /* h - m */
53: 0, 0, 0, S_IRGRP, 0, 0, 0, /* n - u */
54: 0, S_IWGRP, S_IXGRP, 0, 0 /* v - z */
55: },
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_IROTH, 0, 0, 0, /* n - u */
60: 0, S_IWOTH, S_IXOTH, 0, 0 /* v - z */
61: }
62: };
63:
64:
65: /* octal -> string */
66: static const char *cvs_modestr[8] = {
67: "", "x", "w", "wx", "r", "rx", "rw", "rwx"
68: };
69:
1.11 krapht 70:
1.1 jfb 71:
72: /*
73: * cvs_readrepo()
74: *
75: * Read the path stored in the `Repository' CVS file for a given directory
76: * <dir>, and store that path into the buffer pointed to by <dst>, whose size
77: * is <len>.
78: */
79: int
80: cvs_readrepo(const char *dir, char *dst, size_t len)
81: {
1.37 xsa 82: size_t dlen, l;
1.1 jfb 83: FILE *fp;
84: char repo_path[MAXPATHLEN];
85:
1.37 xsa 86: l = cvs_path_cat(dir, "CVS/Repository", repo_path, sizeof(repo_path));
87: if (l >= sizeof(repo_path))
1.65 joris 88: return (-1);
1.21 xsa 89:
1.1 jfb 90: fp = fopen(repo_path, "r");
1.21 xsa 91: if (fp == NULL)
1.1 jfb 92: return (-1);
93:
94: if (fgets(dst, (int)len, fp) == NULL) {
95: if (ferror(fp)) {
96: cvs_log(LP_ERRNO, "failed to read from `%s'",
97: repo_path);
98: }
99: (void)fclose(fp);
100: return (-1);
101: }
102: dlen = strlen(dst);
103: if ((dlen > 0) && (dst[dlen - 1] == '\n'))
104: dst[--dlen] = '\0';
105:
106: (void)fclose(fp);
107: return (0);
1.9 jfb 108: }
109:
110:
111: /*
1.1 jfb 112: * cvs_strtomode()
113: *
114: * Read the contents of the string <str> and generate a permission mode from
115: * the contents of <str>, which is assumed to have the mode format of CVS.
116: * The CVS protocol specification states that any modes or mode types that are
117: * not recognized should be silently ignored. This function does not return
118: * an error in such cases, but will issue warnings.
119: */
1.64 joris 120: void
1.1 jfb 121: cvs_strtomode(const char *str, mode_t *mode)
122: {
1.2 jfb 123: char type;
1.64 joris 124: size_t l;
1.1 jfb 125: mode_t m;
126: char buf[32], ms[4], *sp, *ep;
127:
128: m = 0;
1.64 joris 129: l = strlcpy(buf, str, sizeof(buf));
130: if (l >= sizeof(buf))
131: fatal("cvs_strtomode: string truncation");
132:
1.1 jfb 133: sp = buf;
134: ep = sp;
135:
136: for (sp = buf; ep != NULL; sp = ep + 1) {
137: ep = strchr(sp, ',');
138: if (ep != NULL)
1.2 jfb 139: *ep = '\0';
1.1 jfb 140:
1.14 weingart 141: memset(ms, 0, sizeof ms);
142: if (sscanf(sp, "%c=%3s", &type, ms) != 2 &&
143: sscanf(sp, "%c=", &type) != 1) {
1.1 jfb 144: cvs_log(LP_WARN, "failed to scan mode string `%s'", sp);
145: continue;
146: }
147:
148: if ((type <= 'a') || (type >= 'z') ||
149: (cvs_modetypes[type - 'a'] == -1)) {
150: cvs_log(LP_WARN,
151: "invalid mode type `%c'"
152: " (`u', `g' or `o' expected), ignoring", type);
153: continue;
154: }
155:
156: /* make type contain the actual mode index */
157: type = cvs_modetypes[type - 'a'];
158:
159: for (sp = ms; *sp != '\0'; sp++) {
160: if ((*sp <= 'a') || (*sp >= 'z') ||
1.5 jfb 161: (cvs_modes[(int)type][*sp - 'a'] == 0)) {
1.1 jfb 162: cvs_log(LP_WARN,
163: "invalid permission bit `%c'", *sp);
1.15 deraadt 164: } else
1.5 jfb 165: m |= cvs_modes[(int)type][*sp - 'a'];
1.1 jfb 166: }
167: }
168:
169: *mode = m;
170: }
171:
172:
173: /*
174: * cvs_modetostr()
175: *
1.30 jfb 176: * Generate a CVS-format string to represent the permissions mask on a file
177: * from the mode <mode> and store the result in <buf>, which can accept up to
178: * <len> bytes (including the terminating NUL byte). The result is guaranteed
179: * to be NUL-terminated.
1.1 jfb 180: */
1.65 joris 181: void
1.1 jfb 182: cvs_modetostr(mode_t mode, char *buf, size_t len)
183: {
184: char tmp[16], *bp;
185: mode_t um, gm, om;
186:
187: um = (mode & S_IRWXU) >> 6;
188: gm = (mode & S_IRWXG) >> 3;
189: om = mode & S_IRWXO;
190:
191: bp = buf;
192: *bp = '\0';
193:
194: if (um) {
1.68 xsa 195: if (strlcpy(tmp, "u=", sizeof(tmp)) >= sizeof(tmp) ||
196: strlcat(tmp, cvs_modestr[um], sizeof(tmp)) >= sizeof(tmp))
1.65 joris 197: fatal("cvs_modetostr: overflow for user mode");
198:
1.68 xsa 199: if (strlcat(buf, tmp, len) >= len)
1.65 joris 200: fatal("cvs_modetostr: string truncation");
1.1 jfb 201: }
1.65 joris 202:
1.1 jfb 203: if (gm) {
1.65 joris 204: if (um) {
1.68 xsa 205: if (strlcat(buf, ",", len) >= len)
1.65 joris 206: fatal("cvs_modetostr: string truncation");
207: }
208:
1.68 xsa 209: if (strlcpy(tmp, "g=", sizeof(tmp)) >= sizeof(tmp) ||
210: strlcat(tmp, cvs_modestr[gm], sizeof(tmp)) >= sizeof(tmp))
1.65 joris 211: fatal("cvs_modetostr: overflow for group mode");
212:
1.68 xsa 213: if (strlcat(buf, tmp, len) >= len)
1.65 joris 214: fatal("cvs_modetostr: string truncation");
1.1 jfb 215: }
1.65 joris 216:
1.1 jfb 217: if (om) {
1.65 joris 218: if (um || gm) {
1.68 xsa 219: if (strlcat(buf, ",", len) >= len)
1.65 joris 220: fatal("cvs_modetostr: string truncation");
221: }
222:
1.68 xsa 223: if (strlcpy(tmp, "o=", sizeof(tmp)) >= sizeof(tmp) ||
224: strlcat(tmp, cvs_modestr[gm], sizeof(tmp)) >= sizeof(tmp))
1.65 joris 225: fatal("cvs_modetostr: overflow for others mode");
226:
1.68 xsa 227: if (strlcat(buf, tmp, len) >= len)
1.65 joris 228: fatal("cvs_modetostr: string truncation");
1.1 jfb 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: * cvs_splitpath()
257: *
1.7 jfb 258: * Split a path <path> into the base portion and the filename portion.
259: * The path is copied in <base> and the last delimiter is replaced by a NUL
260: * byte. The <file> pointer is set to point to the first character after
261: * that delimiter.
1.1 jfb 262: * Returns 0 on success, or -1 on failure.
263: */
1.65 joris 264: void
1.7 jfb 265: cvs_splitpath(const char *path, char *base, size_t blen, char **file)
1.1 jfb 266: {
267: size_t rlen;
1.7 jfb 268: char *sp;
1.1 jfb 269:
1.7 jfb 270: if ((rlen = strlcpy(base, path, blen)) >= blen)
1.60 xsa 271: fatal("cvs_splitpath: path truncation");
1.6 jfb 272:
1.7 jfb 273: while ((rlen > 0) && (base[rlen - 1] == '/'))
274: base[--rlen] = '\0';
275:
276: sp = strrchr(base, '/');
1.1 jfb 277: if (sp == NULL) {
1.65 joris 278: rlen = strlcpy(base, "./", blen);
279: if (rlen >= blen)
280: fatal("cvs_splitpath: path truncation");
281:
282: rlen = strlcat(base, path, blen);
283: if (rlen >= blen)
284: fatal("cvs_splitpath: path truncation");
285:
1.7 jfb 286: sp = base + 1;
1.1 jfb 287: }
288:
1.7 jfb 289: *sp = '\0';
290: if (file != NULL)
291: *file = sp + 1;
1.1 jfb 292: }
293:
294: /*
295: * cvs_getargv()
296: *
297: * Parse a line contained in <line> and generate an argument vector by
298: * splitting the line on spaces and tabs. The resulting vector is stored in
299: * <argv>, which can accept up to <argvlen> entries.
1.20 david 300: * Returns the number of arguments in the vector, or -1 if an error occurred.
1.1 jfb 301: */
302: int
303: cvs_getargv(const char *line, char **argv, int argvlen)
304: {
1.65 joris 305: size_t l;
1.1 jfb 306: u_int i;
1.67 xsa 307: int argc, error;
1.1 jfb 308: char linebuf[256], qbuf[128], *lp, *cp, *arg;
309:
1.65 joris 310: l = strlcpy(linebuf, line, sizeof(linebuf));
311: if (l >= sizeof(linebuf))
312: fatal("cvs_getargv: string truncation");
313:
1.27 pat 314: memset(argv, 0, argvlen * sizeof(char *));
1.1 jfb 315: argc = 0;
316:
317: /* build the argument vector */
1.67 xsa 318: error = 0;
1.1 jfb 319: for (lp = linebuf; lp != NULL;) {
320: if (*lp == '"') {
321: /* double-quoted string */
322: lp++;
323: i = 0;
324: memset(qbuf, 0, sizeof(qbuf));
325: while (*lp != '"') {
1.16 jfb 326: if (*lp == '\\')
327: lp++;
1.1 jfb 328: if (*lp == '\0') {
329: cvs_log(LP_ERR, "no terminating quote");
1.67 xsa 330: error++;
1.1 jfb 331: break;
1.16 jfb 332: }
1.1 jfb 333:
1.9 jfb 334: qbuf[i++] = *lp++;
1.1 jfb 335: if (i == sizeof(qbuf)) {
1.67 xsa 336: error++;
1.1 jfb 337: break;
338: }
339: }
340:
341: arg = qbuf;
1.15 deraadt 342: } else {
1.1 jfb 343: cp = strsep(&lp, " \t");
344: if (cp == NULL)
345: break;
346: else if (*cp == '\0')
347: continue;
348:
349: arg = cp;
350: }
351:
1.16 jfb 352: if (argc == argvlen) {
1.67 xsa 353: error++;
1.16 jfb 354: break;
355: }
356:
1.59 joris 357: argv[argc] = xstrdup(arg);
1.1 jfb 358: argc++;
359: }
360:
1.67 xsa 361: if (error != 0) {
1.1 jfb 362: /* ditch the argument vector */
363: for (i = 0; i < (u_int)argc; i++)
1.59 joris 364: xfree(argv[i]);
1.1 jfb 365: argc = -1;
366: }
367:
368: return (argc);
1.17 jfb 369: }
370:
371:
372: /*
373: * cvs_makeargv()
374: *
1.20 david 375: * Allocate an argument vector large enough to accommodate for all the
1.17 jfb 376: * arguments found in <line> and return it.
377: */
1.42 xsa 378: char **
1.17 jfb 379: cvs_makeargv(const char *line, int *argc)
380: {
381: int i, ret;
382: char *argv[1024], **copy;
1.27 pat 383: size_t size;
1.17 jfb 384:
385: ret = cvs_getargv(line, argv, 1024);
386: if (ret == -1)
387: return (NULL);
388:
1.27 pat 389: size = (ret + 1) * sizeof(char *);
1.59 joris 390: copy = (char **)xmalloc(size);
1.27 pat 391: memset(copy, 0, size);
1.17 jfb 392:
393: for (i = 0; i < ret; i++)
394: copy[i] = argv[i];
395: copy[ret] = NULL;
396:
397: *argc = ret;
398: return (copy);
1.1 jfb 399: }
400:
401:
402: /*
403: * cvs_freeargv()
404: *
405: * Free an argument vector previously generated by cvs_getargv().
406: */
407: void
408: cvs_freeargv(char **argv, int argc)
409: {
410: int i;
411:
412: for (i = 0; i < argc; i++)
1.16 jfb 413: if (argv[i] != NULL)
1.59 joris 414: xfree(argv[i]);
1.2 jfb 415: }
416:
417:
418: /*
419: * cvs_mkadmin()
420: *
1.5 jfb 421: * Create the CVS administrative files within the directory <cdir>. If the
422: * files already exist, they are kept as is.
1.2 jfb 423: * Returns 0 on success, or -1 on failure.
424: */
425: int
1.52 xsa 426: cvs_mkadmin(const char *dpath, const char *rootpath, const char *repopath,
427: char *tag, char *date, int nb)
1.2 jfb 428: {
1.37 xsa 429: size_t l;
1.28 joris 430: char path[MAXPATHLEN];
1.2 jfb 431: FILE *fp;
432: CVSENTRIES *ef;
1.5 jfb 433: struct stat st;
1.13 jfb 434:
1.52 xsa 435: cvs_log(LP_TRACE, "cvs_mkadmin(%s, %s, %s, %s, %s, %d)",
436: dpath, rootpath, repopath, tag ? tag : "", date ? date : "", nb);
437:
1.37 xsa 438: l = cvs_path_cat(dpath, CVS_PATH_CVSDIR, path, sizeof(path));
439: if (l >= sizeof(path))
1.60 xsa 440: fatal("cvs_mkadmin: path truncation");
1.21 xsa 441:
1.60 xsa 442: if ((mkdir(path, 0755) == -1) && (errno != EEXIST))
443: fatal("cvs_mkadmin: mkdir: `%s': %s", path, strerror(errno));
1.2 jfb 444:
1.5 jfb 445: /* just create an empty Entries file */
1.13 jfb 446: ef = cvs_ent_open(dpath, O_WRONLY);
1.48 moritz 447: if (ef != NULL)
448: cvs_ent_close(ef);
1.2 jfb 449:
1.37 xsa 450: l = cvs_path_cat(dpath, CVS_PATH_ROOTSPEC, path, sizeof(path));
451: if (l >= sizeof(path))
1.60 xsa 452: fatal("cvs_mkadmin: path truncation");
1.21 xsa 453:
1.47 xsa 454: if ((stat(path, &st) == -1) && (errno == ENOENT)) {
1.60 xsa 455: if ((fp = fopen(path, "w")) == NULL)
456: fatal("cvs_mkadmin: fopen: `%s': %s",
457: path, strerror(errno));
458:
1.28 joris 459: if (rootpath != NULL)
460: fprintf(fp, "%s\n", rootpath);
1.5 jfb 461: (void)fclose(fp);
1.2 jfb 462: }
463:
1.37 xsa 464: l = cvs_path_cat(dpath, CVS_PATH_REPOSITORY, path, sizeof(path));
465: if (l >= sizeof(path))
1.60 xsa 466: fatal("cvs_mkadmin: path truncation");
1.21 xsa 467:
1.47 xsa 468: if ((stat(path, &st) == -1) && (errno == ENOENT)) {
1.60 xsa 469: if ((fp = fopen(path, "w")) == NULL)
470: fatal("cvs_mkadmin: fopen: `%s': %s",
471: path, strerror(errno));
472:
1.28 joris 473: if (repopath != NULL)
474: fprintf(fp, "%s\n", repopath);
1.2 jfb 475: (void)fclose(fp);
476: }
477:
1.52 xsa 478: /* create CVS/Tag file (if needed) */
1.56 joris 479: /* XXX correct? */
480: if (tag != NULL || date != NULL)
481: (void)cvs_write_tagfile(tag, date, nb);
1.52 xsa 482:
1.2 jfb 483: return (0);
1.11 krapht 484: }
485:
486:
487: /*
488: * cvs_exec()
489: */
490: int
491: cvs_exec(int argc, char **argv, int fds[3])
492: {
493: int ret;
494: pid_t pid;
495:
496: if ((pid = fork()) == -1) {
497: cvs_log(LP_ERRNO, "failed to fork");
498: return (-1);
499: } else if (pid == 0) {
500: execvp(argv[0], argv);
1.13 jfb 501: cvs_log(LP_ERRNO, "failed to exec %s", argv[0]);
502: exit(1);
1.11 krapht 503: }
504:
505: if (waitpid(pid, &ret, 0) == -1)
1.13 jfb 506: cvs_log(LP_ERRNO, "failed to waitpid");
1.11 krapht 507:
508: return (ret);
1.38 xsa 509: }
510:
511: /*
512: * cvs_chdir()
513: *
1.63 xsa 514: * Change to directory <path>.
515: * If <rm> is equal to `1', <path> is removed if chdir() fails so we
516: * do not have temporary directories leftovers.
1.60 xsa 517: * Returns 0 on success.
1.50 xsa 518: */
1.38 xsa 519: int
1.63 xsa 520: cvs_chdir(const char *path, int rm)
1.38 xsa 521: {
1.63 xsa 522: if (chdir(path) == -1) {
523: if (rm == 1)
524: cvs_unlink(path);
1.60 xsa 525: fatal("cvs_chdir: `%s': %s", path, strerror(errno));
1.63 xsa 526: }
1.49 xsa 527:
528: return (0);
529: }
530:
531: /*
532: * cvs_rename()
533: * Change the name of a file.
534: * rename() wrapper with an error message.
1.60 xsa 535: * Returns 0 on success.
1.49 xsa 536: */
537: int
538: cvs_rename(const char *from, const char *to)
539: {
540: cvs_log(LP_TRACE, "cvs_rename(%s,%s)", from, to);
541:
542: if (cvs_noexec == 1)
543: return (0);
544:
1.60 xsa 545: if (rename(from, to) == -1)
546: fatal("cvs_rename: `%s'->`%s': %s", from, to, strerror(errno));
1.40 xsa 547:
548: return (0);
549: }
550:
551: /*
552: * cvs_unlink()
553: *
554: * Removes the link named by <path>.
555: * unlink() wrapper with an error message.
556: * Returns 0 on success, or -1 on failure.
557: */
558: int
559: cvs_unlink(const char *path)
560: {
561: cvs_log(LP_TRACE, "cvs_unlink(%s)", path);
562:
563: if (cvs_noexec == 1)
564: return (0);
565:
1.44 joris 566: if ((unlink(path) == -1) && (errno != ENOENT)) {
1.40 xsa 567: cvs_log(LP_ERRNO, "cannot remove `%s'", path);
1.38 xsa 568: return (-1);
569: }
570:
571: return (0);
1.1 jfb 572: }
1.24 joris 573:
574: /*
1.45 xsa 575: * cvs_rmdir()
1.30 jfb 576: *
577: * Remove a directory tree from disk.
578: * Returns 0 on success, or -1 on failure.
1.24 joris 579: */
580: int
1.45 xsa 581: cvs_rmdir(const char *path)
1.24 joris 582: {
1.33 pat 583: int ret = -1;
1.30 jfb 584: size_t len;
1.24 joris 585: DIR *dirp;
586: struct dirent *ent;
587: char fpath[MAXPATHLEN];
1.46 xsa 588:
589: cvs_log(LP_TRACE, "cvs_rmdir(%s)", path);
590:
591: if (cvs_noexec == 1)
592: return (0);
1.24 joris 593:
594: if ((dirp = opendir(path)) == NULL) {
595: cvs_log(LP_ERRNO, "failed to open '%s'", path);
1.30 jfb 596: return (-1);
1.24 joris 597: }
598:
599: while ((ent = readdir(dirp)) != NULL) {
600: if (!strcmp(ent->d_name, ".") ||
601: !strcmp(ent->d_name, ".."))
602: continue;
603:
1.30 jfb 604: len = cvs_path_cat(path, ent->d_name, fpath, sizeof(fpath));
1.33 pat 605: if (len >= sizeof(fpath))
1.65 joris 606: fatal("cvs_rmdir: path truncation");
1.24 joris 607:
608: if (ent->d_type == DT_DIR) {
1.45 xsa 609: if (cvs_rmdir(fpath) == -1)
1.33 pat 610: goto done;
1.41 xsa 611: } else if ((cvs_unlink(fpath) == -1) && (errno != ENOENT))
1.33 pat 612: goto done;
1.24 joris 613: }
614:
615:
1.30 jfb 616: if ((rmdir(path) == -1) && (errno != ENOENT)) {
1.24 joris 617: cvs_log(LP_ERRNO, "failed to remove '%s'", path);
1.33 pat 618: goto done;
1.30 jfb 619: }
1.24 joris 620:
1.33 pat 621: ret = 0;
622: done:
623: closedir(dirp);
1.34 joris 624: return (ret);
625: }
626:
627: /*
628: * Create a directory, and the parent directories if needed.
629: * based upon mkpath() from mkdir.c
630: */
631: int
632: cvs_create_dir(const char *path, int create_adm, char *root, char *repo)
633: {
1.68 xsa 634: int ret;
1.34 joris 635: char *d, *s;
636: struct stat sb;
637: char rpath[MAXPATHLEN], entry[MAXPATHLEN];
638: CVSENTRIES *entf;
639: struct cvs_ent *ent;
640:
1.60 xsa 641: if ((create_adm == 1) && (root == NULL))
642: fatal("cvs_create_dir failed");
1.34 joris 643:
1.59 joris 644: s = xstrdup(path);
1.56 joris 645: rpath[0] = '\0';
646: if (repo != NULL) {
1.60 xsa 647: if (strlcpy(rpath, repo, sizeof(rpath)) >= sizeof(rpath))
648: fatal("cvs_create_dir: path truncation");
1.56 joris 649:
1.60 xsa 650: if (strlcat(rpath, "/", sizeof(rpath)) >= sizeof(rpath))
651: fatal("cvs_create_dir: path truncation");
1.34 joris 652: }
653:
654: ret = -1;
655: entf = NULL;
656: d = strtok(s, "/");
657: while (d != NULL) {
658: if (stat(d, &sb)) {
659: /* try to create the directory */
660: if ((errno != ENOENT) || (mkdir(d, 0755) &&
661: errno != EEXIST)) {
662: cvs_log(LP_ERRNO, "failed to create `%s'", d);
663: goto done;
664: }
665: } else if (!S_ISDIR(sb.st_mode)) {
666: cvs_log(LP_ERR, "`%s' not a directory", d);
667: goto done;
668: }
669:
670: /*
671: * Create administrative files if requested.
672: */
1.43 xsa 673: if (create_adm == 1) {
1.68 xsa 674: if (strlcat(rpath, d, sizeof(rpath)) >= sizeof(rpath))
1.65 joris 675: fatal("cvs_create_dir: path truncation");
1.34 joris 676:
1.68 xsa 677: if (strlcat(rpath, "/", sizeof(rpath)) >= sizeof(rpath))
1.65 joris 678: fatal("cvs_create_dir: path truncation");
1.34 joris 679:
1.69 xsa 680: cvs_mkadmin(d, root, rpath, NULL, NULL, 0);
1.34 joris 681: }
682:
683: /*
684: * Add it to the parent directory entry file.
685: * (if any).
686: */
687: entf = cvs_ent_open(".", O_RDWR);
688: if (entf != NULL && strcmp(d, ".")) {
1.68 xsa 689: if (strlcpy(entry, "D/", sizeof(entry)) >=
690: sizeof(entry) ||
691: strlcat(entry, d, sizeof(entry)) >= sizeof(entry) ||
692: strlcat(entry, "////", sizeof(entry)) >=
693: sizeof(entry))
1.65 joris 694: fatal("cvs_create_dir: overflow in entry buf");
1.34 joris 695:
696: if ((ent = cvs_ent_parse(entry)) == NULL) {
697: cvs_log(LP_ERR, "failed to parse entry");
698: goto done;
699: }
700:
1.56 joris 701: cvs_ent_remove(entf, d, 0);
1.34 joris 702:
703: if (cvs_ent_add(entf, ent) < 0) {
704: cvs_log(LP_ERR, "failed to add entry");
705: goto done;
706: }
707: }
708:
709: if (entf != NULL) {
710: cvs_ent_close(entf);
711: entf = NULL;
712: }
713:
1.61 xsa 714: /* All went ok, switch to the newly created directory. */
1.63 xsa 715: cvs_chdir(d, 0);
1.34 joris 716:
717: d = strtok(NULL, "/");
718: }
719:
720: ret = 0;
721: done:
722: if (entf != NULL)
723: cvs_ent_close(entf);
1.59 joris 724: xfree(s);
1.33 pat 725: return (ret);
1.24 joris 726: }
727:
1.30 jfb 728: /*
729: * cvs_path_cat()
730: *
731: * Concatenate the two paths <base> and <end> and store the generated path
732: * into the buffer <dst>, which can accept up to <dlen> bytes, including the
733: * NUL byte. The result is guaranteed to be NUL-terminated.
734: * Returns the number of bytes necessary to store the full resulting path,
735: * not including the NUL byte (a value equal to or larger than <dlen>
736: * indicates truncation).
737: */
1.29 jfb 738: size_t
739: cvs_path_cat(const char *base, const char *end, char *dst, size_t dlen)
740: {
741: size_t len;
742:
743: len = strlcpy(dst, base, dlen);
744: if (len >= dlen - 1) {
745: errno = ENAMETOOLONG;
746: cvs_log(LP_ERRNO, "%s", dst);
747: } else {
748: dst[len] = '/';
749: dst[len + 1] = '\0';
750: len = strlcat(dst, end, dlen);
751: if (len >= dlen) {
752: errno = ENAMETOOLONG;
753: cvs_log(LP_ERRNO, "%s", dst);
754: }
755: }
756:
757: return (len);
1.35 xsa 758: }
759:
760: /*
761: * cvs_rcs_getpath()
762: *
763: * Get the RCS path of the file <file> and store it in <buf>, which is
764: * of size <len>. For portability, it is recommended that <buf> always be
765: * at least MAXPATHLEN bytes long.
766: * Returns a pointer to the start of the path on success, or NULL on failure.
767: */
1.42 xsa 768: char *
1.35 xsa 769: cvs_rcs_getpath(CVSFILE *file, char *buf, size_t len)
770: {
771: char *repo;
772: struct cvsroot *root;
773:
774: root = CVS_DIR_ROOT(file);
775: repo = CVS_DIR_REPO(file);
776:
1.68 xsa 777: if (strlcpy(buf, root->cr_dir, len) >= len ||
778: strlcat(buf, "/", len) >= len ||
779: strlcat(buf, repo, len) >= len ||
780: strlcat(buf, "/", len) >= len ||
781: strlcat(buf, file->cf_name, len) >= len ||
782: strlcat(buf, RCS_FILE_EXT, len) >= len)
1.60 xsa 783: fatal("cvs_rcs_getpath: path truncation");
1.35 xsa 784:
785: return (buf);
1.51 xsa 786: }
787:
788: /*
789: * cvs_write_tagfile()
790: *
791: * Write the CVS/Tag file for current directory.
1.55 xsa 792: */
1.51 xsa 793: void
794: cvs_write_tagfile(char *tag, char *date, int nb)
795: {
796: FILE *fp;
797: char tagpath[MAXPATHLEN];
798:
799: if (cvs_noexec == 1)
800: return;
801:
802: if (strlcpy(tagpath, CVS_PATH_TAG, sizeof(tagpath)) >= sizeof(tagpath))
803: return;
804:
805: if ((tag != NULL) || (date != NULL)) {
806: fp = fopen(tagpath, "w+");
807: if (fp == NULL) {
808: if (errno != ENOENT)
809: cvs_log(LP_NOTICE,
810: "failed to open `%s' : %s", tagpath,
811: strerror(errno));
812: return;
813: }
814: if (tag != NULL) {
815: if (nb != 0)
816: fprintf(fp, "N%s\n", tag);
817: else
818: fprintf(fp, "T%s\n", tag);
819: } else {
820: fprintf(fp, "D%s\n", date);
821: }
822: (void)fclose(fp);
823: } else {
824: cvs_unlink(tagpath);
825: return;
826: }
827: }
828:
829: /*
830: * cvs_parse_tagfile()
831: *
832: * Parse the CVS/Tag file for current directory.
833: *
834: * If it contains a branch tag, sets <tagp>.
835: * If it contains a date, sets <datep>.
836: * If it contains a non-branch tag, sets <nbp>.
1.55 xsa 837: *
1.51 xsa 838: * Returns nothing but an error message, and sets <tagp>, <datep> to NULL
839: * and <nbp> to 0.
840: */
841: void
842: cvs_parse_tagfile(char **tagp, char **datep, int *nbp)
843: {
844: FILE *fp;
845: int linenum;
846: size_t len;
847: char linebuf[128], tagpath[MAXPATHLEN];
848:
849: if (tagp != NULL)
850: *tagp = (char *)NULL;
851:
852: if (datep != NULL)
853: *datep = (char *)NULL;
854:
855: if (nbp != NULL)
856: *nbp = 0;
857:
858: if (strlcpy(tagpath, CVS_PATH_TAG, sizeof(tagpath)) >= sizeof(tagpath))
859: return;
860:
861: fp = fopen(tagpath, "r");
862: if (fp == NULL) {
863: if (errno != ENOENT)
864: cvs_log(LP_NOTICE, "failed to open `%s' : %s", tagpath,
865: strerror(errno));
866: return;
867: }
868:
869: linenum = 0;
870:
871: while (fgets(linebuf, (int)sizeof(linebuf), fp) != NULL) {
872: linenum++;
873: if ((len = strlen(linebuf)) == 0)
874: continue;
875: if (linebuf[len -1] != '\n') {
876: cvs_log(LP_WARN, "line too long in `%s:%d'", tagpath,
877: linenum);
878: break;
879: }
880: linebuf[--len] = '\0';
881:
1.53 reyk 882: switch (*linebuf) {
1.51 xsa 883: case 'T':
884: if (tagp != NULL)
1.59 joris 885: *tagp = xstrdup(linebuf);
1.51 xsa 886: break;
887: case 'D':
888: if (datep != NULL)
1.59 joris 889: *datep = xstrdup(linebuf);
1.51 xsa 890: break;
891: case 'N':
892: if (tagp != NULL)
1.59 joris 893: *tagp = xstrdup(linebuf);
1.51 xsa 894: if (nbp != NULL)
895: *nbp = 1;
896: break;
897: default:
898: break;
899: }
900: }
901: if (ferror(fp))
902: cvs_log(LP_NOTICE, "failed to read line from `%s'", tagpath);
903:
904: (void)fclose(fp);
1.54 joris 905: }
906:
1.72 niallo 907: /*
908: * a hack to mimic and thus match gnu cvs behaviour.
909: */
910: time_t
911: cvs_hack_time(time_t oldtime, int togmt)
912: {
913: int l;
914: struct tm *t;
915: char tbuf[32];
916:
917: if (togmt == 1) {
918: t = gmtime(&oldtime);
919: if (t == NULL)
920: return (0);
921:
922: return (mktime(t));
923: }
924:
925: t = localtime(&oldtime);
926:
927: l = snprintf(tbuf, sizeof(tbuf), "%d/%d/%d GMT %d:%d:%d",
928: t->tm_mon + 1, t->tm_mday, t->tm_year + 1900, t->tm_hour,
929: t->tm_min, t->tm_sec);
930: if (l == -1 || l >= (int)sizeof(tbuf))
931: return (0);
932:
933: return (cvs_date_parse(tbuf));
934: }
935:
1.54 joris 936: #endif /* !RCSPROG */
937:
938: /*
939: * Split the contents of a file into a list of lines.
940: */
941: struct cvs_lines *
942: cvs_splitlines(const char *fcont)
943: {
944: char *dcp;
945: struct cvs_lines *lines;
946: struct cvs_line *lp;
947:
1.59 joris 948: lines = (struct cvs_lines *)xmalloc(sizeof(*lines));
1.54 joris 949: TAILQ_INIT(&(lines->l_lines));
950: lines->l_nblines = 0;
1.59 joris 951: lines->l_data = xstrdup(fcont);
1.54 joris 952:
1.59 joris 953: lp = (struct cvs_line *)xmalloc(sizeof(*lp));
1.54 joris 954: lp->l_line = NULL;
955: lp->l_lineno = 0;
956: TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list);
957:
958: for (dcp = lines->l_data; *dcp != '\0';) {
1.59 joris 959: lp = (struct cvs_line *)xmalloc(sizeof(*lp));
1.54 joris 960: lp->l_line = dcp;
961: lp->l_lineno = ++(lines->l_nblines);
962: TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list);
963:
964: dcp = strchr(dcp, '\n');
965: if (dcp == NULL)
966: break;
967: *(dcp++) = '\0';
968: }
969:
970: return (lines);
971: }
972:
973: void
974: cvs_freelines(struct cvs_lines *lines)
975: {
976: struct cvs_line *lp;
977:
978: while ((lp = TAILQ_FIRST(&(lines->l_lines))) != NULL) {
979: TAILQ_REMOVE(&(lines->l_lines), lp, l_list);
1.59 joris 980: xfree(lp);
1.54 joris 981: }
982:
1.59 joris 983: xfree(lines->l_data);
984: xfree(lines);
1.54 joris 985: }
986:
987: BUF *
988: cvs_patchfile(const char *data, const char *patch,
989: int (*p)(struct cvs_lines *, struct cvs_lines *))
990: {
991: struct cvs_lines *dlines, *plines;
992: struct cvs_line *lp;
993: size_t len;
994: int lineno;
995: BUF *res;
996:
997: len = strlen(data);
998:
999: if ((dlines = cvs_splitlines(data)) == NULL)
1000: return (NULL);
1001:
1002: if ((plines = cvs_splitlines(patch)) == NULL)
1003: return (NULL);
1004:
1005: if (p(dlines, plines) < 0) {
1006: cvs_freelines(dlines);
1007: cvs_freelines(plines);
1008: return (NULL);
1009: }
1010:
1011: lineno = 0;
1.62 joris 1012: res = cvs_buf_alloc(len, BUF_AUTOEXT);
1.54 joris 1013: TAILQ_FOREACH(lp, &dlines->l_lines, l_list) {
1014: if (lineno != 0)
1015: cvs_buf_fappend(res, "%s\n", lp->l_line);
1016: lineno++;
1017: }
1018:
1019: cvs_freelines(dlines);
1020: cvs_freelines(plines);
1021: return (res);
1.29 jfb 1022: }
1.70 niallo 1023:
1024: /*
1025: * cvs_yesno()
1026: *
1027: * Read from standart input for `y' or `Y' character.
1028: * Returns 0 on success, or -1 on failure.
1029: */
1030: int
1031: cvs_yesno(void)
1032: {
1033: int c, ret;
1034:
1035: ret = 0;
1036:
1037: fflush(stderr);
1038: fflush(stdout);
1039:
1040: if ((c = getchar()) != 'y' && c != 'Y')
1041: ret = -1;
1042: else
1043: while (c != EOF && c != '\n')
1044: c = getchar();
1045:
1046: return (ret);
1047: }
1048:
1.71 xsa 1049: /*
1050: * cvs_strsplit()
1051: *
1052: * Split a string <str> of <sep>-separated values and allocate
1053: * an argument vector for the values found.
1054: */
1.73 pat 1055: struct cvs_argvector *
1.71 xsa 1056: cvs_strsplit(char *str, const char *sep)
1057: {
1.73 pat 1058: struct cvs_argvector *av;
1059: char **nargv;
1.71 xsa 1060: char *cp, *p;
1061: int i = 0;
1062:
1063: cp = xstrdup(str);
1.73 pat 1064: av = xmalloc(sizeof(struct cvs_argvector));
1065: av->str = cp;
1.75 ! ray 1066: av->argv = (char **)xcalloc(i + 1, sizeof(char *));
1.71 xsa 1067:
1068: while ((p = strsep(&cp, sep)) != NULL) {
1.73 pat 1069: av->argv[i++] = p;
1070: nargv = (char **)xrealloc((void *)av->argv,
1.74 ray 1071: i + 1, sizeof(char *));
1.73 pat 1072: av->argv = nargv;
1.71 xsa 1073: }
1.73 pat 1074: av->argv[i] = NULL;
1075:
1076: return (av);
1077: }
1.71 xsa 1078:
1.73 pat 1079: /*
1080: * cvs_argv_destroy()
1081: *
1082: * Free an argument vector previously allocated by cvs_strsplit().
1083: */
1084: void
1085: cvs_argv_destroy(struct cvs_argvector *av)
1086: {
1087: xfree(av->str);
1088: xfree(av->argv);
1089: xfree(av);
1.71 xsa 1090: }