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