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