diff options
author | Federico G. Schwindt <fgsch@cvs.openbsd.org> | 2012-03-04 04:05:16 +0000 |
---|---|---|
committer | Federico G. Schwindt <fgsch@cvs.openbsd.org> | 2012-03-04 04:05:16 +0000 |
commit | ea5319421a589ebd8583734a461346e1f0d91bec (patch) | |
tree | f5bd7462d5be00f2b6c3ea91c579bd03d2ef6dce /gnu/usr.bin | |
parent | c90e6f8af6e01b6bd2b8ec8057571ae41b0ddb00 (diff) |
In preparation for getline and getdelim additions to libc, rename getline()
occurrences to get_line().
Based on a diff from Jan Klemkow <j-dot-klemkow-at-wemelug-dot-de> to tech.
Diffstat (limited to 'gnu/usr.bin')
-rw-r--r-- | gnu/usr.bin/cvs/lib/getline.c | 87 | ||||
-rw-r--r-- | gnu/usr.bin/cvs/lib/getline.h | 10 | ||||
-rw-r--r-- | gnu/usr.bin/cvs/src/client.c | 4 | ||||
-rw-r--r-- | gnu/usr.bin/cvs/src/commit.c | 2 | ||||
-rw-r--r-- | gnu/usr.bin/cvs/src/cvsrc.c | 120 | ||||
-rw-r--r-- | gnu/usr.bin/cvs/src/edit.c | 419 | ||||
-rw-r--r-- | gnu/usr.bin/cvs/src/entries.c | 1003 | ||||
-rw-r--r-- | gnu/usr.bin/cvs/src/fileattr.c | 213 | ||||
-rw-r--r-- | gnu/usr.bin/cvs/src/ignore.c | 2 | ||||
-rw-r--r-- | gnu/usr.bin/cvs/src/login.c | 4 | ||||
-rw-r--r-- | gnu/usr.bin/cvs/src/logmsg.c | 6 | ||||
-rw-r--r-- | gnu/usr.bin/cvs/src/mkmodules.c | 2 | ||||
-rw-r--r-- | gnu/usr.bin/cvs/src/parseinfo.c | 4 | ||||
-rw-r--r-- | gnu/usr.bin/cvs/src/patch.c | 6 | ||||
-rw-r--r-- | gnu/usr.bin/cvs/src/release.c | 416 | ||||
-rw-r--r-- | gnu/usr.bin/cvs/src/repos.c | 201 | ||||
-rw-r--r-- | gnu/usr.bin/cvs/src/root.c | 2 | ||||
-rw-r--r-- | gnu/usr.bin/cvs/src/server.c | 6 | ||||
-rw-r--r-- | gnu/usr.bin/cvs/src/subr.c | 2 | ||||
-rw-r--r-- | gnu/usr.bin/cvs/src/update.c | 2 | ||||
-rw-r--r-- | gnu/usr.bin/cvs/src/wrapper.c | 2 |
21 files changed, 1798 insertions, 715 deletions
diff --git a/gnu/usr.bin/cvs/lib/getline.c b/gnu/usr.bin/cvs/lib/getline.c index c69946148f6..bda96e27e14 100644 --- a/gnu/usr.bin/cvs/lib/getline.c +++ b/gnu/usr.bin/cvs/lib/getline.c @@ -10,11 +10,7 @@ License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +General Public License for more details. */ /* Written by Jan Brittenson, bson@gnu.ai.mit.edu. */ @@ -24,8 +20,9 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include <sys/types.h> #include <stdio.h> -#define NDEBUG #include <assert.h> +#include <errno.h> +#include "getline.h" #if STDC_HEADERS #include <stdlib.h> @@ -37,32 +34,45 @@ char *malloc (), *realloc (); #define MIN_CHUNK 64 /* Read up to (and including) a TERMINATOR from STREAM into *LINEPTR - + OFFSET (and null-terminate it). *LINEPTR is a pointer returned from - malloc (or NULL), pointing to *N characters of space. It is realloc'd - as necessary. Return the number of characters read (not including the - null terminator), or -1 on error or EOF. */ + + OFFSET (and null-terminate it). If LIMIT is non-negative, then + read no more than LIMIT chars. + + *LINEPTR is a pointer returned from malloc (or NULL), pointing to + *N characters of space. It is realloc'd as necessary. + + Return the number of characters read (not including the null + terminator), or -1 on error or EOF. On a -1 return, the caller + should check feof(), if not then errno has been set to indicate the + error. */ int -getstr (lineptr, n, stream, terminator, offset) +getstr (lineptr, n, stream, terminator, offset, limit) char **lineptr; size_t *n; FILE *stream; char terminator; int offset; + int limit; { int nchars_avail; /* Allocated but unused chars in *LINEPTR. */ char *read_pos; /* Where we're reading into *LINEPTR. */ int ret; if (!lineptr || !n || !stream) - return -1; + { + errno = EINVAL; + return -1; + } if (!*lineptr) { *n = MIN_CHUNK; *lineptr = malloc (*n); if (!*lineptr) - return -1; + { + errno = ENOMEM; + return -1; + } } nchars_avail = *n - offset; @@ -70,13 +80,28 @@ getstr (lineptr, n, stream, terminator, offset) for (;;) { - register int c = getc (stream); + int save_errno; + register int c; + + if (limit == 0) + break; + else + { + c = getc (stream); + + /* If limit is negative, then we shouldn't pay attention to + it, so decrement only if positive. */ + if (limit > 0) + limit--; + } + + save_errno = errno; /* We always want at least one char left in the buffer, since we always (unless we get an error while reading the first char) NUL-terminate the line buffer. */ - assert(*n - nchars_avail == read_pos - *lineptr); + assert((*lineptr + *n) == (read_pos + nchars_avail)); if (nchars_avail < 2) { if (*n > MIN_CHUNK) @@ -87,12 +112,24 @@ getstr (lineptr, n, stream, terminator, offset) nchars_avail = *n + *lineptr - read_pos; *lineptr = realloc (*lineptr, *n); if (!*lineptr) - return -1; + { + errno = ENOMEM; + return -1; + } read_pos = *n - nchars_avail + *lineptr; - assert(*n - nchars_avail == read_pos - *lineptr); + assert((*lineptr + *n) == (read_pos + nchars_avail)); } - if (c == EOF || ferror (stream)) + if (ferror (stream)) + { + /* Might like to return partial line, but there is no + place for us to store errno. And we don't want to just + lose errno. */ + errno = save_errno; + return -1; + } + + if (c == EOF) { /* Return partial line, if any. */ if (read_pos == *lineptr) @@ -117,10 +154,20 @@ getstr (lineptr, n, stream, terminator, offset) } int -getline (lineptr, n, stream) +get_line (lineptr, n, stream) + char **lineptr; + size_t *n; + FILE *stream; +{ + return getstr (lineptr, n, stream, '\n', 0, GETLINE_NO_LIMIT); +} + +int +getline_safe (lineptr, n, stream, limit) char **lineptr; size_t *n; FILE *stream; + int limit; { - return getstr (lineptr, n, stream, '\n', 0); + return getstr (lineptr, n, stream, '\n', 0, limit); } diff --git a/gnu/usr.bin/cvs/lib/getline.h b/gnu/usr.bin/cvs/lib/getline.h index 30bcc258373..3c697c8e6e3 100644 --- a/gnu/usr.bin/cvs/lib/getline.h +++ b/gnu/usr.bin/cvs/lib/getline.h @@ -9,7 +9,15 @@ #define __PROTO(args) () #endif /* GCC. */ +#define GETLINE_NO_LIMIT -1 + +int + get_line __PROTO ((char **_lineptr, size_t *_n, FILE *_stream)); +int + getline_safe __PROTO ((char **_lineptr, size_t *_n, FILE *_stream, + int limit)); int - getline __PROTO ((char **_lineptr, size_t *_n, FILE *_stream)); + getstr __PROTO ((char **_lineptr, size_t *_n, FILE *_stream, + char _terminator, int _offset, int limit)); #endif /* _getline_h_ */ diff --git a/gnu/usr.bin/cvs/src/client.c b/gnu/usr.bin/cvs/src/client.c index 57739524ee2..0c0df1bc91d 100644 --- a/gnu/usr.bin/cvs/src/client.c +++ b/gnu/usr.bin/cvs/src/client.c @@ -5818,7 +5818,7 @@ notified_a_file (data, ent_list, short_pathname, filename) char *p; fp = open_file (CVSADM_NOTIFY, "r"); - if (getline (&line, &line_len, fp) < 0) + if (get_line (&line, &line_len, fp) < 0) { if (feof (fp)) error (0, 0, "cannot read %s: end of file", CVSADM_NOTIFY); @@ -5839,7 +5839,7 @@ notified_a_file (data, ent_list, short_pathname, filename) line + 1); } - if (getline (&line, &line_len, fp) < 0) + if (get_line (&line, &line_len, fp) < 0) { if (feof (fp)) { diff --git a/gnu/usr.bin/cvs/src/commit.c b/gnu/usr.bin/cvs/src/commit.c index 0fe9f9425d7..bb576d394ae 100644 --- a/gnu/usr.bin/cvs/src/commit.c +++ b/gnu/usr.bin/cvs/src/commit.c @@ -1482,7 +1482,7 @@ commit_filesdoneproc (callerdat, err, repository, update_dir, entries) line = NULL; line_chars_allocated = 0; - line_length = getline (&line, &line_chars_allocated, fp); + line_length = get_line (&line, &line_chars_allocated, fp); if (line_length > 0) { /* Remove any trailing newline. */ diff --git a/gnu/usr.bin/cvs/src/cvsrc.c b/gnu/usr.bin/cvs/src/cvsrc.c index ec594eb5456..03dbb791683 100644 --- a/gnu/usr.bin/cvs/src/cvsrc.c +++ b/gnu/usr.bin/cvs/src/cvsrc.c @@ -1,20 +1,16 @@ /* - * Copyright (c) 1993 david d zuhn - * - * written by david d `zoo' zuhn while at Cygnus Support - * - * You may distribute under the terms of the GNU General Public License - * as specified in the README file that comes with the CVS 1.4 kit. + * Copyright (c) 1993 david d zuhn + * + * Written by david d `zoo' zuhn while at Cygnus Support + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS source distribution. * */ #include "cvs.h" - -#ifndef lint -static const char rcsid[] = "$CVSid: @(#)cvsrc.c 1.9 94/09/30 $"; -USE(rcsid); -#endif /* lint */ +#include "getline.h" /* this file is to be found in the user's home directory */ @@ -27,19 +23,26 @@ char cvsrc[] = CVSRC_FILENAME; extern char *strtok (); +/* Read cvsrc, processing options matching CMDNAME ("cvs" for global + options, and update *ARGC and *ARGV accordingly. */ + void -read_cvsrc (argc, argv) - int *argc; - char ***argv; +read_cvsrc (argc, argv, cmdname) + int *argc; + char ***argv; + char *cmdname; { char *homedir; char *homeinit; FILE *cvsrcfile; - char linebuf [MAXLINELEN]; - + char *line; + int line_length; + size_t line_chars_allocated; + char *optstart; + int command_len; int found = 0; int i; @@ -48,20 +51,25 @@ read_cvsrc (argc, argv) int max_new_argv; char **new_argv; + /* old_argc and old_argv hold the values returned from the + previous invocation of read_cvsrc and are used to free the + allocated memory. The first invocation of read_cvsrc gets argv + from the system, this memory must not be free'd. */ + static int old_argc = 0; + static char **old_argv = NULL; + /* don't do anything if argc is -1, since that implies "help" mode */ if (*argc == -1) return; - /* setup the new options list */ - - new_argc = 1; - max_new_argv = (*argc) + GROW; - new_argv = (char **) xmalloc (max_new_argv * sizeof (char*)); - new_argv[0] = xstrdup ((*argv)[0]); - /* determine filename for ~/.cvsrc */ - homedir = getenv ("HOME"); + homedir = get_homedir (); + /* If we can't find a home directory, ignore ~/.cvsrc. This may + make tracking down problems a bit of a pain, but on the other + hand it might be obnoxious to complain when CVS will function + just fine without .cvsrc (and many users won't even know what + .cvsrc is). */ if (!homedir) return; @@ -72,7 +80,7 @@ read_cvsrc (argc, argv) /* if it can't be read, there's no point to continuing */ - if (access (homeinit, R_OK) != 0) + if (!isreadable (homeinit)) { free (homeinit); return; @@ -80,59 +88,79 @@ read_cvsrc (argc, argv) /* now scan the file until we find the line for the command in question */ + line = NULL; + line_chars_allocated = 0; + command_len = strlen (cmdname); cvsrcfile = open_file (homeinit, "r"); - while (fgets (linebuf, MAXLINELEN, cvsrcfile)) + while ((line_length = get_line (&line, &line_chars_allocated, cvsrcfile)) + >= 0) { /* skip over comment lines */ - if (linebuf[0] == '#') + if (line[0] == '#') continue; /* stop if we match the current command */ - if (!strncmp (linebuf, (*argv)[0], strlen ((*argv)[0]))) + if (!strncmp (line, cmdname, command_len) + && isspace ((unsigned char) *(line + command_len))) { found = 1; break; } } + if (line_length < 0 && !feof (cvsrcfile)) + error (0, errno, "cannot read %s", homeinit); + fclose (cvsrcfile); + /* setup the new options list */ + + new_argc = 1; + max_new_argv = (*argc) + GROW; + new_argv = (char **) xmalloc (max_new_argv * sizeof (char*)); + new_argv[0] = xstrdup ((*argv)[0]); + if (found) { /* skip over command in the options line */ - optstart = strtok(linebuf+strlen((*argv)[0]), "\t \n"); - - do + for (optstart = strtok (line + command_len, "\t \n"); + optstart; + optstart = strtok (NULL, "\t \n")) { - new_argv [new_argc] = xstrdup (optstart); - new_argv [new_argc+1] = NULL; - new_argc += 1; + new_argv [new_argc++] = xstrdup (optstart); if (new_argc >= max_new_argv) { - char **tmp_argv; max_new_argv += GROW; - tmp_argv = (char **) xmalloc (max_new_argv * sizeof (char*)); - for (i = 0; i <= new_argc; i++) - tmp_argv[i] = new_argv[i]; - free(new_argv); - new_argv = tmp_argv; + new_argv = (char **) xrealloc (new_argv, max_new_argv * sizeof (char*)); } - } - while ((optstart = strtok (NULL, "\t \n")) != NULL); } + if (line != NULL) + free (line); + /* now copy the remaining arguments */ + if (new_argc + *argc > max_new_argv) + { + max_new_argv = new_argc + *argc; + new_argv = (char **) xrealloc (new_argv, max_new_argv * sizeof (char*)); + } for (i=1; i < *argc; i++) { - new_argv [new_argc] = (*argv)[i]; - new_argc += 1; + new_argv [new_argc++] = xstrdup ((*argv)[i]); + } + + if (old_argv != NULL) + { + /* Free the memory which was allocated in the previous + read_cvsrc call. */ + free_names (&old_argc, old_argv); } - *argc = new_argc; - *argv = new_argv; + old_argc = *argc = new_argc; + old_argv = *argv = new_argv; free (homeinit); return; diff --git a/gnu/usr.bin/cvs/src/edit.c b/gnu/usr.bin/cvs/src/edit.c index 8eeecb51e45..0e34eb0c71e 100644 --- a/gnu/usr.bin/cvs/src/edit.c +++ b/gnu/usr.bin/cvs/src/edit.c @@ -8,11 +8,7 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + GNU General Public License for more details. */ #include "cvs.h" #include "getline.h" @@ -29,27 +25,26 @@ static int setting_tedit; static int setting_tunedit; static int setting_tcommit; -static int onoff_fileproc PROTO ((char *, char *, char *, List *, List *)); +static int onoff_fileproc PROTO ((void *callerdat, struct file_info *finfo)); static int -onoff_fileproc (file, update_dir, repository, entries, srcfiles) - char *file; - char *update_dir; - char *repository; - List *entries; - List *srcfiles; +onoff_fileproc (callerdat, finfo) + void *callerdat; + struct file_info *finfo; { - fileattr_set (file, "_watched", turning_on ? "" : NULL); + fileattr_set (finfo->file, "_watched", turning_on ? "" : NULL); return 0; } -static int onoff_filesdoneproc PROTO ((int, char *, char *)); +static int onoff_filesdoneproc PROTO ((void *, int, char *, char *, List *)); static int -onoff_filesdoneproc (err, repository, update_dir) +onoff_filesdoneproc (callerdat, err, repository, update_dir, entries) + void *callerdat; int err; char *repository; char *update_dir; + List *entries; { if (setting_default) fileattr_set (NULL, "_watched", turning_on ? "" : NULL); @@ -65,14 +60,17 @@ watch_onoff (argc, argv) int local = 0; int err; - optind = 1; - while ((c = getopt (argc, argv, "l")) != -1) + optind = 0; + while ((c = getopt (argc, argv, "+lR")) != -1) { switch (c) { case 'l': local = 1; break; + case 'R': + local = 0; + break; case '?': default: usage (watch_usage); @@ -83,7 +81,7 @@ watch_onoff (argc, argv) argv += optind; #ifdef CLIENT_SUPPORT - if (client_active) + if (current_parsed_root->isremote) { start_server (); @@ -91,11 +89,8 @@ watch_onoff (argc, argv) if (local) send_arg ("-l"); - send_file_names (argc, argv); - /* FIXME: We shouldn't have to send current files, but I'm not sure - whether it works. So send the files -- - it's slower but it works. */ - send_files (argc, argv, local, 0); + send_files (argc, argv, local, 0, SEND_NO_CONTENTS); + send_file_names (argc, argv, SEND_EXPAND_WILD); send_to_server (turning_on ? "watch-on\012" : "watch-off\012", 0); return get_responses_and_close (); } @@ -103,14 +98,14 @@ watch_onoff (argc, argv) setting_default = (argc <= 0); - lock_tree_for_write (argc, argv, local, 0); + lock_tree_for_write (argc, argv, local, W_LOCAL, 0); err = start_recursion (onoff_fileproc, onoff_filesdoneproc, - (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, + (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL, argc, argv, local, W_LOCAL, 0, 0, (char *)NULL, - 0, 0); + 0); - lock_tree_cleanup (); + Lock_Cleanup (); return err; } @@ -132,15 +127,12 @@ watch_off (argc, argv) return watch_onoff (argc, argv); } -static int dummy_fileproc PROTO ((char *, char *, char *, List *, List *)); +static int dummy_fileproc PROTO ((void *callerdat, struct file_info *finfo)); static int -dummy_fileproc (file, update_dir, repository, entries, srcfiles) - char *file; - char *update_dir; - char *repository; - List *entries; - List *srcfiles; +dummy_fileproc (callerdat, finfo) + void *callerdat; + struct file_info *finfo; { /* This is a pretty hideous hack, but the gist of it is that recurse.c won't call notify_check unless there is a fileproc, so we can't just @@ -148,9 +140,7 @@ dummy_fileproc (file, update_dir, repository, entries, srcfiles) return 0; } -static int ncheck_fileproc PROTO ((char *file, char *update_dir, - char *repository, - List * entries, List * srcfiles)); +static int ncheck_fileproc PROTO ((void *callerdat, struct file_info *finfo)); /* Check for and process notifications. Local only. I think that doing this as a fileproc is the only way to catch all the @@ -159,12 +149,9 @@ static int ncheck_fileproc PROTO ((char *file, char *update_dir, processed the directory. */ static int -ncheck_fileproc (file, update_dir, repository, entries, srcfiles) - char *file; - char *update_dir; - char *repository; - List *entries; - List *srcfiles; +ncheck_fileproc (callerdat, finfo) + void *callerdat; + struct file_info *finfo; { int notif_type; char *filename; @@ -179,14 +166,15 @@ ncheck_fileproc (file, update_dir, repository, entries, srcfiles) /* We send notifications even if noexec. I'm not sure which behavior is most sensible. */ - fp = fopen (CVSADM_NOTIFY, "r"); + fp = CVS_FOPEN (CVSADM_NOTIFY, "r"); if (fp == NULL) { if (!existence_error (errno)) error (0, errno, "cannot open %s", CVSADM_NOTIFY); return 0; } - while (getline (&line, &line_len, fp) > 0) + + while (get_line (&line, &line_len, fp) > 0) { notif_type = line[0]; if (notif_type == '\0') @@ -216,15 +204,16 @@ ncheck_fileproc (file, update_dir, repository, entries, srcfiles) *cp = '\0'; notify_do (notif_type, filename, getcaller (), val, watches, - repository); + finfo->repository); } + free (line); if (ferror (fp)) error (0, errno, "cannot read %s", CVSADM_NOTIFY); if (fclose (fp) < 0) error (0, errno, "cannot close %s", CVSADM_NOTIFY); - if (unlink (CVSADM_NOTIFY) < 0) + if ( CVS_UNLINK (CVSADM_NOTIFY) < 0) error (0, errno, "cannot remove %s", CVSADM_NOTIFY); return 0; @@ -246,7 +235,7 @@ send_notifications (argc, argv, local) /* OK, we've done everything which needs to happen on the client side. Now we can try to contact the server; if we fail, then the notifications stay in CVSADM_NOTIFY to be sent next time. */ - if (client_active) + if (current_parsed_root->isremote) { if (strcmp (command_name, "release") != 0) { @@ -255,9 +244,9 @@ send_notifications (argc, argv, local) } err += start_recursion (dummy_fileproc, (FILESDONEPROC) NULL, - (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, + (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL, argc, argv, local, W_LOCAL, 0, 0, (char *)NULL, - 0, 0); + 0); send_to_server ("noop\012", 0); if (strcmp (command_name, "release") == 0) @@ -270,25 +259,22 @@ send_notifications (argc, argv, local) { /* Local. */ - lock_tree_for_write (argc, argv, local, 0); + lock_tree_for_write (argc, argv, local, W_LOCAL, 0); err += start_recursion (ncheck_fileproc, (FILESDONEPROC) NULL, - (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, + (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL, argc, argv, local, W_LOCAL, 0, 0, (char *)NULL, - 0, 0); - lock_tree_cleanup (); + 0); + Lock_Cleanup (); } return err; } -static int edit_fileproc PROTO ((char *, char *, char *, List *, List *)); +static int edit_fileproc PROTO ((void *callerdat, struct file_info *finfo)); static int -edit_fileproc (file, update_dir, repository, entries, srcfiles) - char *file; - char *update_dir; - char *repository; - List *entries; - List *srcfiles; +edit_fileproc (callerdat, finfo) + void *callerdat; + struct file_info *finfo; { FILE *fp; time_t now; @@ -298,12 +284,26 @@ edit_fileproc (file, update_dir, repository, entries, srcfiles) if (noexec) return 0; + /* This is a somewhat screwy way to check for this, because it + doesn't help errors other than the nonexistence of the file + (e.g. permissions problems). It might be better to rearrange + the code so that CVSADM_NOTIFY gets written only after the + various actions succeed (but what if only some of them + succeed). */ + if (!isfile (finfo->file)) + { + error (0, 0, "no such file %s; ignored", finfo->fullname); + return 0; + } + fp = open_file (CVSADM_NOTIFY, "a"); (void) time (&now); ascnow = asctime (gmtime (&now)); ascnow[24] = '\0'; - fprintf (fp, "E%s\t%s GMT\t%s\t%s\t", file, + /* Fix non-standard format. */ + if (ascnow[8] == '0') ascnow[8] = ' '; + fprintf (fp, "E%s\t%s GMT\t%s\t%s\t", finfo->file, ascnow, hostname, CurDir); if (setting_tedit) fprintf (fp, "E"); @@ -315,48 +315,50 @@ edit_fileproc (file, update_dir, repository, entries, srcfiles) if (fclose (fp) < 0) { - if (update_dir[0] == '\0') - error (0, errno, "cannot close %s", file); + if (finfo->update_dir[0] == '\0') + error (0, errno, "cannot close %s", CVSADM_NOTIFY); else - error (0, errno, "cannot close %s/%s", update_dir, file); + error (0, errno, "cannot close %s/%s", finfo->update_dir, + CVSADM_NOTIFY); } - xchmod (file, 1); + xchmod (finfo->file, 1); /* Now stash the file away in CVSADM so that unedit can revert even if it can't communicate with the server. We stash away a writable copy so that if the user removes the working file, then restores it with "cvs update" (which clears _editors but does not update CVSADM_BASE), then a future "cvs edit" can still win. */ - /* Could save a system call by only calling mkdir if trying to create - the output file fails. But copy_file isn't set up to facilitate - that. */ - if (CVS_MKDIR (CVSADM_BASE, 0777) < 0) - { - if (errno != EEXIST -#ifdef EACCESS - /* OS/2; see longer comment in client.c. */ - && errno != EACCESS -#endif - ) - error (1, errno, "cannot mkdir %s", CVSADM_BASE); - } - basefilename = xmalloc (10 + sizeof CVSADM_BASE + strlen (file)); + /* Could save a system call by only calling mkdir_if_needed if + trying to create the output file fails. But copy_file isn't + set up to facilitate that. */ + mkdir_if_needed (CVSADM_BASE); + basefilename = xmalloc (10 + sizeof CVSADM_BASE + strlen (finfo->file)); strcpy (basefilename, CVSADM_BASE); strcat (basefilename, "/"); - strcat (basefilename, file); - copy_file (file, basefilename); + strcat (basefilename, finfo->file); + copy_file (finfo->file, basefilename); free (basefilename); + { + Node *node; + + node = findnode_fn (finfo->entries, finfo->file); + if (node != NULL) + base_register (finfo, ((Entnode *) node->data)->version); + } + return 0; } static const char *const edit_usage[] = { - "Usage: %s %s [-l] [files...]\n", + "Usage: %s %s [-lR] [files...]\n", "-l: Local directory only, not recursive\n", + "-R: Process directories recursively\n", "-a: Specify what actions for temporary watch, one of\n", - " edit,unedit,commit.all,none\n", + " edit,unedit,commit,all,none\n", + "(Specify the --help global option for a list of other help options)\n", NULL }; @@ -377,14 +379,17 @@ edit (argc, argv) setting_tedit = 0; setting_tunedit = 0; setting_tcommit = 0; - optind = 1; - while ((c = getopt (argc, argv, "la:")) != -1) + optind = 0; + while ((c = getopt (argc, argv, "+lRa:")) != -1) { switch (c) { case 'l': local = 1; break; + case 'R': + local = 0; + break; case 'a': a_omitted = 0; if (strcmp (optarg, "edit") == 0) @@ -424,27 +429,33 @@ edit (argc, argv) setting_tcommit = 1; } + if (strpbrk (hostname, "+,>;=\t\n") != NULL) + error (1, 0, + "host name (%s) contains an invalid character (+,>;=\\t\\n)", + hostname); + if (strpbrk (CurDir, "+,>;=\t\n") != NULL) + error (1, 0, +"current directory (%s) contains an invalid character (+,>;=\\t\\n)", + CurDir); + /* No need to readlock since we aren't doing anything to the repository. */ err = start_recursion (edit_fileproc, (FILESDONEPROC) NULL, - (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, + (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL, argc, argv, local, W_LOCAL, 0, 0, (char *)NULL, - 0, 0); + 0); err += send_notifications (argc, argv, local); return err; } -static int unedit_fileproc PROTO ((char *, char *, char *, List *, List *)); +static int unedit_fileproc PROTO ((void *callerdat, struct file_info *finfo)); static int -unedit_fileproc (file, update_dir, repository, entries, srcfiles) - char *file; - char *update_dir; - char *repository; - List *entries; - List *srcfiles; +unedit_fileproc (callerdat, finfo) + void *callerdat; + struct file_info *finfo; { FILE *fp; time_t now; @@ -454,10 +465,10 @@ unedit_fileproc (file, update_dir, repository, entries, srcfiles) if (noexec) return 0; - basefilename = xmalloc (10 + sizeof CVSADM_BASE + strlen (file)); + basefilename = xmalloc (10 + sizeof CVSADM_BASE + strlen (finfo->file)); strcpy (basefilename, CVSADM_BASE); strcat (basefilename, "/"); - strcat (basefilename, file); + strcat (basefilename, finfo->file); if (!isfile (basefilename)) { /* This file apparently was never cvs edit'd (e.g. we are uneditting @@ -466,11 +477,9 @@ unedit_fileproc (file, update_dir, repository, entries, srcfiles) return 0; } - if (xcmp (file, basefilename) != 0) + if (xcmp (finfo->file, basefilename) != 0) { - if (update_dir[0] != '\0') - printf ("%s/", update_dir); - printf ("%s has been modified; revert changes? ", file); + printf ("%s has been modified; revert changes? ", finfo->fullname); if (!yesno ()) { /* "no". */ @@ -478,7 +487,7 @@ unedit_fileproc (file, update_dir, repository, entries, srcfiles) return 0; } } - rename_file (basefilename, file); + rename_file (basefilename, finfo->file); free (basefilename); fp = open_file (CVSADM_NOTIFY, "a"); @@ -486,21 +495,86 @@ unedit_fileproc (file, update_dir, repository, entries, srcfiles) (void) time (&now); ascnow = asctime (gmtime (&now)); ascnow[24] = '\0'; - fprintf (fp, "U%s\t%s GMT\t%s\t%s\t\n", file, + /* Fix non-standard format. */ + if (ascnow[8] == '0') ascnow[8] = ' '; + fprintf (fp, "U%s\t%s GMT\t%s\t%s\t\n", finfo->file, ascnow, hostname, CurDir); if (fclose (fp) < 0) { - if (update_dir[0] == '\0') - error (0, errno, "cannot close %s", file); + if (finfo->update_dir[0] == '\0') + error (0, errno, "cannot close %s", CVSADM_NOTIFY); else - error (0, errno, "cannot close %s/%s", update_dir, file); + error (0, errno, "cannot close %s/%s", finfo->update_dir, + CVSADM_NOTIFY); } - xchmod (file, 0); + /* Now update the revision number in CVS/Entries from CVS/Baserev. + The basic idea here is that we are reverting to the revision + that the user edited. If we wanted "cvs update" to update + CVS/Base as we go along (so that an unedit could revert to the + current repository revision), we would need: + + update (or all send_files?) (client) needs to send revision in + new Entry-base request. update (server/local) needs to check + revision against repository and send new Update-base response + (like Update-existing in that the file already exists. While + we are at it, might try to clean up the syntax by having the + mode only in a "Mode" response, not in the Update-base itself). */ + { + char *baserev; + Node *node; + Entnode *entdata; + + baserev = base_get (finfo); + node = findnode_fn (finfo->entries, finfo->file); + /* The case where node is NULL probably should be an error or + something, but I don't want to think about it too hard right + now. */ + if (node != NULL) + { + entdata = (Entnode *) node->data; + if (baserev == NULL) + { + /* This can only happen if the CVS/Baserev file got + corrupted. We suspect it might be possible if the + user interrupts CVS, although I haven't verified + that. */ + error (0, 0, "%s not mentioned in %s", finfo->fullname, + CVSADM_BASEREV); + + /* Since we don't know what revision the file derives from, + keeping it around would be asking for trouble. */ + if (unlink_file (finfo->file) < 0) + error (0, errno, "cannot remove %s", finfo->fullname); + + /* This is cheesy, in a sense; why shouldn't we do the + update for the user? However, doing that would require + contacting the server, so maybe this is OK. */ + error (0, 0, "run update to complete the unedit"); + return 0; + } + Register (finfo->entries, finfo->file, baserev, entdata->timestamp, + entdata->options, entdata->tag, entdata->date, + entdata->conflict); + } + free (baserev); + base_deregister (finfo); + } + + xchmod (finfo->file, 0); return 0; } +static const char *const unedit_usage[] = +{ + "Usage: %s %s [-lR] [files...]\n", + "-l: Local directory only, not recursive\n", + "-R: Process directories recursively\n", + "(Specify the --help global option for a list of other help options)\n", + NULL +}; + int unedit (argc, argv) int argc; @@ -511,19 +585,22 @@ unedit (argc, argv) int err; if (argc == -1) - usage (edit_usage); + usage (unedit_usage); - optind = 1; - while ((c = getopt (argc, argv, "l")) != -1) + optind = 0; + while ((c = getopt (argc, argv, "+lR")) != -1) { switch (c) { case 'l': local = 1; break; + case 'R': + local = 0; + break; case '?': default: - usage (edit_usage); + usage (unedit_usage); break; } } @@ -533,9 +610,9 @@ unedit (argc, argv) /* No need to readlock since we aren't doing anything to the repository. */ err = start_recursion (unedit_fileproc, (FILESDONEPROC) NULL, - (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, + (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL, argc, argv, local, W_LOCAL, 0, 0, (char *)NULL, - 0, 0); + 0); err += send_notifications (argc, argv, local); @@ -571,14 +648,14 @@ editor_set (filename, editor, val) edlist = fileattr_get0 (filename, "_editors"); newlist = fileattr_modify (edlist, editor, val, '>', ','); - if (edlist != NULL) - free (edlist); /* If the attributes is unchanged, don't rewrite the attribute file. */ if (!((edlist == NULL && newlist == NULL) || (edlist != NULL && newlist != NULL && strcmp (edlist, newlist) == 0))) fileattr_set (filename, "_editors", newlist); + if (edlist != NULL) + free (edlist); if (newlist != NULL) free (newlist); } @@ -607,6 +684,7 @@ notify_proc (repository, filter) { FILE *pipefp; char *prog; + char *expanded_prog; char *p; char *q; char *srepos; @@ -636,11 +714,21 @@ notify_proc (repository, filter) } *q = '\0'; - pipefp = Popen (prog, "w"); + /* FIXME: why are we calling expand_proc? Didn't we already + expand it in Parse_Info, before passing it to notify_proc? */ + expanded_prog = expand_path (prog, "notify", 0); + if (!expanded_prog) + { + free (prog); + return 1; + } + + pipefp = run_popen (expanded_prog, "w"); if (pipefp == NULL) { error (0, errno, "cannot write entry to notify filter: %s", prog); free (prog); + free (expanded_prog); return 1; } @@ -652,9 +740,13 @@ notify_proc (repository, filter) logfile_write for inspiration. */ free (prog); + free (expanded_prog); return (pclose (pipefp)); } +/* FIXME: this function should have a way to report whether there was + an error so that server.c can know whether to report Notified back + to the client. */ void notify_do (type, filename, who, val, watches, repository) int type; @@ -676,6 +768,11 @@ notify_do (type, filename, who, val, watches, repository) switch (type) { case 'E': + if (strpbrk (val, ",>;=\n") != NULL) + { + error (0, 0, "invalid character in editor value"); + return; + } editor_set (filename, who, val); break; case 'U': @@ -776,28 +873,35 @@ notify_do (type, filename, who, val, watches, repository) size_t line_len = 0; args.notifyee = NULL; - usersname = xmalloc (strlen (CVSroot) + usersname = xmalloc (strlen (current_parsed_root->directory) + sizeof CVSROOTADM + sizeof CVSROOTADM_USERS + 20); - strcpy (usersname, CVSroot); + strcpy (usersname, current_parsed_root->directory); strcat (usersname, "/"); strcat (usersname, CVSROOTADM); strcat (usersname, "/"); strcat (usersname, CVSROOTADM_USERS); - fp = fopen (usersname, "r"); + fp = CVS_FOPEN (usersname, "r"); if (fp == NULL && !existence_error (errno)) error (0, errno, "cannot read %s", usersname); if (fp != NULL) { - while (getline (&line, &line_len, fp) >= 0) + while (get_line (&line, &line_len, fp) >= 0) { if (strncmp (line, p, len) == 0 && line[len] == ':') { char *cp; args.notifyee = xstrdup (line + len + 1); - cp = strchr (args.notifyee, ':'); + + /* There may or may not be more + colon-separated fields added to this in the + future; in any case, we ignore them right + now, and if there are none we make sure to + chop off the final newline, if any. */ + cp = strpbrk (args.notifyee, ":\n"); + if (cp != NULL) *cp = '\0'; break; @@ -809,7 +913,8 @@ notify_do (type, filename, who, val, watches, repository) error (0, errno, "cannot close %s", usersname); } free (usersname); - free (line); + if (line != NULL) + free (line); if (args.notifyee == NULL) { @@ -859,6 +964,7 @@ notify_do (type, filename, who, val, watches, repository) } } +#ifdef CLIENT_SUPPORT /* Check and send notifications. This is only for the client. */ void notify_check (repository, update_dir) @@ -878,14 +984,14 @@ notify_check (repository, update_dir) /* We send notifications even if noexec. I'm not sure which behavior is most sensible. */ - fp = fopen (CVSADM_NOTIFY, "r"); + fp = CVS_FOPEN (CVSADM_NOTIFY, "r"); if (fp == NULL) { if (!existence_error (errno)) error (0, errno, "cannot open %s", CVSADM_NOTIFY); return; } - while (getline (&line, &line_len, fp) > 0) + while (get_line (&line, &line_len, fp) > 0) { int notif_type; char *filename; @@ -904,7 +1010,8 @@ notify_check (repository, update_dir) client_notify (repository, update_dir, filename, notif_type, val); } - + if (line) + free (line); if (ferror (fp)) error (0, errno, "cannot read %s", CVSADM_NOTIFY); if (fclose (fp) < 0) @@ -913,56 +1020,55 @@ notify_check (repository, update_dir) /* Leave the CVSADM_NOTIFY file there, until the server tells us it has dealt with it. */ } +#endif /* CLIENT_SUPPORT */ + static const char *const editors_usage[] = { - "Usage: %s %s [files...]\n", + "Usage: %s %s [-lR] [files...]\n", + "\t-l\tProcess this directory only (not recursive).\n", + "\t-R\tProcess directories recursively.\n", + "(Specify the --help global option for a list of other help options)\n", NULL }; -static int editors_fileproc PROTO ((char *, char *, char *, List *, List *)); +static int editors_fileproc PROTO ((void *callerdat, struct file_info *finfo)); static int -editors_fileproc (file, update_dir, repository, entries, srcfiles) - char *file; - char *update_dir; - char *repository; - List *entries; - List *srcfiles; +editors_fileproc (callerdat, finfo) + void *callerdat; + struct file_info *finfo; { char *them; char *p; - them = fileattr_get0 (file, "_editors"); + them = fileattr_get0 (finfo->file, "_editors"); if (them == NULL) return 0; - if (update_dir[0] == '\0') - printf ("%s", file); - else - printf ("%s/%s", update_dir, file); + cvs_output (finfo->fullname, 0); p = them; while (1) { - putc ('\t', stdout); + cvs_output ("\t", 1); while (*p != '>' && *p != '\0') - putc (*p++, stdout); + cvs_output (p++, 1); if (*p == '\0') { /* Only happens if attribute is misformed. */ - putc ('\n', stdout); + cvs_output ("\n", 1); break; } ++p; - putc ('\t', stdout); + cvs_output ("\t", 1); while (1) { while (*p != '+' && *p != ',' && *p != '\0') - putc (*p++, stdout); + cvs_output (p++, 1); if (*p == '\0') { - putc ('\n', stdout); + cvs_output ("\n", 1); goto out; } if (*p == ',') @@ -971,11 +1077,12 @@ editors_fileproc (file, update_dir, repository, entries, srcfiles) break; } ++p; - putc ('\t', stdout); + cvs_output ("\t", 1); } - putc ('\n', stdout); + cvs_output ("\n", 1); } out:; + free (them); return 0; } @@ -990,14 +1097,17 @@ editors (argc, argv) if (argc == -1) usage (editors_usage); - optind = 1; - while ((c = getopt (argc, argv, "l")) != -1) + optind = 0; + while ((c = getopt (argc, argv, "+lR")) != -1) { switch (c) { case 'l': local = 1; break; + case 'R': + local = 0; + break; case '?': default: usage (editors_usage); @@ -1008,25 +1118,22 @@ editors (argc, argv) argv += optind; #ifdef CLIENT_SUPPORT - if (client_active) + if (current_parsed_root->isremote) { start_server (); ign_setup (); if (local) send_arg ("-l"); - send_file_names (argc, argv); - /* FIXME: We shouldn't have to send current files, but I'm not sure - whether it works. So send the files -- - it's slower but it works. */ - send_files (argc, argv, local, 0); + send_files (argc, argv, local, 0, SEND_NO_CONTENTS); + send_file_names (argc, argv, SEND_EXPAND_WILD); send_to_server ("editors\012", 0); return get_responses_and_close (); } #endif /* CLIENT_SUPPORT */ return start_recursion (editors_fileproc, (FILESDONEPROC) NULL, - (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, + (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL, argc, argv, local, W_LOCAL, 0, 1, (char *)NULL, - 0, 0); + 0); } diff --git a/gnu/usr.bin/cvs/src/entries.c b/gnu/usr.bin/cvs/src/entries.c index f5b40121632..4b8f235199a 100644 --- a/gnu/usr.bin/cvs/src/entries.c +++ b/gnu/usr.bin/cvs/src/entries.c @@ -3,7 +3,7 @@ * Copyright (c) 1989-1992, Brian Berliner * * You may distribute under the terms of the GNU General Public License as - * specified in the README file that comes with the CVS 1.4 kit. + * specified in the README file that comes with the CVS source distribution. * * Entries file to Files file * @@ -12,20 +12,76 @@ */ #include "cvs.h" +#include "getline.h" -#ifndef lint -static const char rcsid[] = "$CVSid: @(#)entries.c 1.44 94/10/07 $"; -USE(rcsid); -#endif +static Node *AddEntryNode PROTO((List * list, Entnode *entnode)); + +static Entnode *fgetentent PROTO((FILE *, char *, int *)); +static int fputentent PROTO((FILE *, Entnode *)); -static Node *AddEntryNode PROTO((List * list, char *name, char *version, - char *timestamp, char *options, char *tag, - char *date, char *conflict)); +static Entnode *subdir_record PROTO((int, const char *, const char *)); static FILE *entfile; static char *entfilename; /* for error messages */ /* + * Construct an Entnode + */ +static Entnode *Entnode_Create PROTO ((enum ent_type, const char *, + const char *, const char *, + const char *, const char *, + const char *, const char *)); + +static Entnode * +Entnode_Create(type, user, vn, ts, options, tag, date, ts_conflict) + enum ent_type type; + const char *user; + const char *vn; + const char *ts; + const char *options; + const char *tag; + const char *date; + const char *ts_conflict; +{ + Entnode *ent; + + /* Note that timestamp and options must be non-NULL */ + ent = (Entnode *) xmalloc (sizeof (Entnode)); + ent->type = type; + ent->user = xstrdup (user); + ent->version = xstrdup (vn); + ent->timestamp = xstrdup (ts ? ts : ""); + ent->options = xstrdup (options ? options : ""); + ent->tag = xstrdup (tag); + ent->date = xstrdup (date); + ent->conflict = xstrdup (ts_conflict); + + return ent; +} + +/* + * Destruct an Entnode + */ +static void Entnode_Destroy PROTO ((Entnode *)); + +static void +Entnode_Destroy (ent) + Entnode *ent; +{ + free (ent->user); + free (ent->version); + free (ent->timestamp); + free (ent->options); + if (ent->tag) + free (ent->tag); + if (ent->date) + free (ent->date); + if (ent->conflict) + free (ent->conflict); + free (ent); +} + +/* * Write out the line associated with a node of an entries file */ static int write_ent_proc PROTO ((Node *, void *)); @@ -34,32 +90,16 @@ write_ent_proc (node, closure) Node *node; void *closure; { - Entnode *p; + Entnode *entnode; - p = (Entnode *) node->data; - if (fprintf (entfile, "/%s/%s/%s", node->key, p->version, - p->timestamp) == EOF) - error (1, errno, "cannot write %s", entfilename); - if (p->conflict) - { - if (fprintf (entfile, "+%s", p->conflict) < 0) - error (1, errno, "cannot write %s", entfilename); - } - if (fprintf (entfile, "/%s/", p->options) < 0) - error (1, errno, "cannot write %s", entfilename); + entnode = (Entnode *) node->data; - if (p->tag) - { - if (fprintf (entfile, "T%s\n", p->tag) < 0) - error (1, errno, "cannot write %s", entfilename); - } - else if (p->date) - { - if (fprintf (entfile, "D%s\n", p->date) < 0) - error (1, errno, "cannot write %s", entfilename); - } - else if (fprintf (entfile, "\n") < 0) + if (closure != NULL && entnode->type != ENT_FILE) + *(int *) closure = 1; + + if (fputentent(entfile, entnode)) error (1, errno, "cannot write %s", entfilename); + return (0); } @@ -71,10 +111,45 @@ static void write_entries (list) List *list; { + int sawdir; + + sawdir = 0; + /* open the new one and walk the list writing entries */ entfilename = CVSADM_ENTBAK; - entfile = open_file (entfilename, "w+"); - (void) walklist (list, write_ent_proc, NULL); + entfile = CVS_FOPEN (entfilename, "w+"); + if (entfile == NULL) + { + /* Make this a warning, not an error. For example, one user might + have checked out a working directory which, for whatever reason, + contains an Entries.Log file. A second user, without write access + to that working directory, might want to do a "cvs log". The + problem rewriting Entries shouldn't affect the ability of "cvs log" + to work, although the warning is probably a good idea so that + whether Entries gets rewritten is not an inexplicable process. */ + /* FIXME: should be including update_dir in message. */ + error (0, errno, "cannot rewrite %s", entfilename); + + /* Now just return. We leave the Entries.Log file around. As far + as I know, there is never any data lying around in 'list' that + is not in Entries.Log at this time (if there is an error writing + Entries.Log that is a separate problem). */ + return; + } + + (void) walklist (list, write_ent_proc, (void *) &sawdir); + if (! sawdir) + { + struct stickydirtag *sdtp; + + /* We didn't write out any directories. Check the list + private data to see whether subdirectory information is + known. If it is, we need to write out an empty D line. */ + sdtp = (struct stickydirtag *) list->list->data; + if (sdtp == NULL || sdtp->subdirs) + if (fprintf (entfile, "D\n") < 0) + error (1, errno, "cannot write %s", entfilename); + } if (fclose (entfile) == EOF) error (1, errno, "error closing %s", entfilename); @@ -82,7 +157,9 @@ write_entries (list) rename_file (entfilename, CVSADM_ENT); /* now, remove the log file */ - unlink_file (CVSADM_ENTLOG); + if (unlink_file (CVSADM_ENTLOG) < 0 + && !existence_error (errno)) + error (0, errno, "cannot remove %s", CVSADM_ENTLOG); } /* @@ -96,23 +173,32 @@ Scratch_Entry (list, fname) Node *node; if (trace) -#ifdef SERVER_SUPPORT - (void) fprintf (stderr, "%c-> Scratch_Entry(%s)\n", - (server_active) ? 'S' : ' ', fname); -#else - (void) fprintf (stderr, "-> Scratch_Entry(%s)\n", fname); -#endif + (void) fprintf (stderr, "%s-> Scratch_Entry(%s)\n", + CLIENT_SERVER_STR, fname); /* hashlookup to see if it is there */ - if ((node = findnode (list, fname)) != NULL) + if ((node = findnode_fn (list, fname)) != NULL) { + if (!noexec) + { + entfilename = CVSADM_ENTLOG; + entfile = open_file (entfilename, "a"); + + if (fprintf (entfile, "R ") < 0) + error (1, errno, "cannot write %s", entfilename); + + write_ent_proc (node, NULL); + + if (fclose (entfile) == EOF) + error (1, errno, "error closing %s", entfilename); + } + delnode (node); /* delete the node */ + #ifdef SERVER_SUPPORT if (server_active) server_scratch (fname); #endif - if (!noexec) - write_entries (list); /* re-write the file */ } } @@ -131,6 +217,7 @@ Register (list, fname, vn, ts, options, tag, date, ts_conflict) char *date; char *ts_conflict; { + Entnode *entnode; Node *node; #ifdef SERVER_SUPPORT @@ -142,30 +229,37 @@ Register (list, fname, vn, ts, options, tag, date, ts_conflict) if (trace) { -#ifdef SERVER_SUPPORT - (void) fprintf (stderr, "%c-> Register(%s, %s, %s%s%s, %s, %s %s)\n", - (server_active) ? 'S' : ' ', - fname, vn, ts ? ts : "", - ts_conflict ? "+" : "", ts_conflict ? ts_conflict : "", - options, tag ? tag : "", date ? date : ""); -#else - (void) fprintf (stderr, "-> Register(%s, %s, %s%s%s, %s, %s %s)\n", + (void) fprintf (stderr, "%s-> Register(%s, %s, %s%s%s, %s, %s %s)\n", + CLIENT_SERVER_STR, fname, vn, ts ? ts : "", ts_conflict ? "+" : "", ts_conflict ? ts_conflict : "", options, tag ? tag : "", date ? date : ""); -#endif } - node = AddEntryNode (list, fname, vn, ts, options, tag, date, ts_conflict); + entnode = Entnode_Create (ENT_FILE, fname, vn, ts, options, tag, date, + ts_conflict); + node = AddEntryNode (list, entnode); if (!noexec) { - entfile = open_file (CVSADM_ENTLOG, "a"); - + entfilename = CVSADM_ENTLOG; + entfile = CVS_FOPEN (entfilename, "a"); + + if (entfile == NULL) + { + /* Warning, not error, as in write_entries. */ + /* FIXME-update-dir: should be including update_dir in message. */ + error (0, errno, "cannot open %s", entfilename); + return; + } + + if (fprintf (entfile, "A ") < 0) + error (1, errno, "cannot write %s", entfilename); + write_ent_proc (node, NULL); if (fclose (entfile) == EOF) - error (1, errno, "error closing %s", CVSADM_ENTLOG); + error (1, errno, "error closing %s", entfilename); } } @@ -183,37 +277,66 @@ freesdt (p) free (sdtp->tag); if (sdtp->date) free (sdtp->date); - if (sdtp->options) - free (sdtp->options); free ((char *) sdtp); } -struct entent { - char *user; - char *vn; - char *ts; - char *options; - char *tag; - char *date; - char *ts_conflict; -}; +/* Return the next real Entries line. On end of file, returns NULL. + On error, prints an error message and returns NULL. */ -struct entent * -fgetentent(fpin) +static Entnode * +fgetentent(fpin, cmd, sawdir) FILE *fpin; + char *cmd; + int *sawdir; { - static struct entent ent; - static char line[MAXLINELEN]; + Entnode *ent; + char *line; + size_t line_chars_allocated; register char *cp; - char *user, *vn, *ts, *options; + enum ent_type type; + char *l, *user, *vn, *ts, *options; char *tag_or_date, *tag, *date, *ts_conflict; + int line_length; + + line = NULL; + line_chars_allocated = 0; - while (fgets (line, sizeof (line), fpin) != NULL) + ent = NULL; + while ((line_length = get_line (&line, &line_chars_allocated, fpin)) > 0) { - if (line[0] != '/') + l = line; + + /* If CMD is not NULL, we are reading an Entries.Log file. + Each line in the Entries.Log file starts with a single + character command followed by a space. For backward + compatibility, the absence of a space indicates an add + command. */ + if (cmd != NULL) + { + if (l[1] != ' ') + *cmd = 'A'; + else + { + *cmd = l[0]; + l += 2; + } + } + + type = ENT_FILE; + + if (l[0] == 'D') + { + type = ENT_SUBDIR; + *sawdir = 1; + ++l; + /* An empty D line is permitted; it is a signal that this + Entries file lists all known subdirectories. */ + } + + if (l[0] != '/') continue; - user = line + 1; + user = l + 1; if ((cp = strchr (user, '/')) == NULL) continue; *cp++ = '\0'; @@ -239,7 +362,7 @@ fgetentent(fpin) tag = tag_or_date + 1; else if (*tag_or_date == 'D') date = tag_or_date + 1; - + if ((ts_conflict = strchr (ts, '+'))) *ts_conflict++ = '\0'; @@ -255,11 +378,12 @@ fgetentent(fpin) */ { struct stat sb; - if (strlen (ts) > 30 && stat (user, &sb) == 0) + if (strlen (ts) > 30 && CVS_STAT (user, &sb) == 0) { - extern char *ctime (); char *c = ctime (&sb.st_mtime); - + /* Fix non-standard format. */ + if (c[8] == '0') c[8] = ' '; + if (!strncmp (ts + 25, c, 24)) ts = time_stamp (user); else @@ -270,33 +394,81 @@ fgetentent(fpin) } } - ent.user = user; - ent.vn = vn; - ent.ts = ts; - ent.options = options; - ent.tag = tag; - ent.date = date; - ent.ts_conflict = ts_conflict; + ent = Entnode_Create (type, user, vn, ts, options, tag, date, + ts_conflict); + break; + } + + if (line_length < 0 && !feof (fpin)) + error (0, errno, "cannot read entries file"); + + free (line); + return ent; +} + +static int +fputentent(fp, p) + FILE *fp; + Entnode *p; +{ + switch (p->type) + { + case ENT_FILE: + break; + case ENT_SUBDIR: + if (fprintf (fp, "D") < 0) + return 1; + break; + } + + if (fprintf (fp, "/%s/%s/%s", p->user, p->version, p->timestamp) < 0) + return 1; + if (p->conflict) + { + if (fprintf (fp, "+%s", p->conflict) < 0) + return 1; + } + if (fprintf (fp, "/%s/", p->options) < 0) + return 1; - return &ent; + if (p->tag) + { + if (fprintf (fp, "T%s\n", p->tag) < 0) + return 1; + } + else if (p->date) + { + if (fprintf (fp, "D%s\n", p->date) < 0) + return 1; + } + else + { + if (fprintf (fp, "\n") < 0) + return 1; } - return NULL; + return 0; } -/* - * Read the entries file into a list, hashing on the file name. - */ +/* Read the entries file into a list, hashing on the file name. + + UPDATE_DIR is the name of the current directory, for use in error + messages, or NULL if not known (that is, noone has gotten around + to updating the caller to pass in the information). */ List * -Entries_Open (aflag) +Entries_Open (aflag, update_dir) int aflag; + char *update_dir; { List *entries; - struct entent *ent; + struct stickydirtag *sdtp = NULL; + Entnode *ent; char *dirtag, *dirdate; + int dirnonbranch; int do_rewrite = 0; FILE *fpin; + int sawdir; /* get a fresh list... */ entries = getlist (); @@ -305,65 +477,90 @@ Entries_Open (aflag) * Parse the CVS/Tag file, to get any default tag/date settings. Use * list-private storage to tuck them away for Version_TS(). */ - ParseTag (&dirtag, &dirdate); + ParseTag (&dirtag, &dirdate, &dirnonbranch); if (aflag || dirtag || dirdate) { - struct stickydirtag *sdtp; - sdtp = (struct stickydirtag *) xmalloc (sizeof (*sdtp)); memset ((char *) sdtp, 0, sizeof (*sdtp)); sdtp->aflag = aflag; sdtp->tag = xstrdup (dirtag); sdtp->date = xstrdup (dirdate); + sdtp->nonbranch = dirnonbranch; /* feed it into the list-private area */ entries->list->data = (char *) sdtp; entries->list->delproc = freesdt; } - fpin = fopen (CVSADM_ENT, "r"); + sawdir = 0; + + fpin = CVS_FOPEN (CVSADM_ENT, "r"); if (fpin == NULL) + { + if (update_dir != NULL) + error (0, 0, "in directory %s:", update_dir); error (0, errno, "cannot open %s for reading", CVSADM_ENT); + } else { - while ((ent = fgetentent (fpin)) != NULL) + while ((ent = fgetentent (fpin, (char *) NULL, &sawdir)) != NULL) { - (void) AddEntryNode (entries, - ent->user, - ent->vn, - ent->ts, - ent->options, - ent->tag, - ent->date, - ent->ts_conflict); + (void) AddEntryNode (entries, ent); } - fclose (fpin); + if (fclose (fpin) < 0) + /* FIXME-update-dir: should include update_dir in message. */ + error (0, errno, "cannot close %s", CVSADM_ENT); } - fpin = fopen (CVSADM_ENTLOG, "r"); - if (fpin != NULL) { - while ((ent = fgetentent (fpin)) != NULL) + fpin = CVS_FOPEN (CVSADM_ENTLOG, "r"); + if (fpin != NULL) + { + char cmd; + Node *node; + + while ((ent = fgetentent (fpin, &cmd, &sawdir)) != NULL) { - (void) AddEntryNode (entries, - ent->user, - ent->vn, - ent->ts, - ent->options, - ent->tag, - ent->date, - ent->ts_conflict); + switch (cmd) + { + case 'A': + (void) AddEntryNode (entries, ent); + break; + case 'R': + node = findnode_fn (entries, ent->user); + if (node != NULL) + delnode (node); + Entnode_Destroy (ent); + break; + default: + /* Ignore unrecognized commands. */ + break; + } } do_rewrite = 1; - fclose (fpin); + if (fclose (fpin) < 0) + /* FIXME-update-dir: should include update_dir in message. */ + error (0, errno, "cannot close %s", CVSADM_ENTLOG); + } + + /* Update the list private data to indicate whether subdirectory + information is known. Nonexistent list private data is taken + to mean that it is known. */ + if (sdtp != NULL) + sdtp->subdirs = sawdir; + else if (! sawdir) + { + sdtp = (struct stickydirtag *) xmalloc (sizeof (*sdtp)); + memset ((char *) sdtp, 0, sizeof (*sdtp)); + sdtp->subdirs = 0; + entries->list->data = (char *) sdtp; + entries->list->delproc = freesdt; } if (do_rewrite && !noexec) write_entries (entries); /* clean up and return */ - if (fpin) - (void) fclose (fpin); if (dirtag) free (dirtag); if (dirdate) @@ -398,16 +595,7 @@ Entries_delproc (node) Entnode *p; p = (Entnode *) node->data; - free (p->version); - free (p->timestamp); - free (p->options); - if (p->tag) - free (p->tag); - if (p->date) - free (p->date); - if (p->conflict) - free (p->conflict); - free ((char *) p); + Entnode_Destroy(p); } /* @@ -415,21 +603,14 @@ Entries_delproc (node) * list */ static Node * -AddEntryNode (list, name, version, timestamp, options, tag, date, conflict) +AddEntryNode (list, entdata) List *list; - char *name; - char *version; - char *timestamp; - char *options; - char *tag; - char *date; - char *conflict; + Entnode *entdata; { Node *p; - Entnode *entdata; /* was it already there? */ - if ((p = findnode (list, name)) != NULL) + if ((p = findnode_fn (list, entdata->user)) != NULL) { /* take it out */ delnode (p); @@ -441,21 +622,11 @@ AddEntryNode (list, name, version, timestamp, options, tag, date, conflict) p->delproc = Entries_delproc; /* this one gets a key of the name for hashing */ - p->key = xstrdup (name); - - /* malloc the data parts and fill them in */ - p->data = xmalloc (sizeof (Entnode)); - entdata = (Entnode *) p->data; - entdata->version = xstrdup (version); - entdata->timestamp = xstrdup (timestamp); - if (entdata->timestamp == NULL) - entdata->timestamp = xstrdup ("");/* must be non-NULL */ - entdata->options = xstrdup (options); - if (entdata->options == NULL) - entdata->options = xstrdup ("");/* must be non-NULL */ - entdata->conflict = xstrdup (conflict); - entdata->tag = xstrdup (tag); - entdata->date = xstrdup (date); + /* FIXME This results in duplicated data --- the hash package shouldn't + assume that the key is dynamically allocated. The user's free proc + should be responsible for freeing the key. */ + p->key = xstrdup (entdata->user); + p->data = (char *) entdata; /* put the node into the list */ addnode (list, p); @@ -466,17 +637,23 @@ AddEntryNode (list, name, version, timestamp, options, tag, date, conflict) * Write out/Clear the CVS/Tag file. */ void -WriteTag (dir, tag, date) +WriteTag (dir, tag, date, nonbranch, update_dir, repository) char *dir; char *tag; char *date; + int nonbranch; + char *update_dir; + char *repository; { FILE *fout; - char tmp[PATH_MAX]; + char *tmp; if (noexec) return; + tmp = xmalloc ((dir ? strlen (dir) : 0) + + sizeof (CVSADM_TAG) + + 10); if (dir == NULL) (void) strcpy (tmp, CVSADM_TAG); else @@ -487,8 +664,16 @@ WriteTag (dir, tag, date) fout = open_file (tmp, "w+"); if (tag) { - if (fprintf (fout, "T%s\n", tag) < 0) - error (1, errno, "write to %s failed", tmp); + if (nonbranch) + { + if (fprintf (fout, "N%s\n", tag) < 0) + error (1, errno, "write to %s failed", tmp); + } + else + { + if (fprintf (fout, "T%s\n", tag) < 0) + error (1, errno, "write to %s failed", tmp); + } } else { @@ -499,38 +684,504 @@ WriteTag (dir, tag, date) error (1, errno, "cannot close %s", tmp); } else - if (unlink_file (tmp) < 0 && errno != ENOENT) + if (unlink_file (tmp) < 0 && ! existence_error (errno)) error (1, errno, "cannot remove %s", tmp); + free (tmp); +#ifdef SERVER_SUPPORT + if (server_active) + server_set_sticky (update_dir, repository, tag, date, nonbranch); +#endif } -/* - * Parse the CVS/Tag file for the current directory. - */ +/* Parse the CVS/Tag file for the current directory. + + If it contains a date, sets *DATEP to the date in a newly malloc'd + string, *TAGP to NULL, and *NONBRANCHP to an unspecified value. + + If it contains a branch tag, sets *TAGP to the tag in a newly + malloc'd string, *NONBRANCHP to 0, and *DATEP to NULL. + + If it contains a nonbranch tag, sets *TAGP to the tag in a newly + malloc'd string, *NONBRANCHP to 1, and *DATEP to NULL. + + If it does not exist, or contains something unrecognized by this + version of CVS, set *DATEP and *TAGP to NULL and *NONBRANCHP to + an unspecified value. + + If there is an error, print an error message, set *DATEP and *TAGP + to NULL, and return. */ void -ParseTag (tagp, datep) +ParseTag (tagp, datep, nonbranchp) char **tagp; char **datep; + int *nonbranchp; { FILE *fp; - char line[MAXLINELEN]; - char *cp; if (tagp) *tagp = (char *) NULL; if (datep) *datep = (char *) NULL; - fp = fopen (CVSADM_TAG, "r"); + /* Always store a value here, even in the 'D' case where the value + is unspecified. Shuts up tools which check for references to + uninitialized memory. */ + if (nonbranchp != NULL) + *nonbranchp = 0; + fp = CVS_FOPEN (CVSADM_TAG, "r"); if (fp) { - if (fgets (line, sizeof (line), fp) != NULL) + char *line; + int line_length; + size_t line_chars_allocated; + + line = NULL; + line_chars_allocated = 0; + + if ((line_length = get_line (&line, &line_chars_allocated, fp)) > 0) + { + /* Remove any trailing newline. */ + if (line[line_length - 1] == '\n') + line[--line_length] = '\0'; + switch (*line) + { + case 'T': + if (tagp != NULL) + *tagp = xstrdup (line + 1); + break; + case 'D': + if (datep != NULL) + *datep = xstrdup (line + 1); + break; + case 'N': + if (tagp != NULL) + *tagp = xstrdup (line + 1); + if (nonbranchp != NULL) + *nonbranchp = 1; + break; + default: + /* Silently ignore it; it may have been + written by a future version of CVS which extends the + syntax. */ + break; + } + } + + if (line_length < 0) + { + /* FIXME-update-dir: should include update_dir in messages. */ + if (feof (fp)) + error (0, 0, "cannot read %s: end of file", CVSADM_TAG); + else + error (0, errno, "cannot read %s", CVSADM_TAG); + } + + if (fclose (fp) < 0) + /* FIXME-update-dir: should include update_dir in message. */ + error (0, errno, "cannot close %s", CVSADM_TAG); + + free (line); + } + else if (!existence_error (errno)) + /* FIXME-update-dir: should include update_dir in message. */ + error (0, errno, "cannot open %s", CVSADM_TAG); +} + +/* + * This is called if all subdirectory information is known, but there + * aren't any subdirectories. It records that fact in the list + * private data. + */ + +void +Subdirs_Known (entries) + List *entries; +{ + struct stickydirtag *sdtp; + + /* If there is no list private data, that means that the + subdirectory information is known. */ + sdtp = (struct stickydirtag *) entries->list->data; + if (sdtp != NULL && ! sdtp->subdirs) + { + FILE *fp; + + sdtp->subdirs = 1; + if (!noexec) { - if ((cp = strrchr (line, '\n')) != NULL) - *cp = '\0'; - if (*line == 'T' && tagp) - *tagp = xstrdup (line + 1); - else if (*line == 'D' && datep) - *datep = xstrdup (line + 1); + /* Create Entries.Log so that Entries_Close will do something. */ + entfilename = CVSADM_ENTLOG; + fp = CVS_FOPEN (entfilename, "a"); + if (fp == NULL) + { + int save_errno = errno; + + /* As in subdir_record, just silently skip the whole thing + if there is no CVSADM directory. */ + if (! isdir (CVSADM)) + return; + error (1, save_errno, "cannot open %s", entfilename); + } + else + { + if (fclose (fp) == EOF) + error (1, errno, "cannot close %s", entfilename); + } } - (void) fclose (fp); } } + +/* Record subdirectory information. */ + +static Entnode * +subdir_record (cmd, parent, dir) + int cmd; + const char *parent; + const char *dir; +{ + Entnode *entnode; + + /* None of the information associated with a directory is + currently meaningful. */ + entnode = Entnode_Create (ENT_SUBDIR, dir, "", "", "", + (char *) NULL, (char *) NULL, + (char *) NULL); + + if (!noexec) + { + if (parent == NULL) + entfilename = CVSADM_ENTLOG; + else + { + entfilename = xmalloc (strlen (parent) + + sizeof CVSADM_ENTLOG + + 10); + sprintf (entfilename, "%s/%s", parent, CVSADM_ENTLOG); + } + + entfile = CVS_FOPEN (entfilename, "a"); + if (entfile == NULL) + { + int save_errno = errno; + + /* It is not an error if there is no CVS administration + directory. Permitting this case simplifies some + calling code. */ + + if (parent == NULL) + { + if (! isdir (CVSADM)) + return entnode; + } + else + { + sprintf (entfilename, "%s/%s", parent, CVSADM); + if (! isdir (entfilename)) + { + free (entfilename); + entfilename = NULL; + return entnode; + } + } + + error (1, save_errno, "cannot open %s", entfilename); + } + + if (fprintf (entfile, "%c ", cmd) < 0) + error (1, errno, "cannot write %s", entfilename); + + if (fputentent (entfile, entnode) != 0) + error (1, errno, "cannot write %s", entfilename); + + if (fclose (entfile) == EOF) + error (1, errno, "error closing %s", entfilename); + + if (parent != NULL) + { + free (entfilename); + entfilename = NULL; + } + } + + return entnode; +} + +/* + * Record the addition of a new subdirectory DIR in PARENT. PARENT + * may be NULL, which means the current directory. ENTRIES is the + * current entries list; it may be NULL, which means that it need not + * be updated. + */ + +void +Subdir_Register (entries, parent, dir) + List *entries; + const char *parent; + const char *dir; +{ + Entnode *entnode; + + /* Ignore attempts to register ".". These can happen in the + server code. */ + if (dir[0] == '.' && dir[1] == '\0') + return; + + entnode = subdir_record ('A', parent, dir); + + if (entries != NULL && (parent == NULL || strcmp (parent, ".") == 0)) + (void) AddEntryNode (entries, entnode); + else + Entnode_Destroy (entnode); +} + +/* + * Record the removal of a subdirectory. The arguments are the same + * as for Subdir_Register. + */ + +void +Subdir_Deregister (entries, parent, dir) + List *entries; + const char *parent; + const char *dir; +{ + Entnode *entnode; + + entnode = subdir_record ('R', parent, dir); + Entnode_Destroy (entnode); + + if (entries != NULL && (parent == NULL || strcmp (parent, ".") == 0)) + { + Node *p; + + p = findnode_fn (entries, dir); + if (p != NULL) + delnode (p); + } +} + + + +/* OK, the following base_* code tracks the revisions of the files in + CVS/Base. We do this in a file CVS/Baserev. Separate from + CVS/Entries because it needs to go in separate data structures + anyway (the name in Entries must be unique), so this seemed + cleaner. The business of rewriting the whole file in + base_deregister and base_register is the kind of thing we used to + do for Entries and which turned out to be slow, which is why there + is now the Entries.Log machinery. So maybe from that point of + view it is a mistake to do this separately from Entries, I dunno. + + We also need something analogous for: + + 1. CVS/Template (so we can update the Template file automagically + without the user needing to check out a new working directory). + Updating would probably print a message (that part might be + optional, although probably it should be visible because not all + cvs commands would make the update happen and so it is a + user-visible behavior). Constructing version number for template + is a bit hairy (base it on the timestamp on the server? Or see if + the template is in checkoutlist and if yes use its versioning and + if no don't version it?).... + + 2. cvsignore (need to keep a copy in the working directory to do + "cvs release" on the client side; see comment at src/release.c + (release). Would also allow us to stop needing Questionable. */ + +enum base_walk { + /* Set the revision for FILE to *REV. */ + BASE_REGISTER, + /* Get the revision for FILE and put it in a newly malloc'd string + in *REV, or put NULL if not mentioned. */ + BASE_GET, + /* Remove FILE. */ + BASE_DEREGISTER +}; + +static void base_walk PROTO ((enum base_walk, struct file_info *, char **)); + +/* Read through the lines in CVS/Baserev, taking the actions as documented + for CODE. */ + +static void +base_walk (code, finfo, rev) + enum base_walk code; + struct file_info *finfo; + char **rev; +{ + FILE *fp; + char *line; + size_t line_allocated; + FILE *newf; + char *baserev_fullname; + char *baserevtmp_fullname; + + line = NULL; + line_allocated = 0; + newf = NULL; + + /* First compute the fullnames for the error messages. This + computation probably should be broken out into a separate function, + as recurse.c does it too and places like Entries_Open should be + doing it. */ + baserev_fullname = xmalloc (sizeof (CVSADM_BASEREV) + + strlen (finfo->update_dir) + + 2); + baserev_fullname[0] = '\0'; + baserevtmp_fullname = xmalloc (sizeof (CVSADM_BASEREVTMP) + + strlen (finfo->update_dir) + + 2); + baserevtmp_fullname[0] = '\0'; + if (finfo->update_dir[0] != '\0') + { + strcat (baserev_fullname, finfo->update_dir); + strcat (baserev_fullname, "/"); + strcat (baserevtmp_fullname, finfo->update_dir); + strcat (baserevtmp_fullname, "/"); + } + strcat (baserev_fullname, CVSADM_BASEREV); + strcat (baserevtmp_fullname, CVSADM_BASEREVTMP); + + fp = CVS_FOPEN (CVSADM_BASEREV, "r"); + if (fp == NULL) + { + if (!existence_error (errno)) + { + error (0, errno, "cannot open %s for reading", baserev_fullname); + goto out; + } + } + + switch (code) + { + case BASE_REGISTER: + case BASE_DEREGISTER: + newf = CVS_FOPEN (CVSADM_BASEREVTMP, "w"); + if (newf == NULL) + { + error (0, errno, "cannot open %s for writing", + baserevtmp_fullname); + goto out; + } + break; + case BASE_GET: + *rev = NULL; + break; + } + + if (fp != NULL) + { + while (get_line (&line, &line_allocated, fp) >= 0) + { + char *linefile; + char *p; + char *linerev; + + if (line[0] != 'B') + /* Ignore, for future expansion. */ + continue; + + linefile = line + 1; + p = strchr (linefile, '/'); + if (p == NULL) + /* Syntax error, ignore. */ + continue; + linerev = p + 1; + p = strchr (linerev, '/'); + if (p == NULL) + continue; + + linerev[-1] = '\0'; + if (fncmp (linefile, finfo->file) == 0) + { + switch (code) + { + case BASE_REGISTER: + case BASE_DEREGISTER: + /* Don't copy over the old entry, we don't want it. */ + break; + case BASE_GET: + *p = '\0'; + *rev = xstrdup (linerev); + *p = '/'; + goto got_it; + } + } + else + { + linerev[-1] = '/'; + switch (code) + { + case BASE_REGISTER: + case BASE_DEREGISTER: + if (fprintf (newf, "%s\n", line) < 0) + error (0, errno, "error writing %s", + baserevtmp_fullname); + break; + case BASE_GET: + break; + } + } + } + if (ferror (fp)) + error (0, errno, "cannot read %s", baserev_fullname); + } + got_it: + + if (code == BASE_REGISTER) + { + if (fprintf (newf, "B%s/%s/\n", finfo->file, *rev) < 0) + error (0, errno, "error writing %s", + baserevtmp_fullname); + } + + out: + + if (line != NULL) + free (line); + + if (fp != NULL) + { + if (fclose (fp) < 0) + error (0, errno, "cannot close %s", baserev_fullname); + } + if (newf != NULL) + { + if (fclose (newf) < 0) + error (0, errno, "cannot close %s", baserevtmp_fullname); + rename_file (CVSADM_BASEREVTMP, CVSADM_BASEREV); + } + + free (baserev_fullname); + free (baserevtmp_fullname); +} + +/* Return, in a newly malloc'd string, the revision for FILE in CVS/Baserev, + or NULL if not listed. */ + +char * +base_get (finfo) + struct file_info *finfo; +{ + char *rev; + base_walk (BASE_GET, finfo, &rev); + return rev; +} + +/* Set the revision for FILE to REV. */ + +void +base_register (finfo, rev) + struct file_info *finfo; + char *rev; +{ + base_walk (BASE_REGISTER, finfo, &rev); +} + +/* Remove FILE. */ + +void +base_deregister (finfo) + struct file_info *finfo; +{ + base_walk (BASE_DEREGISTER, finfo, NULL); +} diff --git a/gnu/usr.bin/cvs/src/fileattr.c b/gnu/usr.bin/cvs/src/fileattr.c index 44987006bc8..24eda660cc0 100644 --- a/gnu/usr.bin/cvs/src/fileattr.c +++ b/gnu/usr.bin/cvs/src/fileattr.c @@ -8,11 +8,7 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + GNU General Public License for more details. */ #include "cvs.h" #include "getline.h" @@ -35,6 +31,14 @@ static int attr_read_attempted; /* Have the in-memory attributes been modified since we read them? */ static int attrs_modified; +/* More in-memory attributes: linked list of unrecognized + fileattr lines. We pass these on unchanged. */ +struct unrecog { + char *line; + struct unrecog *next; +}; +static struct unrecog *unrecog_head; + /* Note that if noone calls fileattr_get, this is very cheap. No stat(), no open(), no nothing. */ void @@ -45,13 +49,16 @@ fileattr_startdir (repos) fileattr_stored_repos = xstrdup (repos); assert (attrlist == NULL); attr_read_attempted = 0; + assert (unrecog_head == NULL); } static void fileattr_delproc (node) Node *node; { + assert (node->data != NULL); free (node->data); + node->data = NULL; } /* Read all the attributes for the current directory into memory. */ @@ -82,7 +89,7 @@ fileattr_read () strcat (fname, CVSREP_FILEATTR); attr_read_attempted = 1; - fp = fopen (fname, "r"); + fp = CVS_FOPEN (fname, FOPEN_BINARY_READ); if (fp == NULL) { if (!existence_error (errno)) @@ -93,7 +100,7 @@ fileattr_read () attrlist = getlist (); while (1) { int nread; - nread = getline (&line, &line_len, fp); + nread = get_line (&line, &line_len, fp); if (nread < 0) break; /* Remove trailing newline. */ @@ -104,13 +111,22 @@ fileattr_read () Node *newnode; p = strchr (line, '\t'); + if (p == NULL) + error (1, 0, + "file attribute database corruption: tab missing in %s", + fname); *p++ = '\0'; newnode = getnode (); newnode->type = FILEATTR; newnode->delproc = fileattr_delproc; newnode->key = xstrdup (line + 1); newnode->data = xstrdup (p); - addnode (attrlist, newnode); + if (addnode (attrlist, newnode) != 0) + /* If the same filename appears twice in the file, discard + any line other than the first for that filename. This + is the way that CVS has behaved since file attributes + were first introduced. */ + freenode (newnode); } else if (line[0] == 'D') { @@ -118,10 +134,24 @@ fileattr_read () /* Currently nothing to skip here, but for future expansion, ignore anything located here. */ p = strchr (line, '\t'); + if (p == NULL) + error (1, 0, + "file attribute database corruption: tab missing in %s", + fname); ++p; fileattr_default_attrs = xstrdup (p); } - /* else just ignore the line, for future expansion. */ + else + { + /* Unrecognized type, we want to just preserve the line without + changing it, for future expansion. */ + struct unrecog *new; + + new = (struct unrecog *) xmalloc (sizeof (struct unrecog)); + new->line = xstrdup (line); + new->next = unrecog_head; + unrecog_head = new; + } } if (ferror (fp)) error (0, errno, "cannot read %s", fname); @@ -135,8 +165,8 @@ fileattr_read () char * fileattr_get (filename, attrname) - char *filename; - char *attrname; + const char *filename; + const char *attrname; { Node *node; size_t attrname_len = strlen (attrname); @@ -149,12 +179,18 @@ fileattr_get (filename, attrname) an error message. */ return NULL; - node = findnode (attrlist, filename); - if (node == NULL) - /* A file not mentioned has no attributes. */ - return NULL; - p = node->data; - while (1) { + if (filename == NULL) + p = fileattr_default_attrs; + else + { + node = findnode (attrlist, filename); + if (node == NULL) + /* A file not mentioned has no attributes. */ + return NULL; + p = node->data; + } + while (p) + { if (strncmp (attrname, p, attrname_len) == 0 && p[attrname_len] == '=') { @@ -172,8 +208,8 @@ fileattr_get (filename, attrname) char * fileattr_get0 (filename, attrname) - char *filename; - char *attrname; + const char *filename; + const char *attrname; { char *cp; char *cpend; @@ -194,8 +230,8 @@ fileattr_get0 (filename, attrname) char * fileattr_modify (list, attrname, attrval, namevalsep, entsep) char *list; - char *attrname; - char *attrval; + const char *attrname; + const char *attrval; int namevalsep; int entsep; { @@ -290,15 +326,13 @@ fileattr_modify (list, attrname, attrval, namevalsep, entsep) void fileattr_set (filename, attrname, attrval) - char *filename; - char *attrname; - char *attrval; + const char *filename; + const char *attrname; + const char *attrval; { Node *node; char *p; - attrs_modified = 1; - if (filename == NULL) { p = fileattr_modify (fileattr_default_attrs, attrname, attrval, @@ -306,6 +340,7 @@ fileattr_set (filename, attrname, attrval) if (fileattr_default_attrs != NULL) free (fileattr_default_attrs); fileattr_default_attrs = p; + attrs_modified = 1; return; } if (attrlist == NULL) @@ -338,16 +373,100 @@ fileattr_set (filename, attrname, attrval) } p = fileattr_modify (node->data, attrname, attrval, '=', ';'); - free (node->data); if (p == NULL) delnode (node); else + { + free (node->data); node->data = p; + } + + attrs_modified = 1; +} + +char * +fileattr_getall (filename) + const char *filename; +{ + Node *node; + char *p; + + if (attrlist == NULL) + fileattr_read (); + if (attrlist == NULL) + /* Either nothing has any attributes, or fileattr_read already printed + an error message. */ + return NULL; + + if (filename == NULL) + p = fileattr_default_attrs; + else + { + node = findnode (attrlist, filename); + if (node == NULL) + /* A file not mentioned has no attributes. */ + return NULL; + p = node->data; + } + return xstrdup (p); +} + +void +fileattr_setall (filename, attrs) + const char *filename; + const char *attrs; +{ + Node *node; + + if (filename == NULL) + { + if (fileattr_default_attrs != NULL) + free (fileattr_default_attrs); + fileattr_default_attrs = xstrdup (attrs); + attrs_modified = 1; + return; + } + if (attrlist == NULL) + fileattr_read (); + if (attrlist == NULL) + { + /* Not sure this is a graceful way to handle things + in the case where fileattr_read was unable to read the file. */ + /* No attributes existed previously. */ + attrlist = getlist (); + } + + node = findnode (attrlist, filename); + if (node == NULL) + { + /* The file had no attributes. Add them if we have any to add. */ + if (attrs != NULL) + { + node = getnode (); + node->type = FILEATTR; + node->delproc = fileattr_delproc; + node->key = xstrdup (filename); + node->data = xstrdup (attrs); + addnode (attrlist, node); + } + } + else + { + if (attrs == NULL) + delnode (node); + else + { + free (node->data); + node->data = xstrdup (attrs); + } + } + + attrs_modified = 1; } void fileattr_newfile (filename) - char *filename; + const char *filename; { Node *node; @@ -384,7 +503,7 @@ writeattr_proc (node, data) fputs (node->key, fp); fputs ("\t", fp); fputs (node->data, fp); - fputs ("\n", fp); + fputs ("\012", fp); return 0; } @@ -394,6 +513,7 @@ fileattr_write () FILE *fp; char *fname; mode_t omask; + struct unrecog *p; if (!attrs_modified) return; @@ -414,7 +534,9 @@ fileattr_write () strcat (fname, "/"); strcat (fname, CVSREP_FILEATTR); - if (list_isempty (attrlist) && fileattr_default_attrs == NULL) + if (list_isempty (attrlist) + && fileattr_default_attrs == NULL + && unrecog_head == NULL) { /* There are no attributes. */ if (unlink_file (fname) < 0) @@ -431,7 +553,7 @@ fileattr_write () strcpy (fname, fileattr_stored_repos); strcat (fname, "/"); strcat (fname, CVSREP); - if (rmdir (fname) < 0) + if (CVS_RMDIR (fname) < 0) { if (errno != ENOTEMPTY @@ -447,7 +569,7 @@ fileattr_write () } omask = umask (cvsumask); - fp = fopen (fname, "w"); + fp = CVS_FOPEN (fname, FOPEN_BINARY_WRITE); if (fp == NULL) { if (existence_error (errno)) @@ -472,7 +594,7 @@ fileattr_write () } free (repname); - fp = fopen (fname, "w"); + fp = CVS_FOPEN (fname, FOPEN_BINARY_WRITE); } if (fp == NULL) { @@ -482,13 +604,25 @@ fileattr_write () } } (void) umask (omask); + + /* First write the "F" attributes. */ walklist (attrlist, writeattr_proc, fp); + + /* Then the "D" attribute. */ if (fileattr_default_attrs != NULL) { fputs ("D\t", fp); fputs (fileattr_default_attrs, fp); - fputs ("\n", fp); + fputs ("\012", fp); + } + + /* Then any other attributes. */ + for (p = unrecog_head; p != NULL; p = p->next) + { + fputs (p->line, fp); + fputs ("\012", fp); } + if (fclose (fp) < 0) error (0, errno, "cannot close %s", fname); attrs_modified = 0; @@ -498,6 +632,10 @@ fileattr_write () void fileattr_free () { + /* Note that attrs_modified will ordinarily be zero, but there are + a few cases in which fileattr_write will fail to zero it (if + noexec is set, or error conditions). This probably is the way + it should be. */ dellist (&attrlist); if (fileattr_stored_repos != NULL) free (fileattr_stored_repos); @@ -505,4 +643,11 @@ fileattr_free () if (fileattr_default_attrs != NULL) free (fileattr_default_attrs); fileattr_default_attrs = NULL; + while (unrecog_head) + { + struct unrecog *p = unrecog_head; + unrecog_head = p->next; + free (p->line); + free (p); + } } diff --git a/gnu/usr.bin/cvs/src/ignore.c b/gnu/usr.bin/cvs/src/ignore.c index f46bc5ed746..f1a3a0c88aa 100644 --- a/gnu/usr.bin/cvs/src/ignore.c +++ b/gnu/usr.bin/cvs/src/ignore.c @@ -158,7 +158,7 @@ ign_add_file (file, hold) error (0, errno, "cannot open %s", file); return; } - while (getline (&line, &line_allocated, fp) >= 0) + while (get_line (&line, &line_allocated, fp) >= 0) ign_add (line, hold); if (ferror (fp)) error (0, errno, "cannot read %s", file); diff --git a/gnu/usr.bin/cvs/src/login.c b/gnu/usr.bin/cvs/src/login.c index 8bbfe2406da..c23d31c453b 100644 --- a/gnu/usr.bin/cvs/src/login.c +++ b/gnu/usr.bin/cvs/src/login.c @@ -331,7 +331,7 @@ password_entry_operation (operation, root, newpassword) /* Check each line to see if we have this entry already. */ line = 0; - while ((line_length = getline (&linebuf, &linebuf_len, fp)) >= 0) + while ((line_length = get_line (&linebuf, &linebuf_len, fp)) >= 0) { line++; password = password_entry_parseline(cvsroot_canonical, 1, line, linebuf); @@ -400,7 +400,7 @@ process: error (1, errno, "unable to open temp file %s", tmp_name); line = 0; - while ((line_length = getline (&linebuf, &linebuf_len, fp)) >= 0) + while ((line_length = get_line (&linebuf, &linebuf_len, fp)) >= 0) { line++; if (line < found_at diff --git a/gnu/usr.bin/cvs/src/logmsg.c b/gnu/usr.bin/cvs/src/logmsg.c index 89e0ece72fa..ee7f90ed796 100644 --- a/gnu/usr.bin/cvs/src/logmsg.c +++ b/gnu/usr.bin/cvs/src/logmsg.c @@ -319,7 +319,7 @@ do_editor (dir, messagep, repository, changes) size_t offset = 0; while (1) { - line_length = getline (&line, &line_chars_allocated, fp); + line_length = get_line (&line, &line_chars_allocated, fp); if (line_length == -1) { if (ferror (fp)) @@ -348,7 +348,7 @@ do_editor (dir, messagep, repository, changes) (void) printf ("a)bort, c)ontinue, e)dit, !)reuse this message unchanged for remaining dirs\n"); (void) printf ("Action: (continue) "); (void) fflush (stdout); - line_length = getline (&line, &line_chars_allocated, stdin); + line_length = get_line (&line, &line_chars_allocated, stdin); if (line_length < 0) { error (0, errno, "cannot read from stdin"); @@ -489,7 +489,7 @@ rcsinfo_proc (repository, template) char *line = NULL; size_t line_chars_allocated = 0; - while (getline (&line, &line_chars_allocated, tfp) >= 0) + while (get_line (&line, &line_chars_allocated, tfp) >= 0) (void) fputs (line, fp); if (ferror (tfp)) error (0, errno, "warning: cannot read %s", template); diff --git a/gnu/usr.bin/cvs/src/mkmodules.c b/gnu/usr.bin/cvs/src/mkmodules.c index 48a5f4f1e2d..eb11afed42d 100644 --- a/gnu/usr.bin/cvs/src/mkmodules.c +++ b/gnu/usr.bin/cvs/src/mkmodules.c @@ -467,7 +467,7 @@ mkmodules (dir) * * comment lines begin with '#' */ - while (getline (&line, &line_allocated, fp) >= 0) + while (get_line (&line, &line_allocated, fp) >= 0) { /* skip lines starting with # */ if (line[0] == '#') diff --git a/gnu/usr.bin/cvs/src/parseinfo.c b/gnu/usr.bin/cvs/src/parseinfo.c index fbcc3a53c8c..c1482e63616 100644 --- a/gnu/usr.bin/cvs/src/parseinfo.c +++ b/gnu/usr.bin/cvs/src/parseinfo.c @@ -70,7 +70,7 @@ Parse_Info (infofile, repository, callproc, all) /* search the info file for lines that match */ callback_done = line_number = 0; - while (getline (&line, &line_allocated, fp_info) >= 0) + while (get_line (&line, &line_allocated, fp_info) >= 0) { line_number++; @@ -259,7 +259,7 @@ parse_config (cvsroot) return 0; } - while (getline (&line, &line_allocated, fp_info) >= 0) + while (get_line (&line, &line_allocated, fp_info) >= 0) { /* Skip comments. */ if (line[0] == '#') diff --git a/gnu/usr.bin/cvs/src/patch.c b/gnu/usr.bin/cvs/src/patch.c index 437c395be17..024bc5b9921 100644 --- a/gnu/usr.bin/cvs/src/patch.c +++ b/gnu/usr.bin/cvs/src/patch.c @@ -603,8 +603,8 @@ patch_fileproc (callerdat, finfo) cvs_output ("\n", 1); fp = open_file (tmpfile3, "r"); - if (getline (&line1, &line1_chars_allocated, fp) < 0 || - getline (&line2, &line2_chars_allocated, fp) < 0) + if (get_line (&line1, &line1_chars_allocated, fp) < 0 || + get_line (&line2, &line2_chars_allocated, fp) < 0) { if (feof (fp)) error (0, 0, "\ @@ -709,7 +709,7 @@ failed to read diff file header %s for %s: end of file", tmpfile3, rcs); /* spew the rest of the diff out */ while ((line_length - = getline (&line1, &line1_chars_allocated, fp)) + = get_line (&line1, &line1_chars_allocated, fp)) >= 0) cvs_output (line1, 0); if (line_length < 0 && !feof (fp)) diff --git a/gnu/usr.bin/cvs/src/release.c b/gnu/usr.bin/cvs/src/release.c index 987edd0d301..5145cb40101 100644 --- a/gnu/usr.bin/cvs/src/release.c +++ b/gnu/usr.bin/cvs/src/release.c @@ -1,31 +1,64 @@ /* * Release: "cancel" a checkout in the history log. * - * - Don't allow release if anything is active - Don't allow release if not - * above or inside repository. - Don't allow release if ./CVS/Repository is - * not the same as the directory specified in the module database. - * * - Enter a line in the history log indicating the "release". - If asked to, * delete the local working directory. */ #include "cvs.h" - -#ifndef lint -static const char rcsid[] = "$CVSid: @(#)release.c 1.23 94/09/21 $"; -USE(rcsid); -#endif - -static void release_delete PROTO((char *dir)); +#include "savecwd.h" +#include "getline.h" static const char *const release_usage[] = { - "Usage: %s %s [-d] modules...\n", + "Usage: %s %s [-d] directories...\n", "\t-d\tDelete the given directory.\n", + "(Specify the --help global option for a list of other help options)\n", NULL }; -static short delete; +#ifdef SERVER_SUPPORT +static int release_server PROTO ((int argc, char **argv)); + +/* This is the server side of cvs release. */ +static int +release_server (argc, argv) + int argc; + char **argv; +{ + int i; + + /* Note that we skip argv[0]. */ + for (i = 1; i < argc; ++i) + history_write ('F', argv[i], "", argv[i], ""); + return 0; +} + +#endif /* SERVER_SUPPORT */ + +/* There are various things to improve about this implementation: + + 1. Using run_popen to run "cvs update" could be replaced by a + fairly simple start_recursion/classify_file loop--a win for + portability, performance, and cleanliness. In particular, there is + no particularly good way to find the right "cvs". + + 2. The fact that "cvs update" contacts the server slows things down; + it undermines the case for using "cvs release" rather than "rm -rf". + However, for correctly printing "? foo" and correctly handling + CVSROOTADM_IGNORE, we currently need to contact the server. (One + idea for how to fix this is to stash a copy of CVSROOTADM_IGNORE in + the working directories; see comment at base_* in entries.c for a + few thoughts on that). + + 3. Would be nice to take processing things on the client side one step + further, and making it like edit/unedit in terms of working well if + disconnected from the network, and then sending a delayed + notification. + + 4. Having separate network turnarounds for the "Notify" request + which we do as part of unedit, and for the "release" itself, is slow + and unnecessary. */ int release (argc, argv) @@ -33,229 +66,234 @@ release (argc, argv) char **argv; { FILE *fp; - register int i, c; - char *repository, *srepos; - char line[PATH_MAX], update_cmd[PATH_MAX]; + int i, c; + char *repository; + char *line = NULL; + size_t line_allocated = 0; + char *update_cmd; char *thisarg; int arg_start_idx; + int err = 0; + short delete_flag = 0; + struct saved_cwd cwd; #ifdef SERVER_SUPPORT - if (!server_active) - { -#endif /* SERVER_SUPPORT */ - if (argc == -1) - usage (release_usage); - optind = 1; - while ((c = getopt (argc, argv, "Qdq")) != -1) - { - switch (c) - { - case 'Q': - case 'q': -#ifdef SERVER_SUPPORT - /* The CVS 1.5 client sends these options (in addition to - Global_option requests), so we must ignore them. */ - if (!server_active) + if (server_active) + return release_server (argc, argv); #endif - error (1, 0, - "-q or -Q must be specified before \"%s\"", - command_name); + + /* Everything from here on is client or local. */ + if (argc == -1) + usage (release_usage); + optind = 0; + while ((c = getopt (argc, argv, "+Qdq")) != -1) + { + switch (c) + { + case 'Q': + case 'q': + error (1, 0, + "-q or -Q must be specified before \"%s\"", + command_name); break; - case 'd': - delete++; + case 'd': + delete_flag++; break; - case '?': - default: + case '?': + default: usage (release_usage); break; - } - } - argc -= optind; - argv += optind; -#ifdef SERVER_SUPPORT - } -#endif /* SERVER_SUPPORT */ + } + } + argc -= optind; + argv += optind; /* We're going to run "cvs -n -q update" and check its output; if * the output is sufficiently unalarming, then we release with no * questions asked. Else we prompt, then maybe release. + * (Well, actually we ask no matter what. Our notion of "sufficiently + * unalarming" doesn't take into account "? foo.c" files, so it is + * up to the user to take note of them, at least currently + * (ignore-193 in testsuite)). */ /* Construct the update command. */ + update_cmd = xmalloc (strlen (program_path) + + strlen (current_parsed_root->original) + + 20); sprintf (update_cmd, "%s -n -q -d %s update", - program_name, CVSroot); + program_path, current_parsed_root->original); #ifdef CLIENT_SUPPORT /* Start the server; we'll close it after looping. */ - if (client_active) - { + if (current_parsed_root->isremote) + { start_server (); ign_setup (); - } + } #endif /* CLIENT_SUPPORT */ - /* If !server_active, we already skipped over argv[0] in the "argc - -= optind;" statement above. But if server_active, we need to - skip it now. */ -#ifdef SERVER_SUPPORT - if (server_active) - arg_start_idx = 1; - else - arg_start_idx = 0; -#endif /* SERVER_SUPPORT */ + /* Remember the directory where "cvs release" was invoked because + all args are relative to this directory and we chdir around. + */ + if (save_cwd (&cwd)) + error_exit (); + + arg_start_idx = 0; for (i = arg_start_idx; i < argc; i++) { - thisarg = argv[i]; - -#ifdef SERVER_SUPPORT - if (server_active) - { - /* Just log the release -- all the interesting stuff happened - * on the client. - */ - history_write ('F', thisarg, "", thisarg, ""); /* F == Free */ - } - else - { -#endif /* SERVER_SUPPORT */ - - /* - * If we are in a repository, do it. Else if we are in the parent of - * a directory with the same name as the module, "cd" into it and - * look for a repository there. - */ + thisarg = argv[i]; + if (isdir (thisarg)) { - if (chdir (thisarg) < 0) - { - if (!really_quiet) - error (0, 0, "can't chdir to: %s", thisarg); - continue; - } - if (!isdir (CVSADM)) - { - if (!really_quiet) - error (0, 0, "no repository module: %s", thisarg); - continue; - } + if (CVS_CHDIR (thisarg) < 0) + { + if (!really_quiet) + error (0, errno, "can't chdir to: %s", thisarg); + continue; + } + if (!isdir (CVSADM)) + { + if (!really_quiet) + error (0, 0, "no repository directory: %s", thisarg); + if (restore_cwd (&cwd, NULL)) + error_exit (); + continue; + } } else { - if (!really_quiet) - error (0, 0, "no such directory: %s", thisarg); - continue; + if (!really_quiet) + error (0, 0, "no such directory: %s", thisarg); + continue; } repository = Name_Repository ((char *) NULL, (char *) NULL); - srepos = Short_Repository (repository); - + if (!really_quiet) { - /* The "release" command piggybacks on "update", which - * does the real work of finding out if anything is not - * up-to-date with the repository. Then "release" prompts - * the user, telling her how many files have been - * modified, and asking if she still wants to do the - * release. - * - * This is "popen()" instead of "Popen()" since we - * wouldn't want the `noexec' flag to stop it. - */ - fp = popen (update_cmd, "r"); - c = 0; - - while (fgets (line, sizeof (line), fp)) - { - if (strchr ("MARCZ", *line)) - c++; - (void) printf (line); - } - - /* If the update exited with an error, then we just want to - * complain and go on to the next arg. Especially, we do - * not want to delete the local copy, since it's obviously - * not what the user thinks it is. - */ - if ((pclose (fp)) != 0) - { - error (0, 0, "unable to release `%s'", thisarg); - continue; - } - - (void) printf ("You have [%d] altered files in this repository.\n", - c); - (void) printf ("Are you sure you want to release %smodule `%s': ", - delete ? "(and delete) " : "", thisarg); - c = !yesno (); - if (c) /* "No" */ - { - (void) fprintf (stderr, "** `%s' aborted by user choice.\n", - command_name); - free (repository); - continue; - } + int line_length; + + /* The "release" command piggybacks on "update", which + does the real work of finding out if anything is not + up-to-date with the repository. Then "release" prompts + the user, telling her how many files have been + modified, and asking if she still wants to do the + release. */ + fp = run_popen (update_cmd, "r"); + if (fp == NULL) + error (1, 0, "cannot run command %s", update_cmd); + + c = 0; + + while ((line_length = get_line (&line, &line_allocated, fp)) >= 0) + { + if (strchr ("MARCZ", *line)) + c++; + (void) fputs (line, stdout); + } + if (line_length < 0 && !feof (fp)) + error (0, errno, "cannot read from subprocess"); + + /* If the update exited with an error, then we just want to + complain and go on to the next arg. Especially, we do + not want to delete the local copy, since it's obviously + not what the user thinks it is. */ + if ((pclose (fp)) != 0) + { + error (0, 0, "unable to release `%s'", thisarg); + free (repository); + if (restore_cwd (&cwd, NULL)) + error_exit (); + continue; + } + + printf ("You have [%d] altered files in this repository.\n", + c); + printf ("Are you sure you want to release %sdirectory `%s': ", + delete_flag ? "(and delete) " : "", thisarg); + c = !yesno (); + if (c) /* "No" */ + { + (void) fprintf (stderr, "** `%s' aborted by user choice.\n", + command_name); + free (repository); + if (restore_cwd (&cwd, NULL)) + error_exit (); + continue; + } + } + + if (1 +#ifdef CLIENT_SUPPORT + && !(current_parsed_root->isremote + && (!supported_request ("noop") + || !supported_request ("Notify"))) +#endif + ) + { + /* We are chdir'ed into the directory in question. + So don't pass args to unedit. */ + int argc = 1; + char *argv[3]; + argv[0] = "dummy"; + argv[1] = NULL; + err += unedit (argc, argv); } #ifdef CLIENT_SUPPORT - if (client_active) + if (current_parsed_root->isremote) { - if (fprintf (to_server, "Argument %s\n", thisarg) < 0) - error (1, errno, "writing to server"); - if (fprintf (to_server, "release\n") < 0) - error (1, errno, "writing to server"); - } + send_to_server ("Argument ", 0); + send_to_server (thisarg, 0); + send_to_server ("\012", 1); + send_to_server ("release\012", 0); + } else - { -#endif /* CLIENT_SUPPORT */ - history_write ('F', thisarg, "", thisarg, ""); /* F == Free */ -#ifdef CLIENT_SUPPORT - } /* else client not active */ #endif /* CLIENT_SUPPORT */ - + { + history_write ('F', thisarg, "", thisarg, ""); /* F == Free */ + } + free (repository); - if (delete) release_delete (thisarg); - + + if (restore_cwd (&cwd, NULL)) + error_exit (); + + if (delete_flag) + { + /* FIXME? Shouldn't this just delete the CVS-controlled + files and, perhaps, the files that would normally be + ignored and leave everything else? */ + + if (unlink_file_dir (thisarg) < 0) + error (0, errno, "deletion of directory %s failed", thisarg); + } + #ifdef CLIENT_SUPPORT - if (client_active) - return get_responses_and_close (); - else + if (current_parsed_root->isremote) + err += get_server_responses (); #endif /* CLIENT_SUPPORT */ - return (0); - -#ifdef SERVER_SUPPORT - } /* else server not active */ -#endif /* SERVER_SUPPORT */ - } /* `for' loop */ -} + } + if (restore_cwd (&cwd, NULL)) + error_exit (); + free_cwd (&cwd); -/* We want to "rm -r" the working directory, but let us be a little - paranoid. */ -static void -release_delete (dir) - char *dir; -{ - struct stat st; - ino_t ino; - int retcode = 0; - - (void) stat (".", &st); - ino = st.st_ino; - (void) chdir (".."); - (void) stat (dir, &st); - if (ino != st.st_ino) +#ifdef CLIENT_SUPPORT + if (current_parsed_root->isremote) { - error (0, 0, - "Parent dir on a different disk, delete of %s aborted", dir); - return; + /* Unfortunately, client.c doesn't offer a way to close + the connection without waiting for responses. The extra + network turnaround here is quite unnecessary other than + that.... */ + send_to_server ("noop\012", 0); + err += get_responses_and_close (); } - /* - * XXX - shouldn't this just delete the CVS-controlled files and, perhaps, - * the files that would normally be ignored and leave everything else? - */ - run_setup ("%s -fr", RM); - run_arg (dir); - if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL)) != 0) - error (0, retcode == -1 ? errno : 0, - "deletion of module %s failed.", dir); +#endif /* CLIENT_SUPPORT */ + + free (update_cmd); + if (line != NULL) + free (line); + return err; } diff --git a/gnu/usr.bin/cvs/src/repos.c b/gnu/usr.bin/cvs/src/repos.c index 85664334afe..be425b14a29 100644 --- a/gnu/usr.bin/cvs/src/repos.c +++ b/gnu/usr.bin/cvs/src/repos.c @@ -3,19 +3,21 @@ * Copyright (c) 1989-1992, Brian Berliner * * You may distribute under the terms of the GNU General Public License as - * specified in the README file that comes with the CVS 1.4 kit. - * - * Name of Repository - * - * Determine the name of the RCS repository and sets "Repository" accordingly. + * specified in the README file that comes with the CVS source distribution. */ +#include <assert.h> #include "cvs.h" +#include "getline.h" -#ifndef lint -static const char rcsid[] = "$CVSid: @(#)repos.c 1.32 94/09/23 $"; -USE(rcsid); -#endif +/* Determine the name of the RCS repository for directory DIR in the + current working directory, or for the current working directory + itself if DIR is NULL. Returns the name in a newly-malloc'd + string. On error, gives a fatal error and does not return. + UPDATE_DIR is the path from where cvs was invoked (for use in error + messages), and should contain DIR as its last component. + UPDATE_DIR can be NULL to signify the directory in which cvs was + invoked. */ char * Name_Repository (dir, update_dir) @@ -23,11 +25,10 @@ Name_Repository (dir, update_dir) char *update_dir; { FILE *fpin; - char *ret, *xupdate_dir; - char repos[PATH_MAX]; - char path[PATH_MAX]; - char tmp[PATH_MAX]; - char cvsadm[PATH_MAX]; + char *xupdate_dir; + char *repos = NULL; + size_t repos_allocated = 0; + char *tmp; char *cp; if (update_dir && *update_dir) @@ -36,52 +37,67 @@ Name_Repository (dir, update_dir) xupdate_dir = "."; if (dir != NULL) - (void) sprintf (cvsadm, "%s/%s", dir, CVSADM); - else - (void) strcpy (cvsadm, CVSADM); - - /* sanity checks */ - if (!isdir (cvsadm)) - { - error (0, 0, "in directory %s:", xupdate_dir); - error (1, 0, "there is no version here; do '%s checkout' first", - program_name); - } - - if (dir != NULL) - (void) sprintf (tmp, "%s/%s", dir, CVSADM_ENT); - else - (void) strcpy (tmp, CVSADM_ENT); - - if (!isreadable (tmp)) { - error (0, 0, "in directory %s:", xupdate_dir); - error (1, 0, "*PANIC* administration files missing"); - } - - if (dir != NULL) + tmp = xmalloc (strlen (dir) + sizeof (CVSADM_REP) + 10); (void) sprintf (tmp, "%s/%s", dir, CVSADM_REP); - else - (void) strcpy (tmp, CVSADM_REP); - - if (!isreadable (tmp)) - { - error (0, 0, "in directory %s:", xupdate_dir); - error (1, 0, "*PANIC* administration files missing"); } + else + tmp = xstrdup (CVSADM_REP); /* * The assumption here is that the repository is always contained in the * first line of the "Repository" file. */ - fpin = open_file (tmp, "r"); + fpin = CVS_FOPEN (tmp, "r"); + + if (fpin == NULL) + { + int save_errno = errno; + char *cvsadm; + + if (dir != NULL) + { + cvsadm = xmalloc (strlen (dir) + sizeof (CVSADM) + 10); + (void) sprintf (cvsadm, "%s/%s", dir, CVSADM); + } + else + cvsadm = xstrdup (CVSADM); + + if (!isdir (cvsadm)) + { + error (0, 0, "in directory %s:", xupdate_dir); + error (1, 0, "there is no version here; do '%s checkout' first", + program_name); + } + free (cvsadm); + + if (existence_error (save_errno)) + { + /* FIXME: This is a very poorly worded error message. It + occurs at least in the case where the user manually + creates a directory named CVS, so the error message + should be more along the lines of "CVS directory found + without administrative files; use CVS to create the CVS + directory, or rename it to something else if the + intention is to store something besides CVS + administrative files". */ + error (0, 0, "in directory %s:", xupdate_dir); + error (1, 0, "*PANIC* administration files missing"); + } + + error (1, save_errno, "cannot open %s", tmp); + } - if (fgets (repos, PATH_MAX, fpin) == NULL) + if (get_line (&repos, &repos_allocated, fpin) < 0) { + /* FIXME: should be checking for end of file separately. */ error (0, 0, "in directory %s:", xupdate_dir); error (1, errno, "cannot read %s", CVSADM_REP); } - (void) fclose (fpin); + if (fclose (fpin) < 0) + error (0, errno, "cannot close %s", tmp); + free (tmp); + if ((cp = strrchr (repos, '\n')) != NULL) *cp = '\0'; /* strip the newline */ @@ -90,38 +106,32 @@ Name_Repository (dir, update_dir) * one by tacking on the CVSROOT environment variable. If the CVSROOT * environment variable is not set, die now. */ - if (strcmp (repos, "..") == 0 || strncmp (repos, "../", 3) == 0) - { - error (0, 0, "in directory %s:", xupdate_dir); - error (0, 0, "`..'-relative repositories are not supported."); - error (1, 0, "illegal source repository"); - } if (! isabsolute(repos)) { - if (CVSroot == NULL) + char *newrepos; + + if (current_parsed_root == NULL) { error (0, 0, "in directory %s:", xupdate_dir); error (0, 0, "must set the CVSROOT environment variable\n"); error (0, 0, "or specify the '-d' option to %s.", program_name); error (1, 0, "illegal repository setting"); } - (void) strcpy (path, repos); - (void) sprintf (repos, "%s/%s", CVSroot, path); - } -#ifdef CLIENT_SUPPORT - if (!client_active && !isdir (repos)) -#else - if (!isdir (repos)) -#endif - { - error (0, 0, "in directory %s:", xupdate_dir); - error (1, 0, "there is no repository %s", repos); + if (pathname_levels (repos) > 0) + { + error (0, 0, "in directory %s:", xupdate_dir); + error (0, 0, "`..'-relative repositories are not supported."); + error (1, 0, "illegal source repository"); + } + newrepos = xmalloc (strlen (current_parsed_root->directory) + strlen (repos) + 2); + (void) sprintf (newrepos, "%s/%s", current_parsed_root->directory, repos); + free (repos); + repos = newrepos; } - /* allocate space to return and fill it in */ - strip_path (repos); - ret = xstrdup (repos); - return (ret); + Sanitize_Repository_Name (repos); + + return repos; } /* @@ -137,11 +147,60 @@ Short_Repository (repository) /* If repository matches CVSroot at the beginning, strip off CVSroot */ /* And skip leading '/' in rep, in case CVSroot ended with '/'. */ - if (strncmp (CVSroot, repository, strlen (CVSroot)) == 0) + if (strncmp (current_parsed_root->directory, repository, + strlen (current_parsed_root->directory)) == 0) { - char *rep = repository + strlen (CVSroot); + char *rep = repository + strlen (current_parsed_root->directory); return (*rep == '/') ? rep+1 : rep; } else return (repository); } + +/* Sanitize the repository name (in place) by removing trailing + * slashes and a trailing "." if present. It should be safe for + * callers to use strcat and friends to create repository names. + * Without this check, names like "/path/to/repos/./foo" and + * "/path/to/repos//foo" would be created. For example, one + * significant case is the CVSROOT-detection code in commit.c. It + * decides whether or not it needs to rebuild the administrative file + * database by doing a string compare. If we've done a `cvs co .' to + * get the CVSROOT files, "/path/to/repos/./CVSROOT" and + * "/path/to/repos/CVSROOT" are the arguments that are compared! + * + * This function ends up being called from the same places as + * strip_path, though what it does is much more conservative. Many + * comments about this operation (which was scattered around in + * several places in the source code) ran thus: + * + * ``repository ends with "/."; omit it. This sort of thing used + * to be taken care of by strip_path. Now we try to be more + * selective. I suspect that it would be even better to push it + * back further someday, so that the trailing "/." doesn't get into + * repository in the first place, but we haven't taken things that + * far yet.'' --Jim Kingdon (recurse.c, 07-Sep-97) + * + * Ahh, all too true. The major consideration is RELATIVE_REPOS. If + * the "/." doesn't end up in the repository while RELATIVE_REPOS is + * defined, there will be nothing in the CVS/Repository file. I + * haven't verified that the remote protocol will handle that + * correctly yet, so I've not made that change. */ + +void +Sanitize_Repository_Name (repository) + char *repository; +{ + size_t len; + + assert (repository != NULL); + + strip_trailing_slashes (repository); + + len = strlen (repository); + if (len >= 2 + && repository[len - 1] == '.' + && ISDIRSEP (repository[len - 2])) + { + repository[len - 2] = '\0'; + } +} diff --git a/gnu/usr.bin/cvs/src/root.c b/gnu/usr.bin/cvs/src/root.c index 08dab3e94a6..c9870b9e294 100644 --- a/gnu/usr.bin/cvs/src/root.c +++ b/gnu/usr.bin/cvs/src/root.c @@ -72,7 +72,7 @@ Name_Root (dir, update_dir) */ fpin = open_file (tmp, "r"); - if (getline (&root, &root_allocated, fpin) < 0) + if (get_line (&root, &root_allocated, fpin) < 0) { /* FIXME: should be checking for end of file separately; errno is not set in that case. */ diff --git a/gnu/usr.bin/cvs/src/server.c b/gnu/usr.bin/cvs/src/server.c index da7540f57bb..fa0a95ae689 100644 --- a/gnu/usr.bin/cvs/src/server.c +++ b/gnu/usr.bin/cvs/src/server.c @@ -2605,7 +2605,7 @@ check_command_legal_p (cmd_name) } else /* successfully opened readers file */ { - while ((num_red = getline (&linebuf, &linebuf_len, fp)) >= 0) + while ((num_red = get_line (&linebuf, &linebuf_len, fp)) >= 0) { /* Hmmm, is it worth importing my own readline library into CVS? It takes care of chopping @@ -2666,7 +2666,7 @@ check_command_legal_p (cmd_name) } found_it = 0; - while ((num_red = getline (&linebuf, &linebuf_len, fp)) >= 0) + while ((num_red = get_line (&linebuf, &linebuf_len, fp)) >= 0) { /* Chop newline by hand, for strcmp()'s sake. */ if (num_red > 0 && linebuf[num_red - 1] == '\n') @@ -5582,7 +5582,7 @@ check_repository_password (username, password, repository, host_user_ptr) /* Look for a relevant line -- one with this user's name. */ namelen = strlen (username); - while (getline (&linebuf, &linebuf_len, fp) >= 0) + while (get_line (&linebuf, &linebuf_len, fp) >= 0) { if ((strncmp (linebuf, username, namelen) == 0) && (linebuf[namelen] == ':')) diff --git a/gnu/usr.bin/cvs/src/subr.c b/gnu/usr.bin/cvs/src/subr.c index bb647773bcf..597b2f137f6 100644 --- a/gnu/usr.bin/cvs/src/subr.c +++ b/gnu/usr.bin/cvs/src/subr.c @@ -605,7 +605,7 @@ file_has_markers (finfo) fp = CVS_FOPEN (finfo->file, "r"); if (fp == NULL) error (1, errno, "cannot open %s", finfo->fullname); - while (getline (&line, &line_allocated, fp) > 0) + while (get_line (&line, &line_allocated, fp) > 0) { if (strncmp (line, RCS_MERGE_PAT_1, sizeof RCS_MERGE_PAT_1 - 1) == 0 || strncmp (line, RCS_MERGE_PAT_2, sizeof RCS_MERGE_PAT_2 - 1) == 0 || diff --git a/gnu/usr.bin/cvs/src/update.c b/gnu/usr.bin/cvs/src/update.c index 16bcc04bdeb..1d11d24f2d5 100644 --- a/gnu/usr.bin/cvs/src/update.c +++ b/gnu/usr.bin/cvs/src/update.c @@ -1095,7 +1095,7 @@ update_dirleave_proc (callerdat, dir, err, update_dir, entries) size_t line_allocated = 0; repository = Name_Repository ((char *) NULL, update_dir); - if (getline (&line, &line_allocated, fp) >= 0) + if (get_line (&line, &line_allocated, fp) >= 0) { if ((cp = strrchr (line, '\n')) != NULL) *cp = '\0'; diff --git a/gnu/usr.bin/cvs/src/wrapper.c b/gnu/usr.bin/cvs/src/wrapper.c index 91fdbafbbd0..1ee4e656fb9 100644 --- a/gnu/usr.bin/cvs/src/wrapper.c +++ b/gnu/usr.bin/cvs/src/wrapper.c @@ -294,7 +294,7 @@ wrap_add_file (file, temp) error (0, errno, "cannot open %s", file); return; } - while (getline (&line, &line_allocated, fp) >= 0) + while (get_line (&line, &line_allocated, fp) >= 0) wrap_add (line, temp); if (line) free (line); |