Annotation of src/usr.bin/cvs/repo.c, Revision 1.5
1.5 ! joris 1: /* $OpenBSD: repo.c,v 1.4 2005/05/31 08:58:48 xsa Exp $ */
1.1 jfb 2: /*
3: * Copyright (c) 2005 Jean-Francois Brousseau <jfb@openbsd.org>
4: * All rights reserved.
5: *
6: * Redistribution and use in source and binary forms, with or without
7: * modification, are permitted provided that the following conditions
8: * are met:
9: *
10: * 1. Redistributions of source code must retain the above copyright
11: * notice, this list of conditions and the following disclaimer.
12: * 2. The name of the author may not be used to endorse or promote products
13: * derived from this software without specific prior written permission.
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
24: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25: */
26:
27: #include <sys/param.h>
28: #include <sys/queue.h>
29: #include <sys/time.h>
30: #include <sys/stat.h>
31:
32: #include <errno.h>
1.4 xsa 33: #include <dirent.h>
34: #include <fcntl.h>
35: #include <libgen.h>
1.1 jfb 36: #include <stdio.h>
37: #include <stdlib.h>
1.4 xsa 38: #include <string.h>
1.1 jfb 39: #include <unistd.h>
40:
41: #include "log.h"
42: #include "repo.h"
43:
44:
45:
46: static CVSRPENT* cvs_repo_loadrec (CVSREPO *, const char *);
47:
48:
49: /*
50: * cvs_repo_load()
51: *
52: * Load the information for a specific CVS repository whose base directory
53: * is specified in <base>.
54: */
55:
56: CVSREPO*
57: cvs_repo_load(const char *base, int flags)
58: {
59: struct stat st;
60: CVSREPO *repo;
61:
62: cvs_log(LP_DEBUG, "loading repository %s", base);
63:
64: if (stat(base, &st) == -1) {
65: cvs_log(LP_ERRNO, "failed to stat %s", base);
66: return (NULL);
67: }
68:
69: if (!S_ISDIR(st.st_mode)) {
70: cvs_log(LP_ERR, "%s: repository path is not a directory", base);
71: return (NULL);
72: }
73:
74: repo = (struct cvs_repo *)malloc(sizeof(*repo));
75: if (repo == NULL) {
76: cvs_log(LP_ERRNO, "failed to allocate repository data");
77: return (NULL);
78: }
79: memset(repo, 0, sizeof(*repo));
80:
81: TAILQ_INIT(&(repo->cr_modules));
82:
83: repo->cr_path = strdup(base);
84: if (repo->cr_path == NULL) {
85: cvs_log(LP_ERRNO, "failed to copy repository path");
86: free(repo);
87: return (NULL);
88: }
89:
90: repo->cr_tree = cvs_repo_loadrec(repo, repo->cr_path);
91: if (repo->cr_tree == NULL) {
92: cvs_repo_free(repo);
93: return (NULL);
94: }
95:
96: return (repo);
97: }
98:
99:
100: /*
101: * cvs_repo_free()
102: *
103: * Free the data associated to a repository.
104: */
105:
106: void
107: cvs_repo_free(CVSREPO *repo)
108: {
109: CVSMODULE *mod;
110:
111: if (repo != NULL) {
112: if (repo->cr_path != NULL)
113: free(repo->cr_path);
114:
115: while ((mod = TAILQ_FIRST(&(repo->cr_modules))) != NULL) {
116: TAILQ_REMOVE(&(repo->cr_modules), mod, cm_link);
117: cvs_repo_modfree(mod);
118: }
119:
120: if (repo->cr_tree != NULL)
121: cvs_repo_entfree(repo->cr_tree);
122:
123: free(repo);
124: }
125: }
126:
127:
128: /*
129: * cvs_repo_lockdir()
130: *
131: * Obtain a lock on the directory <dir> which is relative to the root of
132: * the repository <repo>. The owner of the lock becomes <pid>.
133: * Returns 0 on success, or -1 on failure.
134: */
135: int
136: cvs_repo_lockdir(CVSREPO *repo, const char *dir, int type, pid_t owner)
137: {
138: CVSRPENT *ent;
139:
140: if ((ent = cvs_repo_find(repo, dir)) == NULL) {
141: return (-1);
142: }
143:
144: return cvs_repo_lockent(ent, type, owner);
145: }
146:
147:
148: /*
149: * cvs_repo_unlockdir()
150: *
151: * Attempt to unlock the directory <dir> in the repository <repo>. The <owner>
152: * argument is used to make sure that the caller really owns the lock it is
153: * trying to release.
154: * Returns 0 on success, or -1 on failure.
155: */
156: int
157: cvs_repo_unlockdir(CVSREPO *repo, const char *dir, pid_t owner)
158: {
159: CVSRPENT *ent;
160:
161: if ((ent = cvs_repo_find(repo, dir)) == NULL) {
162: return (-1);
163: }
164:
165: return cvs_repo_unlockent(ent, owner);
166: }
167:
168:
169: /*
170: * cvs_repo_lockent()
171: *
172: * Obtain a lock on the entry <ent>. The owner of the lock becomes <pid>.
173: * Returns 0 on success, or -1 on failure.
174: */
175: int
176: cvs_repo_lockent(CVSRPENT *ent, int type, pid_t owner)
177: {
178: struct cvs_lock *lk;
179: struct cvs_lklist *list;
180:
181: if ((type != CVS_LOCK_READ) && (type != CVS_LOCK_WRITE)) {
182: cvs_log(LP_ERR, "invalid lock type (%d) requested");
183: return (-1);
184: }
185:
186: lk = (struct cvs_lock *)malloc(sizeof(*lk));
187: if (lk == NULL) {
188: cvs_log(LP_ERRNO, "failed to allocate repository lock");
189: return (-1);
190: }
191: lk->lk_owner = owner;
192: lk->lk_type = type;
193: lk->lk_ent = ent;
194:
195: if ((ent->cr_wlock != NULL) && (ent->cr_wlock->lk_owner != 0)) {
196: /*
197: * Another process has already locked the entry with a write
198: * lock, so regardless of the type of lock we are requesting,
199: * we'll have to wait in the pending requests queue.
1.5 ! joris 200: */
1.1 jfb 201: if (ent->cr_wlock->lk_owner == owner) {
202: cvs_log(LP_WARN, "double-lock attempt");
203: free(lk);
204: } else
205: TAILQ_INSERT_TAIL(&(ent->cr_lkreq), lk, lk_link);
206: } else {
207: if (type == CVS_LOCK_READ) {
208: /*
209: * If there are any pending write lock requests,
210: * add the read lock request at the tail of the queue
211: * instead of assigning it right away. Otherwise,
212: * we could end up with a write lock request never
213: * being obtained if other processes make overlapping
214: * read lock requests.
215: */
216: if (TAILQ_EMPTY(&(ent->cr_lkreq)))
217: list = &(ent->cr_rlocks);
218: else
219: list = &(ent->cr_lkreq);
220: TAILQ_INSERT_TAIL(list, lk, lk_link);
221: } else if (type == CVS_LOCK_WRITE) {
222: if (TAILQ_EMPTY(&(ent->cr_rlocks)))
223: ent->cr_wlock = lk;
224: else
225: TAILQ_INSERT_TAIL(&(ent->cr_lkreq), lk, lk_link);
226: }
227: }
228:
229: return (0);
230: }
231:
232:
233: /*
234: * cvs_repo_unlockent()
235: *
236: * Attempt to unlock the entry <ent>. The <owner> argument is used to make
237: * sure that the caller really owns the lock it is trying to release.
238: * Returns 0 on success, or -1 on failure.
239: */
240: int
241: cvs_repo_unlockent(CVSRPENT *ent, pid_t owner)
242: {
243: struct cvs_lock *lk;
244:
245: if ((ent->cr_wlock != NULL) && (ent->cr_wlock->lk_owner != 0)) {
246: if (ent->cr_wlock->lk_owner != owner) {
247: cvs_log(LP_ERR, "child %d attempted to unlock write "
248: "lock owned by %d", ent->cr_wlock->lk_owner);
249: return (-1);
250: }
251:
252: free(ent->cr_wlock);
253: ent->cr_wlock = NULL;
254: } else {
255: TAILQ_FOREACH(lk, &(ent->cr_rlocks), lk_link) {
256: if (lk->lk_owner == owner) {
257: TAILQ_REMOVE(&(ent->cr_rlocks), lk, lk_link);
258: free(lk);
259: break;
260: }
261: }
262: }
263:
264: #ifdef notyet
265: /* assign lock to any process with a pending request */
266: while ((lk = TAILQ_FIRST(&(ent->cr_lkreq))) != NULL) {
267: TAILQ_REMOVE(&(ent->cr_lkreq), lk, lk_link);
268: /* XXX send message to process */
269: child = cvsd_child_find(lk->lk_owner);
270: if (child == NULL)
271: continue;
272:
273: break;
274: }
275: #endif
276:
277: return (0);
278: }
279:
280:
281: /*
282: * cvs_repo_alias()
283: *
284: * Add a new module entry with name <alias> in the repository <repo>, which
1.5 ! joris 285: * points to the path <path> within the repository.
1.1 jfb 286: * Returns 0 on success, or -1 on failure.
287: */
288: int
289: cvs_repo_alias(CVSREPO *repo, const char *path, const char *alias)
290: {
291: CVSMODULE *mod;
292:
293: mod = (CVSMODULE *)malloc(sizeof(*mod));
294: if (mod == NULL) {
295: cvs_log(LP_ERRNO, "failed to allocate module alias");
296: return (-1);
297: }
298: memset(mod, 0, sizeof(*mod));
299:
300: mod->cm_name = strdup(alias);
301: if (mod->cm_name == NULL) {
302: cvs_log(LP_ERRNO, "failed to allocate module alias");
303: free(mod);
304: return (-1);
305: }
306: mod->cm_flags |= CVS_MODULE_ISALIAS;
307:
308: mod->cm_path = strdup(path);
309: if (mod->cm_path == NULL) {
310: cvs_log(LP_ERRNO, "failed to allocate module alias");
311: free(mod->cm_name);
312: free(mod);
313: return (-1);
314: }
315:
316: TAILQ_INSERT_TAIL(&(repo->cr_modules), mod, cm_link);
317:
318: return (0);
319: }
320:
321:
322: /*
323: * cvs_repo_unalias()
324: *
325: * Remove the module alias <alias> from the repository <repo>.
326: * Returns 0 on success, or -1 on failure.
327: */
328: int
329: cvs_repo_unalias(CVSREPO *repo, const char *alias)
330: {
331: CVSMODULE *mod;
332:
333: TAILQ_FOREACH(mod, &(repo->cr_modules), cm_link) {
334: if (strcmp(mod->cm_name, alias) == 0) {
335: if (!(mod->cm_flags & CVS_MODULE_ISALIAS)) {
336: cvs_log(LP_ERR,
337: "attempt to remove non-aliased module `%s'",
338: mod->cm_name);
339: return (-1);
340: }
341:
342: break;
343: }
344: }
345: if (mod == NULL)
346: return (-1);
347:
348: TAILQ_REMOVE(&(repo->cr_modules), mod, cm_link);
349: return (0);
350: }
351:
352:
353: /*
354: * cvs_repo_find()
355: *
356: * Find the pointer to a CVS file entry within the file hierarchy <hier>.
357: * The file's pathname <path> must be relative to the base of <hier>.
358: * Returns the entry on success, or NULL on failure.
359: */
360: CVSRPENT*
361: cvs_repo_find(CVSREPO *repo, const char *path)
362: {
363: size_t len;
364: char *pp, *sp, pbuf[MAXPATHLEN];
365: CVSRPENT *sf, *cf;
366:
367: if ((len = strlcpy(pbuf, path, sizeof(pbuf))) >= sizeof(pbuf)) {
1.2 xsa 368: errno = ENAMETOOLONG;
369: cvs_log(LP_ERRNO, "%s", path);
1.1 jfb 370: return (NULL);
371: }
372:
373: /* remove any trailing slashes */
374: while ((len > 0) && (pbuf[len - 1] == '/'))
375: pbuf[--len] = '\0';
376:
377: cf = repo->cr_tree;
378: pp = pbuf;
379: do {
380: if (cf->cr_type != CVS_RPENT_DIR) {
1.2 xsa 381: errno = ENOTDIR;
382: cvs_log(LP_ERRNO, "%s", path);
1.1 jfb 383: return (NULL);
384: }
385: sp = strchr(pp, '/');
386: if (sp != NULL)
387: *(sp++) = '\0';
388:
389: /* special case */
390: if (*pp == '.') {
391: if ((*(pp + 1) == '.') && (*(pp + 2) == '\0')) {
392: /* request to go back to parent */
393: if (cf->cr_parent == NULL) {
394: cvs_log(LP_NOTICE,
395: "path %s goes back too far", path);
396: return (NULL);
397: }
398: cf = cf->cr_parent;
399: continue;
400: } else if (*(pp + 1) == '\0')
401: continue;
402: }
403:
404: TAILQ_FOREACH(sf, &(cf->cr_files), cr_link) {
405: if (strcmp(pp, sf->cr_name) == 0)
406: break;
407: }
408: if (sf == NULL)
409: return (NULL);
410:
411: cf = sf;
412: pp = sp;
413: } while (sp != NULL);
414:
415: return (cf);
416: }
417:
418:
419: #if 0
420: /*
421: * cvs_repo_getpath()
422: *
423: * Get the full path of the file <file> and store it in <buf>, which is of
424: * size <len>. For portability, it is recommended that <buf> always be
425: * at least MAXPATHLEN bytes long.
426: * Returns a pointer to the start of the path on success, or NULL on failure.
427: */
428: char*
429: cvs_repo_getpath(CVSRPENT *file, char *buf, size_t len)
430: {
431: u_int i;
432: char *fp, *namevec[CVS_FILE_MAXDEPTH];
433: CVSRPENT *top;
434:
435: buf[0] = '\0';
436: i = CVS_FILE_MAXDEPTH;
437: memset(namevec, 0, sizeof(namevec));
438:
439: /* find the top node */
440: for (top = file; (top != NULL) && (i > 0); top = top->cr_parent) {
441: fp = top->cr_name;
442:
443: /* skip self-references */
444: if ((fp[0] == '.') && (fp[1] == '\0'))
445: continue;
446: namevec[--i] = fp;
447: }
448:
449: if (i == 0)
450: return (NULL);
451: else if (i == CVS_FILE_MAXDEPTH) {
452: strlcpy(buf, ".", len);
453: return (buf);
454: }
455:
456: while (i < CVS_FILE_MAXDEPTH - 1) {
457: strlcat(buf, namevec[i++], len);
458: strlcat(buf, "/", len);
459: }
460: strlcat(buf, namevec[i], len);
461:
462: return (buf);
463: }
464: #endif
465:
466:
467: /*
468: * cvs_repo_loadrec()
469: *
470: * Recursively load the repository structure
471: */
472: static CVSRPENT*
473: cvs_repo_loadrec(CVSREPO *repo, const char *path)
474: {
1.3 xsa 475: int ret, fd, l;
1.1 jfb 476: long base;
477: u_char *dp, *ep;
478: mode_t fmode;
479: char fbuf[2048], pbuf[MAXPATHLEN];
480: struct dirent *ent;
481: CVSRPENT *cfp, *cr_ent;
482: struct stat st;
483:
484: cvs_log(LP_NOTICE, "loading %s", path);
485: if (stat(path, &st) == -1) {
486: cvs_log(LP_ERRNO, "failed to stat %s", path);
487: return (NULL);
488: }
489:
490: cfp = (CVSRPENT *)malloc(sizeof(*cfp));
491: if (cfp == NULL) {
492: cvs_log(LP_ERRNO, "failed to allocate repository entry");
493: return (NULL);
494: }
495: memset(cfp, 0, sizeof(*cfp));
496: TAILQ_INIT(&(cfp->cr_rlocks));
497: TAILQ_INIT(&(cfp->cr_lkreq));
498:
499: cfp->cr_name = strdup(basename(path));
500: if (cfp->cr_name == NULL) {
501: cvs_log(LP_ERRNO, "failed to copy entry name");
502: free(cfp);
503: return (NULL);
504: }
505:
506: if (repo->cr_flags & CVS_REPO_CHKPERM) {
507: if (S_ISDIR(st.st_mode))
508: fmode = CVSD_DPERM;
509: else
510: fmode = CVSD_FPERM;
511: /* perform permission checks on the file */
512: if (st.st_uid != cvsd_uid) {
513: cvs_log(LP_WARN, "owner of `%s' is not %s",
514: path, CVSD_USER);
515: }
516:
517: if (st.st_gid != cvsd_gid) {
518: cvs_log(LP_WARN, "group of `%s' is not %s",
519: path, CVSD_GROUP);
520: }
521:
522: if (st.st_mode & S_IWGRP) {
523: cvs_log(LP_WARN, "file `%s' is group-writable",
524: path, fmode);
525: }
526:
527: if (st.st_mode & S_IWOTH) {
528: cvs_log(LP_WARN, "file `%s' is world-writable",
529: path, fmode);
530: }
531: }
532:
533: if (S_ISREG(st.st_mode))
534: cfp->cr_type = CVS_RPENT_RCSFILE;
535: else if (S_ISDIR(st.st_mode)) {
536: cfp->cr_type = CVS_RPENT_DIR;
537:
538: TAILQ_INIT(&(cfp->cr_files));
539:
540: if ((fd = open(path, O_RDONLY)) == -1) {
541: cvs_log(LP_ERRNO, "failed to open `%s'", path);
542: cvs_repo_entfree(cfp);
543: return (NULL);
544: }
545:
546: do {
547: ret = getdirentries(fd, fbuf, sizeof(fbuf), &base);
548: if (ret == -1) {
549: cvs_log(LP_ERRNO,
550: "failed to get directory entries");
551: cvs_repo_entfree(cfp);
552: (void)close(fd);
553: return (NULL);
554: }
555:
556: dp = fbuf;
557: ep = fbuf + (size_t)ret;
558: while (dp < ep) {
559: ent = (struct dirent *)dp;
560: dp += ent->d_reclen;
561: if (ent->d_fileno == 0)
562: continue;
563:
564: if (((ent->d_namlen == 1) &&
565: (ent->d_name[0] == '.')) ||
566: ((ent->d_namlen == 2) &&
567: (ent->d_name[0] == '.') &&
568: (ent->d_name[1] == '.')))
569: continue;
570:
1.3 xsa 571: l = snprintf(pbuf, sizeof(pbuf), "%s/%s", path,
1.1 jfb 572: ent->d_name);
1.3 xsa 573: if (l == -1 || l >= (int)sizeof(pbuf)) {
574: errno = ENAMETOOLONG;
575: cvs_log(LP_ERRNO, "%s", pbuf);
576:
577: cvs_repo_entree(cfp);
578: (void)close(fd);
579: return (NULL);
580: }
1.1 jfb 581:
582: if ((ent->d_type != DT_DIR) &&
583: (ent->d_type != DT_REG)) {
584: cvs_log(LP_NOTICE, "skipping non-"
585: "regular file `%s'", pbuf);
586: continue;
587: }
588:
589: cr_ent = cvs_repo_loadrec(repo, pbuf);
590: if (cr_ent == NULL) {
591: cvs_repo_entfree(cfp);
592: (void)close(fd);
593: return (NULL);
594: }
595:
596: cr_ent->cr_parent = cfp;
597: TAILQ_INSERT_TAIL(&(cfp->cr_files), cr_ent, cr_link);
598: }
599: } while (ret > 0);
600:
601: (void)close(fd);
602: }
603:
604: return (cfp);
605: }
606:
607:
608: /*
609: * cvs_repo_entfree()
610: *
611: * Free a repository entry structure and all underlying data. In the case of
612: * directories, any child entries are also freed recursively.
613: */
614: void
615: cvs_repo_entfree(CVSRPENT *ent)
616: {
617: CVSRPENT *ch_ent;
618:
619: if (ent->cr_type == CVS_RPENT_DIR) {
620: while ((ch_ent = TAILQ_FIRST(&(ent->cr_files))) != NULL) {
621: TAILQ_REMOVE(&(ent->cr_files), ch_ent, cr_link);
622: cvs_repo_entfree(ch_ent);
623: }
624:
625: }
626:
627: if (ent->cr_name != NULL)
628: free(ent->cr_name);
629: free(ent);
630: }
631:
632:
633: /*
634: * cvs_repo_modfree()
635: *
636: * Free a CVS module structure.
637: */
638: void
639: cvs_repo_modfree(CVSMODULE *mod)
640: {
641: if (mod->cm_name != NULL)
642: free(mod->cm_name);
643: if (mod->cm_path != NULL)
644: free(mod->cm_path);
645: free(mod);
646: }