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