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