Annotation of src/usr.bin/cvs/util.c, Revision 1.62
1.62 ! joris 1: /* $OpenBSD: util.c,v 1.61 2005/12/22 14:31:44 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:
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: *
498: * Change to directory.
499: * chdir() wrapper with an error message.
1.60 xsa 500: * Returns 0 on success.
1.50 xsa 501: */
1.38 xsa 502: int
503: cvs_chdir(const char *path)
504: {
1.60 xsa 505: if (chdir(path) == -1)
506: fatal("cvs_chdir: `%s': %s", path, strerror(errno));
1.49 xsa 507:
508: return (0);
509: }
510:
511: /*
512: * cvs_rename()
513: * Change the name of a file.
514: * rename() wrapper with an error message.
1.60 xsa 515: * Returns 0 on success.
1.49 xsa 516: */
517: int
518: cvs_rename(const char *from, const char *to)
519: {
520: cvs_log(LP_TRACE, "cvs_rename(%s,%s)", from, to);
521:
522: if (cvs_noexec == 1)
523: return (0);
524:
1.60 xsa 525: if (rename(from, to) == -1)
526: fatal("cvs_rename: `%s'->`%s': %s", from, to, strerror(errno));
1.40 xsa 527:
528: return (0);
529: }
530:
531: /*
532: * cvs_unlink()
533: *
534: * Removes the link named by <path>.
535: * unlink() wrapper with an error message.
536: * Returns 0 on success, or -1 on failure.
537: */
538: int
539: cvs_unlink(const char *path)
540: {
541: cvs_log(LP_TRACE, "cvs_unlink(%s)", path);
542:
543: if (cvs_noexec == 1)
544: return (0);
545:
1.44 joris 546: if ((unlink(path) == -1) && (errno != ENOENT)) {
1.40 xsa 547: cvs_log(LP_ERRNO, "cannot remove `%s'", path);
1.38 xsa 548: return (-1);
549: }
550:
551: return (0);
1.1 jfb 552: }
1.24 joris 553:
554: /*
1.45 xsa 555: * cvs_rmdir()
1.30 jfb 556: *
557: * Remove a directory tree from disk.
558: * Returns 0 on success, or -1 on failure.
1.24 joris 559: */
560: int
1.45 xsa 561: cvs_rmdir(const char *path)
1.24 joris 562: {
1.33 pat 563: int ret = -1;
1.30 jfb 564: size_t len;
1.24 joris 565: DIR *dirp;
566: struct dirent *ent;
567: char fpath[MAXPATHLEN];
1.46 xsa 568:
569: cvs_log(LP_TRACE, "cvs_rmdir(%s)", path);
570:
571: if (cvs_noexec == 1)
572: return (0);
1.24 joris 573:
574: if ((dirp = opendir(path)) == NULL) {
575: cvs_log(LP_ERRNO, "failed to open '%s'", path);
1.30 jfb 576: return (-1);
1.24 joris 577: }
578:
579: while ((ent = readdir(dirp)) != NULL) {
580: if (!strcmp(ent->d_name, ".") ||
581: !strcmp(ent->d_name, ".."))
582: continue;
583:
1.30 jfb 584: len = cvs_path_cat(path, ent->d_name, fpath, sizeof(fpath));
1.33 pat 585: if (len >= sizeof(fpath))
586: goto done;
1.24 joris 587:
588: if (ent->d_type == DT_DIR) {
1.45 xsa 589: if (cvs_rmdir(fpath) == -1)
1.33 pat 590: goto done;
1.41 xsa 591: } else if ((cvs_unlink(fpath) == -1) && (errno != ENOENT))
1.33 pat 592: goto done;
1.24 joris 593: }
594:
595:
1.30 jfb 596: if ((rmdir(path) == -1) && (errno != ENOENT)) {
1.24 joris 597: cvs_log(LP_ERRNO, "failed to remove '%s'", path);
1.33 pat 598: goto done;
1.30 jfb 599: }
1.24 joris 600:
1.33 pat 601: ret = 0;
602: done:
603: closedir(dirp);
1.34 joris 604: return (ret);
605: }
606:
607: /*
608: * Create a directory, and the parent directories if needed.
609: * based upon mkpath() from mkdir.c
610: */
611: int
612: cvs_create_dir(const char *path, int create_adm, char *root, char *repo)
613: {
614: size_t l;
615: int len, ret;
616: char *d, *s;
617: struct stat sb;
618: char rpath[MAXPATHLEN], entry[MAXPATHLEN];
619: CVSENTRIES *entf;
620: struct cvs_ent *ent;
621:
1.60 xsa 622: if ((create_adm == 1) && (root == NULL))
623: fatal("cvs_create_dir failed");
1.34 joris 624:
1.59 joris 625: s = xstrdup(path);
1.56 joris 626: rpath[0] = '\0';
627: if (repo != NULL) {
1.60 xsa 628: if (strlcpy(rpath, repo, sizeof(rpath)) >= sizeof(rpath))
629: fatal("cvs_create_dir: path truncation");
1.56 joris 630:
1.60 xsa 631: if (strlcat(rpath, "/", sizeof(rpath)) >= sizeof(rpath))
632: fatal("cvs_create_dir: path truncation");
1.34 joris 633: }
634:
635: ret = -1;
636: entf = NULL;
637: d = strtok(s, "/");
638: while (d != NULL) {
639: if (stat(d, &sb)) {
640: /* try to create the directory */
641: if ((errno != ENOENT) || (mkdir(d, 0755) &&
642: errno != EEXIST)) {
643: cvs_log(LP_ERRNO, "failed to create `%s'", d);
644: goto done;
645: }
646: } else if (!S_ISDIR(sb.st_mode)) {
647: cvs_log(LP_ERR, "`%s' not a directory", d);
648: goto done;
649: }
650:
651: /*
652: * Create administrative files if requested.
653: */
1.43 xsa 654: if (create_adm == 1) {
1.34 joris 655: l = strlcat(rpath, d, sizeof(rpath));
656: if (l >= sizeof(rpath))
657: goto done;
658:
659: l = strlcat(rpath, "/", sizeof(rpath));
660: if (l >= sizeof(rpath))
661: goto done;
662:
1.52 xsa 663: if (cvs_mkadmin(d, root, rpath, NULL, NULL, 0) < 0) {
1.34 joris 664: cvs_log(LP_ERR, "failed to create adm files");
665: goto done;
666: }
667: }
668:
669: /*
670: * Add it to the parent directory entry file.
671: * (if any).
672: */
673: entf = cvs_ent_open(".", O_RDWR);
674: if (entf != NULL && strcmp(d, ".")) {
675: len = snprintf(entry, sizeof(entry), "D/%s////", d);
676: if (len == -1 || len >= (int)sizeof(entry)) {
677: errno = ENAMETOOLONG;
678: cvs_log(LP_ERRNO, "%s", entry);
679: goto done;
680: }
681:
682: if ((ent = cvs_ent_parse(entry)) == NULL) {
683: cvs_log(LP_ERR, "failed to parse entry");
684: goto done;
685: }
686:
1.56 joris 687: cvs_ent_remove(entf, d, 0);
1.34 joris 688:
689: if (cvs_ent_add(entf, ent) < 0) {
690: cvs_log(LP_ERR, "failed to add entry");
691: goto done;
692: }
693: }
694:
695: if (entf != NULL) {
696: cvs_ent_close(entf);
697: entf = NULL;
698: }
699:
1.61 xsa 700: /* All went ok, switch to the newly created directory. */
701: cvs_chdir(d);
1.34 joris 702:
703: d = strtok(NULL, "/");
704: }
705:
706: ret = 0;
707: done:
708: if (entf != NULL)
709: cvs_ent_close(entf);
1.59 joris 710: xfree(s);
1.33 pat 711: return (ret);
1.24 joris 712: }
713:
1.30 jfb 714: /*
715: * cvs_path_cat()
716: *
717: * Concatenate the two paths <base> and <end> and store the generated path
718: * into the buffer <dst>, which can accept up to <dlen> bytes, including the
719: * NUL byte. The result is guaranteed to be NUL-terminated.
720: * Returns the number of bytes necessary to store the full resulting path,
721: * not including the NUL byte (a value equal to or larger than <dlen>
722: * indicates truncation).
723: */
1.29 jfb 724: size_t
725: cvs_path_cat(const char *base, const char *end, char *dst, size_t dlen)
726: {
727: size_t len;
728:
729: len = strlcpy(dst, base, dlen);
730: if (len >= dlen - 1) {
731: errno = ENAMETOOLONG;
732: cvs_log(LP_ERRNO, "%s", dst);
733: } else {
734: dst[len] = '/';
735: dst[len + 1] = '\0';
736: len = strlcat(dst, end, dlen);
737: if (len >= dlen) {
738: errno = ENAMETOOLONG;
739: cvs_log(LP_ERRNO, "%s", dst);
740: }
741: }
742:
743: return (len);
1.35 xsa 744: }
745:
746: /*
747: * cvs_rcs_getpath()
748: *
749: * Get the RCS path of the file <file> and store it in <buf>, which is
750: * of size <len>. For portability, it is recommended that <buf> always be
751: * at least MAXPATHLEN bytes long.
752: * Returns a pointer to the start of the path on success, or NULL on failure.
753: */
1.42 xsa 754: char *
1.35 xsa 755: cvs_rcs_getpath(CVSFILE *file, char *buf, size_t len)
756: {
757: int l;
758: char *repo;
759: struct cvsroot *root;
760:
761: root = CVS_DIR_ROOT(file);
762: repo = CVS_DIR_REPO(file);
763:
1.36 deraadt 764: l = snprintf(buf, len, "%s/%s/%s%s",
1.35 xsa 765: root->cr_dir, repo, file->cf_name, RCS_FILE_EXT);
1.60 xsa 766: if (l == -1 || l >= (int)len)
767: fatal("cvs_rcs_getpath: path truncation");
1.35 xsa 768:
769: return (buf);
1.51 xsa 770: }
771:
772: /*
773: * cvs_write_tagfile()
774: *
775: * Write the CVS/Tag file for current directory.
1.55 xsa 776: */
1.51 xsa 777: void
778: cvs_write_tagfile(char *tag, char *date, int nb)
779: {
780: FILE *fp;
781: char tagpath[MAXPATHLEN];
782:
783: if (cvs_noexec == 1)
784: return;
785:
786: if (strlcpy(tagpath, CVS_PATH_TAG, sizeof(tagpath)) >= sizeof(tagpath))
787: return;
788:
789: if ((tag != NULL) || (date != NULL)) {
790: fp = fopen(tagpath, "w+");
791: if (fp == NULL) {
792: if (errno != ENOENT)
793: cvs_log(LP_NOTICE,
794: "failed to open `%s' : %s", tagpath,
795: strerror(errno));
796: return;
797: }
798: if (tag != NULL) {
799: if (nb != 0)
800: fprintf(fp, "N%s\n", tag);
801: else
802: fprintf(fp, "T%s\n", tag);
803: } else {
804: fprintf(fp, "D%s\n", date);
805: }
806: (void)fclose(fp);
807: } else {
808: cvs_unlink(tagpath);
809: return;
810: }
811: }
812:
813: /*
814: * cvs_parse_tagfile()
815: *
816: * Parse the CVS/Tag file for current directory.
817: *
818: * If it contains a branch tag, sets <tagp>.
819: * If it contains a date, sets <datep>.
820: * If it contains a non-branch tag, sets <nbp>.
1.55 xsa 821: *
1.51 xsa 822: * Returns nothing but an error message, and sets <tagp>, <datep> to NULL
823: * and <nbp> to 0.
824: */
825: void
826: cvs_parse_tagfile(char **tagp, char **datep, int *nbp)
827: {
828: FILE *fp;
829: int linenum;
830: size_t len;
831: char linebuf[128], tagpath[MAXPATHLEN];
832:
833: if (tagp != NULL)
834: *tagp = (char *)NULL;
835:
836: if (datep != NULL)
837: *datep = (char *)NULL;
838:
839: if (nbp != NULL)
840: *nbp = 0;
841:
842: if (strlcpy(tagpath, CVS_PATH_TAG, sizeof(tagpath)) >= sizeof(tagpath))
843: return;
844:
845: fp = fopen(tagpath, "r");
846: if (fp == NULL) {
847: if (errno != ENOENT)
848: cvs_log(LP_NOTICE, "failed to open `%s' : %s", tagpath,
849: strerror(errno));
850: return;
851: }
852:
853: linenum = 0;
854:
855: while (fgets(linebuf, (int)sizeof(linebuf), fp) != NULL) {
856: linenum++;
857: if ((len = strlen(linebuf)) == 0)
858: continue;
859: if (linebuf[len -1] != '\n') {
860: cvs_log(LP_WARN, "line too long in `%s:%d'", tagpath,
861: linenum);
862: break;
863: }
864: linebuf[--len] = '\0';
865:
1.53 reyk 866: switch (*linebuf) {
1.51 xsa 867: case 'T':
868: if (tagp != NULL)
1.59 joris 869: *tagp = xstrdup(linebuf);
1.51 xsa 870: break;
871: case 'D':
872: if (datep != NULL)
1.59 joris 873: *datep = xstrdup(linebuf);
1.51 xsa 874: break;
875: case 'N':
876: if (tagp != NULL)
1.59 joris 877: *tagp = xstrdup(linebuf);
1.51 xsa 878: if (nbp != NULL)
879: *nbp = 1;
880: break;
881: default:
882: break;
883: }
884: }
885: if (ferror(fp))
886: cvs_log(LP_NOTICE, "failed to read line from `%s'", tagpath);
887:
888: (void)fclose(fp);
1.54 joris 889: }
890:
891: #endif /* !RCSPROG */
892:
893: /*
894: * Split the contents of a file into a list of lines.
895: */
896: struct cvs_lines *
897: cvs_splitlines(const char *fcont)
898: {
899: char *dcp;
900: struct cvs_lines *lines;
901: struct cvs_line *lp;
902:
1.59 joris 903: lines = (struct cvs_lines *)xmalloc(sizeof(*lines));
1.54 joris 904: TAILQ_INIT(&(lines->l_lines));
905: lines->l_nblines = 0;
1.59 joris 906: lines->l_data = xstrdup(fcont);
1.54 joris 907:
1.59 joris 908: lp = (struct cvs_line *)xmalloc(sizeof(*lp));
1.54 joris 909: lp->l_line = NULL;
910: lp->l_lineno = 0;
911: TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list);
912:
913: for (dcp = lines->l_data; *dcp != '\0';) {
1.59 joris 914: lp = (struct cvs_line *)xmalloc(sizeof(*lp));
1.54 joris 915: lp->l_line = dcp;
916: lp->l_lineno = ++(lines->l_nblines);
917: TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list);
918:
919: dcp = strchr(dcp, '\n');
920: if (dcp == NULL)
921: break;
922: *(dcp++) = '\0';
923: }
924:
925: return (lines);
926: }
927:
928: void
929: cvs_freelines(struct cvs_lines *lines)
930: {
931: struct cvs_line *lp;
932:
933: while ((lp = TAILQ_FIRST(&(lines->l_lines))) != NULL) {
934: TAILQ_REMOVE(&(lines->l_lines), lp, l_list);
1.59 joris 935: xfree(lp);
1.54 joris 936: }
937:
1.59 joris 938: xfree(lines->l_data);
939: xfree(lines);
1.54 joris 940: }
941:
942: BUF *
943: cvs_patchfile(const char *data, const char *patch,
944: int (*p)(struct cvs_lines *, struct cvs_lines *))
945: {
946: struct cvs_lines *dlines, *plines;
947: struct cvs_line *lp;
948: size_t len;
949: int lineno;
950: BUF *res;
951:
952: len = strlen(data);
953:
954: if ((dlines = cvs_splitlines(data)) == NULL)
955: return (NULL);
956:
957: if ((plines = cvs_splitlines(patch)) == NULL)
958: return (NULL);
959:
960: if (p(dlines, plines) < 0) {
961: cvs_freelines(dlines);
962: cvs_freelines(plines);
963: return (NULL);
964: }
965:
966: lineno = 0;
1.62 ! joris 967: res = cvs_buf_alloc(len, BUF_AUTOEXT);
1.54 joris 968: TAILQ_FOREACH(lp, &dlines->l_lines, l_list) {
969: if (lineno != 0)
970: cvs_buf_fappend(res, "%s\n", lp->l_line);
971: lineno++;
972: }
973:
974: cvs_freelines(dlines);
975: cvs_freelines(plines);
976: return (res);
1.58 joris 977: }
978:
979: /*
980: * a hack to mimic and thus match gnu cvs behaviour.
981: */
982: time_t
983: cvs_hack_time(time_t oldtime, int togmt)
984: {
985: int l;
986: struct tm *t;
987: char tbuf[32];
988:
989: if (togmt == 1) {
990: t = gmtime(&oldtime);
991: if (t == NULL)
992: return (0);
993:
994: return (mktime(t));
995: }
996:
997: t = localtime(&oldtime);
998:
999: l = snprintf(tbuf, sizeof(tbuf), "%d/%d/%d GMT %d:%d:%d",
1000: t->tm_mon + 1, t->tm_mday, t->tm_year + 1900, t->tm_hour,
1001: t->tm_min, t->tm_sec);
1002: if (l == -1 || l >= (int)sizeof(tbuf))
1003: return (0);
1004:
1005: return (cvs_date_parse(tbuf));
1.29 jfb 1006: }