[BACK]Return to repo.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / cvs

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: }