/* $OpenBSD: util.c,v 1.147 2008/06/21 15:39:15 joris Exp $ */ /* * Copyright (c) 2004 Jean-Francois Brousseau * Copyright (c) 2005, 2006 Joris Vink * Copyright (c) 2005, 2006 Xavier Santolaria * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include "cvs.h" #include "remote.h" #include "hash.h" extern int print_stdout; extern int build_dirs; extern int disable_fast_checkout; /* letter -> mode type map */ static const int cvs_modetypes[26] = { -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, -1, -1, 2, -1, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1, }; /* letter -> mode map */ static const mode_t cvs_modes[3][26] = { { 0, 0, 0, 0, 0, 0, 0, /* a - g */ 0, 0, 0, 0, 0, 0, 0, /* h - m */ 0, 0, 0, S_IRUSR, 0, 0, 0, /* n - u */ 0, S_IWUSR, S_IXUSR, 0, 0 /* v - z */ }, { 0, 0, 0, 0, 0, 0, 0, /* a - g */ 0, 0, 0, 0, 0, 0, 0, /* h - m */ 0, 0, 0, S_IRGRP, 0, 0, 0, /* n - u */ 0, S_IWGRP, S_IXGRP, 0, 0 /* v - z */ }, { 0, 0, 0, 0, 0, 0, 0, /* a - g */ 0, 0, 0, 0, 0, 0, 0, /* h - m */ 0, 0, 0, S_IROTH, 0, 0, 0, /* n - u */ 0, S_IWOTH, S_IXOTH, 0, 0 /* v - z */ } }; /* octal -> string */ static const char *cvs_modestr[8] = { "", "x", "w", "wx", "r", "rx", "rw", "rwx" }; /* * cvs_strtomode() * * Read the contents of the string and generate a permission mode from * the contents of , which is assumed to have the mode format of CVS. * The CVS protocol specification states that any modes or mode types that are * not recognized should be silently ignored. This function does not return * an error in such cases, but will issue warnings. */ void cvs_strtomode(const char *str, mode_t *mode) { char type; size_t l; mode_t m; char buf[32], ms[4], *sp, *ep; m = 0; l = strlcpy(buf, str, sizeof(buf)); if (l >= sizeof(buf)) fatal("cvs_strtomode: string truncation"); sp = buf; ep = sp; for (sp = buf; ep != NULL; sp = ep + 1) { ep = strchr(sp, ','); if (ep != NULL) *ep = '\0'; memset(ms, 0, sizeof ms); if (sscanf(sp, "%c=%3s", &type, ms) != 2 && sscanf(sp, "%c=", &type) != 1) { fatal("failed to scan mode string `%s'", sp); } if (type <= 'a' || type >= 'z' || cvs_modetypes[type - 'a'] == -1) { cvs_log(LP_ERR, "invalid mode type `%c'" " (`u', `g' or `o' expected), ignoring", type); continue; } /* make type contain the actual mode index */ type = cvs_modetypes[type - 'a']; for (sp = ms; *sp != '\0'; sp++) { if (*sp <= 'a' || *sp >= 'z' || cvs_modes[(int)type][*sp - 'a'] == 0) { fatal("invalid permission bit `%c'", *sp); } else m |= cvs_modes[(int)type][*sp - 'a']; } } *mode = m; } /* * cvs_modetostr() * * Generate a CVS-format string to represent the permissions mask on a file * from the mode and store the result in , which can accept up to * bytes (including the terminating NUL byte). The result is guaranteed * to be NUL-terminated. */ void cvs_modetostr(mode_t mode, char *buf, size_t len) { char tmp[16], *bp; mode_t um, gm, om; um = (mode & S_IRWXU) >> 6; gm = (mode & S_IRWXG) >> 3; om = mode & S_IRWXO; bp = buf; *bp = '\0'; if (um) { if (strlcpy(tmp, "u=", sizeof(tmp)) >= sizeof(tmp) || strlcat(tmp, cvs_modestr[um], sizeof(tmp)) >= sizeof(tmp)) fatal("cvs_modetostr: overflow for user mode"); if (strlcat(buf, tmp, len) >= len) fatal("cvs_modetostr: string truncation"); } if (gm) { if (um) { if (strlcat(buf, ",", len) >= len) fatal("cvs_modetostr: string truncation"); } if (strlcpy(tmp, "g=", sizeof(tmp)) >= sizeof(tmp) || strlcat(tmp, cvs_modestr[gm], sizeof(tmp)) >= sizeof(tmp)) fatal("cvs_modetostr: overflow for group mode"); if (strlcat(buf, tmp, len) >= len) fatal("cvs_modetostr: string truncation"); } if (om) { if (um || gm) { if (strlcat(buf, ",", len) >= len) fatal("cvs_modetostr: string truncation"); } if (strlcpy(tmp, "o=", sizeof(tmp)) >= sizeof(tmp) || strlcat(tmp, cvs_modestr[gm], sizeof(tmp)) >= sizeof(tmp)) fatal("cvs_modetostr: overflow for others mode"); if (strlcat(buf, tmp, len) >= len) fatal("cvs_modetostr: string truncation"); } } /* * cvs_cksum() * * Calculate the MD5 checksum of the file whose path is and generate * a CVS-format 32 hex-digit string, which is stored in , whose size is * given in and must be at least 33. * Returns 0 on success, or -1 on failure. */ int cvs_cksum(const char *file, char *dst, size_t len) { if (len < CVS_CKSUM_LEN) { cvs_log(LP_ERR, "buffer too small for checksum"); return (-1); } if (MD5File(file, dst) == NULL) { cvs_log(LP_ERR, "failed to generate checksum for %s", file); return (-1); } return (0); } /* * cvs_getargv() * * Parse a line contained in and generate an argument vector by * splitting the line on spaces and tabs. The resulting vector is stored in * , which can accept up to entries. * Returns the number of arguments in the vector, or -1 if an error occurred. */ int cvs_getargv(const char *line, char **argv, int argvlen) { u_int i; int argc, error; char *linebuf, *lp, *cp; linebuf = xstrdup(line); memset(argv, 0, argvlen * sizeof(char *)); argc = 0; /* build the argument vector */ error = 0; for (lp = linebuf; lp != NULL;) { cp = strsep(&lp, " \t"); if (cp == NULL) break; else if (*cp == '\0') continue; if (argc == argvlen) { error++; break; } argv[argc] = xstrdup(cp); argc++; } if (error != 0) { /* ditch the argument vector */ for (i = 0; i < (u_int)argc; i++) xfree(argv[i]); argc = -1; } xfree(linebuf); return (argc); } /* * cvs_makeargv() * * Allocate an argument vector large enough to accommodate for all the * arguments found in and return it. */ char ** cvs_makeargv(const char *line, int *argc) { int i, ret; char *argv[1024], **copy; ret = cvs_getargv(line, argv, 1024); if (ret == -1) return (NULL); copy = xcalloc(ret + 1, sizeof(char *)); for (i = 0; i < ret; i++) copy[i] = argv[i]; copy[ret] = NULL; *argc = ret; return (copy); } /* * cvs_freeargv() * * Free an argument vector previously generated by cvs_getargv(). */ void cvs_freeargv(char **argv, int argc) { int i; for (i = 0; i < argc; i++) if (argv[i] != NULL) xfree(argv[i]); } /* * cvs_chdir() * * Change to directory . * If is equal to `1', is removed if chdir() fails so we * do not have temporary directories leftovers. * Returns 0 on success. */ int cvs_chdir(const char *path, int rm) { if (chdir(path) == -1) { if (rm == 1) cvs_unlink(path); fatal("cvs_chdir: `%s': %s", path, strerror(errno)); } return (0); } /* * cvs_rename() * Change the name of a file. * rename() wrapper with an error message. * Returns 0 on success. */ int cvs_rename(const char *from, const char *to) { if (cvs_server_active == 0) cvs_log(LP_TRACE, "cvs_rename(%s,%s)", from, to); if (cvs_noexec == 1) return (0); if (rename(from, to) == -1) fatal("cvs_rename: `%s'->`%s': %s", from, to, strerror(errno)); return (0); } /* * cvs_unlink() * * Removes the link named by . * unlink() wrapper with an error message. * Returns 0 on success, or -1 on failure. */ int cvs_unlink(const char *path) { if (cvs_server_active == 0) cvs_log(LP_TRACE, "cvs_unlink(%s)", path); if (cvs_noexec == 1 && disable_fast_checkout != 0) return (0); if (unlink(path) == -1 && errno != ENOENT) { cvs_log(LP_ERRNO, "%s", path); return (-1); } return (0); } /* * cvs_rmdir() * * Remove a directory tree from disk. * Returns 0 on success, or -1 on failure. */ int cvs_rmdir(const char *path) { int type, ret = -1; DIR *dirp; struct dirent *ent; struct stat st; char fpath[MAXPATHLEN]; if (cvs_server_active == 0) cvs_log(LP_TRACE, "cvs_rmdir(%s)", path); if (cvs_noexec == 1 && disable_fast_checkout != 0) return (0); if ((dirp = opendir(path)) == NULL) { cvs_log(LP_ERR, "failed to open '%s'", path); return (-1); } while ((ent = readdir(dirp)) != NULL) { if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) continue; (void)xsnprintf(fpath, sizeof(fpath), "%s/%s", path, ent->d_name); if (ent->d_type == DT_UNKNOWN) { if (lstat(fpath, &st) == -1) fatal("'%s': %s", fpath, strerror(errno)); switch (st.st_mode & S_IFMT) { case S_IFDIR: type = CVS_DIR; break; case S_IFREG: type = CVS_FILE; break; default: fatal("'%s': Unknown file type in copy", fpath); } } else { switch (ent->d_type) { case DT_DIR: type = CVS_DIR; break; case DT_REG: type = CVS_FILE; break; default: fatal("'%s': Unknown file type in copy", fpath); } } switch (type) { case CVS_DIR: if (cvs_rmdir(fpath) == -1) goto done; break; case CVS_FILE: if (cvs_unlink(fpath) == -1 && errno != ENOENT) goto done; break; default: fatal("type %d unknown, shouldn't happen", type); } } if (rmdir(path) == -1 && errno != ENOENT) { cvs_log(LP_ERRNO, "%s", path); goto done; } ret = 0; done: closedir(dirp); return (ret); } void cvs_get_repository_path(const char *dir, char *dst, size_t len) { char buf[MAXPATHLEN]; cvs_get_repository_name(dir, buf, sizeof(buf)); (void)xsnprintf(dst, len, "%s/%s", current_cvsroot->cr_dir, buf); cvs_validate_directory(dst); } void cvs_get_repository_name(const char *dir, char *dst, size_t len) { FILE *fp; char fpath[MAXPATHLEN]; dst[0] = '\0'; if (!(cmdp->cmd_flags & CVS_USE_WDIR)) { if (strlcpy(dst, dir, len) >= len) fatal("cvs_get_repository_name: truncation"); return; } switch (cvs_cmdop) { case CVS_OP_EXPORT: if (strcmp(dir, ".")) if (strlcpy(dst, dir, len) >= len) fatal("cvs_get_repository_name: truncation"); break; case CVS_OP_IMPORT: if (strlcpy(dst, import_repository, len) >= len) fatal("cvs_get_repository_name: truncation"); if (strlcat(dst, "/", len) >= len) fatal("cvs_get_repository_name: truncation"); if (strcmp(dir, ".")) if (strlcat(dst, dir, len) >= len) fatal("cvs_get_repository_name: truncation"); break; default: (void)xsnprintf(fpath, sizeof(fpath), "%s/%s", dir, CVS_PATH_REPOSITORY); if ((fp = fopen(fpath, "r")) != NULL) { if ((fgets(dst, len, fp)) == NULL) fatal("%s: bad repository file", fpath); dst[strcspn(dst, "\n")] = '\0'; (void)fclose(fp); } else if (cvs_cmdop != CVS_OP_CHECKOUT) fatal("%s is missing", fpath); break; } } void cvs_mkadmin(const char *path, const char *root, const char *repo, char *tag, char *date) { FILE *fp; int fd; char buf[MAXPATHLEN]; struct hash_data *hdata, hd; hdata = hash_table_find(&created_cvs_directories, path, strlen(path)); if (hdata != NULL) return; hd.h_key = xstrdup(path); hd.h_data = NULL; hash_table_enter(&created_cvs_directories, &hd); if (cvs_server_active == 0) cvs_log(LP_TRACE, "cvs_mkadmin(%s, %s, %s, %s, %s)", path, root, repo, (tag != NULL) ? tag : "", (date != NULL) ? date : ""); (void)xsnprintf(buf, sizeof(buf), "%s/%s", path, CVS_PATH_CVSDIR); if (mkdir(buf, 0755) == -1 && errno != EEXIST) fatal("cvs_mkadmin: %s: %s", buf, strerror(errno)); if (cvs_cmdop == CVS_OP_CHECKOUT || cvs_cmdop == CVS_OP_ADD || (cvs_cmdop == CVS_OP_UPDATE && build_dirs == 1)) { (void)xsnprintf(buf, sizeof(buf), "%s/%s", path, CVS_PATH_ROOTSPEC); if ((fp = fopen(buf, "w")) == NULL) fatal("cvs_mkadmin: %s: %s", buf, strerror(errno)); fprintf(fp, "%s\n", root); (void)fclose(fp); } (void)xsnprintf(buf, sizeof(buf), "%s/%s", path, CVS_PATH_REPOSITORY); if ((fp = fopen(buf, "w")) == NULL) fatal("cvs_mkadmin: %s: %s", buf, strerror(errno)); fprintf(fp, "%s\n", repo); (void)fclose(fp); cvs_write_tagfile(path, tag, date); (void)xsnprintf(buf, sizeof(buf), "%s/%s", path, CVS_PATH_ENTRIES); if ((fd = open(buf, O_WRONLY|O_CREAT|O_EXCL, 0666 & ~cvs_umask)) == -1) { if (errno == EEXIST) return; fatal("cvs_mkadmin: %s: %s", buf, strerror(errno)); } if (atomicio(vwrite, fd, "D\n", 2) != 2) fatal("cvs_mkadmin: %s", strerror(errno)); close(fd); } void cvs_mkpath(const char *path, char *tag) { CVSENTRIES *ent; FILE *fp; size_t len; struct hash_data *hdata, hd; char *entry, sticky[CVS_REV_BUFSZ]; char *sp, *dp, *dir, *p, rpath[MAXPATHLEN], repo[MAXPATHLEN]; hdata = hash_table_find(&created_directories, path, strlen(path)); if (hdata != NULL) return; hd.h_key = xstrdup(path); hd.h_data = NULL; hash_table_enter(&created_directories, &hd); if (current_cvsroot->cr_method != CVS_METHOD_LOCAL || cvs_server_active == 1) cvs_validate_directory(path); dir = xstrdup(path); STRIP_SLASH(dir); if (cvs_server_active == 0) cvs_log(LP_TRACE, "cvs_mkpath(%s)", dir); repo[0] = '\0'; rpath[0] = '\0'; if (cvs_cmdop == CVS_OP_UPDATE || cvs_cmdop == CVS_OP_COMMIT) { if ((fp = fopen(CVS_PATH_REPOSITORY, "r")) != NULL) { if ((fgets(repo, sizeof(repo), fp)) == NULL) fatal("cvs_mkpath: bad repository file"); repo[strcspn(repo, "\n")] = '\0'; (void)fclose(fp); } } for (sp = dir; sp != NULL; sp = dp) { dp = strchr(sp, '/'); if (dp != NULL) *(dp++) = '\0'; if (sp == dir && module_repo_root != NULL) { len = strlcpy(repo, module_repo_root, sizeof(repo)); if (len >= (int)sizeof(repo)) fatal("cvs_mkpath: overflow"); } else if (strcmp(sp, ".")) { if (repo[0] != '\0') { len = strlcat(repo, "/", sizeof(repo)); if (len >= (int)sizeof(repo)) fatal("cvs_mkpath: overflow"); } len = strlcat(repo, sp, sizeof(repo)); if (len >= (int)sizeof(repo)) fatal("cvs_mkpath: overflow"); } if (rpath[0] != '\0') { len = strlcat(rpath, "/", sizeof(rpath)); if (len >= (int)sizeof(rpath)) fatal("cvs_mkpath: overflow"); } len = strlcat(rpath, sp, sizeof(rpath)); if (len >= (int)sizeof(rpath)) fatal("cvs_mkpath: overflow"); if (mkdir(rpath, 0755) == -1 && errno != EEXIST) fatal("cvs_mkpath: %s: %s", rpath, strerror(errno)); if (cvs_cmdop == CVS_OP_EXPORT && !cvs_server_active) continue; cvs_mkadmin(rpath, current_cvsroot->cr_str, repo, tag, NULL); if (dp != NULL) { if ((p = strchr(dp, '/')) != NULL) *p = '\0'; entry = xmalloc(CVS_ENT_MAXLINELEN); cvs_ent_line_str(dp, NULL, NULL, NULL, NULL, 1, 0, entry, CVS_ENT_MAXLINELEN); ent = cvs_ent_open(rpath); cvs_ent_add(ent, entry); xfree(entry); if (p != NULL) *p = '/'; } if (cvs_server_active == 1 && strcmp(rpath, ".")) { if (tag != NULL) { (void)xsnprintf(sticky, sizeof(sticky), "T%s", tag); cvs_server_set_sticky(rpath, sticky); } } } xfree(dir); } /* * Split the contents of a file into a list of lines. */ struct cvs_lines * cvs_splitlines(u_char *data, size_t len) { u_char *p, *c; size_t i, tlen; struct cvs_lines *lines; struct cvs_line *lp; lines = xcalloc(1, sizeof(*lines)); TAILQ_INIT(&(lines->l_lines)); lp = xcalloc(1, sizeof(*lp)); TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list); p = c = data; for (i = 0; i < len; i++) { if (*p == '\n' || (i == len - 1)) { tlen = p - c + 1; lp = xcalloc(1, sizeof(*lp)); lp->l_line = c; lp->l_len = tlen; lp->l_lineno = ++(lines->l_nblines); TAILQ_INSERT_TAIL(&(lines->l_lines), lp, l_list); c = p + 1; } p++; } return (lines); } void cvs_freelines(struct cvs_lines *lines) { struct cvs_line *lp; while ((lp = TAILQ_FIRST(&(lines->l_lines))) != NULL) { TAILQ_REMOVE(&(lines->l_lines), lp, l_list); if (lp->l_needsfree == 1) xfree(lp->l_line); xfree(lp); } xfree(lines); } /* * cvs_strsplit() * * Split a string of -separated values and allocate * an argument vector for the values found. */ struct cvs_argvector * cvs_strsplit(char *str, const char *sep) { struct cvs_argvector *av; size_t i = 0; char *cp, *p; cp = xstrdup(str); av = xmalloc(sizeof(*av)); av->str = cp; av->argv = xmalloc(sizeof(*(av->argv))); while ((p = strsep(&cp, sep)) != NULL) { av->argv[i++] = p; av->argv = xrealloc(av->argv, i + 1, sizeof(*(av->argv))); } av->argv[i] = NULL; return (av); } /* * cvs_argv_destroy() * * Free an argument vector previously allocated by cvs_strsplit(). */ void cvs_argv_destroy(struct cvs_argvector *av) { xfree(av->str); xfree(av->argv); xfree(av); } u_int cvs_revision_select(RCSFILE *file, char *range) { int i; u_int nrev; char *lstr, *rstr; struct rcs_delta *rdp; struct cvs_argvector *revargv, *revrange; RCSNUM *lnum, *rnum; nrev = 0; lnum = rnum = NULL; revargv = cvs_strsplit(range, ","); for (i = 0; revargv->argv[i] != NULL; i++) { revrange = cvs_strsplit(revargv->argv[i], ":"); if (revrange->argv[0] == NULL) fatal("invalid revision range: %s", revargv->argv[i]); else if (revrange->argv[1] == NULL) lstr = rstr = revrange->argv[0]; else { if (revrange->argv[2] != NULL) fatal("invalid revision range: %s", revargv->argv[i]); lstr = revrange->argv[0]; rstr = revrange->argv[1]; if (strcmp(lstr, "") == 0) lstr = NULL; if (strcmp(rstr, "") == 0) rstr = NULL; } if (lstr == NULL) lstr = RCS_HEAD_INIT; if ((lnum = rcs_translate_tag(lstr, file)) == NULL) fatal("cvs_revision_select: could not translate tag `%s'", lstr); if (rstr != NULL) { if ((rnum = rcs_translate_tag(rstr, file)) == NULL) fatal("cvs_revision_select: could not translate tag `%s'", rstr); } else { rnum = rcsnum_alloc(); rcsnum_cpy(file->rf_head, rnum, 0); } cvs_argv_destroy(revrange); TAILQ_FOREACH(rdp, &(file->rf_delta), rd_list) { if (rcsnum_cmp(rdp->rd_num, lnum, 0) <= 0 && rcsnum_cmp(rdp->rd_num, rnum, 0) >= 0 && !(rdp->rd_flags & RCS_RD_SELECT)) { rdp->rd_flags |= RCS_RD_SELECT; nrev++; } } rcsnum_free(lnum); rcsnum_free(rnum); } cvs_argv_destroy(revargv); return (nrev); } int cvs_yesno(void) { int c, ret; ret = 0; fflush(stderr); fflush(stdout); if ((c = getchar()) != 'y' && c != 'Y') ret = -1; else while (c != EOF && c != '\n') c = getchar(); return (ret); } /* * cvs_exec() * * Execute and send to the STDIN if not NULL. * If == 1, return the result of , * else, 0 or -1 if an error occur. */ int cvs_exec(char *prog, const char *in, int needwait) { pid_t pid; int fds[2], size, st; char *argp[4] = { "sh", "-c", prog, NULL }; if (in != NULL && pipe(fds) < 0) { cvs_log(LP_ERR, "cvs_exec: pipe failed"); return (-1); } if ((pid = fork()) == -1) { cvs_log(LP_ERR, "cvs_exec: fork failed"); return (-1); } else if (pid == 0) { if (in != NULL) { close(fds[1]); dup2(fds[0], STDIN_FILENO); } setenv("CVSROOT", current_cvsroot->cr_dir, 1); execv(_PATH_BSHELL, argp); cvs_log(LP_ERR, "cvs_exec: failed to run '%s'", prog); _exit(127); } if (in != NULL) { close(fds[0]); size = strlen(in); if (atomicio(vwrite, fds[1], in, size) != size) cvs_log(LP_ERR, "cvs_exec: failed to write on STDIN"); close(fds[1]); } if (needwait == 1) { while (waitpid(pid, &st, 0) == -1) ; if (!WIFEXITED(st)) { errno = EINTR; return (-1); } return (WEXITSTATUS(st)); } return (0); }