Index: src/README.RSE =================================================================== RCS file: src/README.RSE diff -N src/README.RSE --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/README.RSE 5 Oct 2005 17:43:04 -0000 @@ -0,0 +1,261 @@ + + CVS RSE Patches + =============== + + This is the patched version of CVS from Ralf S. Engelschall + - an enhanced version of the official CVS + version 1.12.13 (see http://www.nongnu.org/cvs/). + + The following changes against the vendor CVS version are provided: + - new `cvs pserverd' for running stand-alone pserver daemons + - support for an `admininfo' hook to ACL `cvs admin' commands. + - support for an `importinfo' hook to ACL `cvs import' commands. + - support for a `-h' option to `cvs diff' for compressed time spec. + - allow a hard-coded CVS super-user to override the CVS user via $CVSUSER + - support for .cvsrc files in both $HOME and working and its parent dirs + - support for $HOME/.cvsroot to alias CVSROOTs and to support root mirrors + - support global but command specific options in .cvsrc files + - support for stand-alone external custom commands `cvs ' + - support for prolog and epilog command line hooks + - support for additional `%x' variables on `loginfo' hook command lines + - support a `UMask=' variable in `$CVSROOT/CVSROOT/config' + - speeded up `cvs update' by sending whole file if smaller than the diff + - adjusted `cvs diff -rHEAD' to be consistent with other commands + - set `$LOGNAME' to the real user and not the CVS user + - use prefix 'T' ("touched/tagged") instead of 'U' ("updated") on `cvs import' + - additional SetUID/SetGID support for `cvs server' situations. + - new global --map-root=/oldpath:/newpath option for mapping root paths + - various cosmetic changes + + Some of my RSE functional patches are only useful for the server side, + others are also useful on the client side. All source patches to + *.[ch] files were entirely wrapped with ``#ifdef RSE_PATCH_ ... + #endif'' pairs. So, a particular patch is enabled by building CVS with + -DRSE_PATCH_. All patches are enabled with -DRSE_PATCHES. + + Ralf S. Engelschall + rse@engelschall.com + www.engelschall.com + ________________________________________________________________________ + + The following particular patches are available: + + RSE_PATCH_CVSRC: + In addition to processing `$HOME/.cvsrc', process also `.cvsrc' + files in the current working directory and the parent directories of + the current working directory. This allows one to use for instance a + `commit -m ""' locally (as used for GNU Pth development where CVS is + only used for plain revision control and not for bookkeeping changes + or group communication - instead a plain manually edited ChangeLog + exists) or `commit -d ' (if working with a local repository + copy). Additionally this adds support for quoted strings inside + .cvsrc files. + [Origin: Ralf S. Engelschall] + + RSE_PATCH_CVSROOT: + This adds support for a new dot-file ~/.cvsroot which is used + optionally by CVS. It can be used by the user to configure a + nickname for a CVS repository root (the master location) plus a + possibly existing local repository copy (the slave location). An + entry in ~/.cvsroot is of the format `` + [ []]''. Those entries can be either created + manually in ~/.cvsroot or with the `cvs root -e' command. + + The idea is this: if a global `-d' option is used with it is + automatically expanded to . If no global `-d' option is used, + the CVS command is checked. If it is one of the commands which are known + to CVS to modify the repository, and the $CVSROOT or CVS/Root specify a + slave location, the repository is switched to the corresponding master + location (because modifications have to be performed there). If the + command is one of the commands which are known to CVS to NOT modify the + repository, and the $CVSROOT or CVS/Root specify a master location, the + repository is switched to the corresponding slave location (because the + slave location is faster than the master location per definition). + + After a modifying operation, CVS can either run a synchronization job + automatically to bring slave in sync with master again or the user can run + `cvs root -s ' manually to perform this task. + [Origin: Ralf S. Engelschall] + + RSE_PATCH_GLOBALOPTION: + RSE_PATCH_GLOBALOPTION_PARTLY: + By default, global options in `.cvsrc' files are specified with a + `cvs' prefix. These options then apply to _all_ cvs commands. If + RSE_PATCH_GLOBALOPTION is enabled, a second pass is done where all + global options are read with prefix `cvs/' where + is the official cvs command (for instance `commit' or `checkout', + even if `ci' or `co' are used on the command line). This is useful + for instance to override CVSROOT in commit commands if using a local + repository copy. The drawback of this feature is that it obviously + slows down cvs calls, because a second pass has to be done. But + usually this feature is intended only for use with `commit', `tag', + `rtag', `history', `admin', `import' and `rdiff' commands, so if + RSE_PATCH_GLOBALOPTION_PARTLY is additionally enabled, this second + pass is only done for those commands (which is the recommended use + of this feature). + [Origin: Ralf S. Engelschall] + + RSE_PATCH_CUSTOMCMD: + This provides an additional global option `-C + :' which defines an additional CVS command + . It is intended for use on `cvs' lines inside .cvsrc + files. The effect of having `cvs -Csync:$CVSROOT/CVSROOT/sync' in a + .cvsrc file is (and assuming $CVSROOT is /e/ossp/cvs) that if you + run `cvs sync ', CVS executes `/e/ossp/cvs/CVSROOT/sync + '. So this is a way to externally extend CVS with + additional commands. + [Origin: Ralf S. Engelschall] + + RSE_PATCH_PROLOGEPILOG: + Provides the two additional CVS options `-P ' and `-E + ' for running prolog and epilog programs before and + after the usual CVS processing. This is mainly intended as local + hooks for implicitly wrapping CVS commands (without having to + create slow wrapping shell scripts, etc.). Developers usually + use it to automatically start their RSYNC command to update the + local repository copy after a commit. The is called + with four arguments: $1 is either `prolog' or `epilog' (useful + if one just wants to use a single hook program), $2 is the cvs + command (it is `commit' even `ci' is used, etc.), $3 is the current + working directory from which the cvs command was run and $4 is the + corresponding $CVSROOT variable. + [Origin: Ralf S. Engelschall] + + RSE_PATCH_EXTRAPERCENT: + This adds extra percent expansions to `loginfo' command lines: + `%o' to expand the operation (`A' = added, `M' = modified, `R' = + removed), `%t' to expand the tag, `%d' to expand the date. + [Origin: Ralf S. Engelschall] + + RSE_PATCH_READDNEW: + If a file was re-added to the repository, log the revision in the + `commitlog' as `NONE' instead of the previous dead revision. + [Origin: NetBSD] + + RSE_PATCH_CONFIGUMASK: + Provide a `UMask=' variable in `$CVSROOT/CVSROOT/config' which + overrides the umask of the CVS server process. + [Origin: OpenBSD] + + RSE_PATCH_FASTERUPDATE: + This speeds up `cvs update' by sending the whole file over the + network if it is smaller than the diff content. This is useful for + working remotely over slow Internet links. + [Origin: OpenBSD] + + RSE_PATCH_LOGNAME: + This is for SUID-based CVS servers and passes in `$LOGNAME' the real + user (first field in `$CVSROOT/CVSROOT/passwd') instead of the SUID + user (third field in `$CVSROOT/CVSROOT/passwd') + [Origin: Chris Cameron] + + RSE_PATCH_ADMININFO: + This adds the feature of an extra `$CVSROOT/CVSROOT/admininfo' + configuration file which can be used for access controlling `cvs + admin' commands similar to `cvs tag' (which is already done + with `taginfo') and `cvs commit' (which is already done with + `commitinfo'). The specified filters in this info file receive the + absolute repository directory as the first argument, followed by all + names of files in this directory on which the `cvs admin' command + should be performed. If the filter returns 0, the operation is + allowed. If it returns not 0, the operation is denied. + [Origin: Ralf S. Engelschall] + + RSE_PATCH_IMPORTINFO: + This adds the feature of an extra `$CVSROOT/CVSROOT/importinfo' + configuration file which can be used for access controlling + `cvs import'. The specified filters in this info file receives + the following arguments: the vendor branch tag, the (absolute) + repository path which is the root of the import and then zero or + more relative file paths under this repository. If the filter + returns 0, the operation is allowed. If it returns not 0, the + operation is denied. + [Origin: Ralf S. Engelschall] + + RSE_PATCH_HANDLE: + This adds a convenient `-h' option to `cvs diff'. `' + is a string of the format `[!]YYMMDDhhmmssoo' (YY=year, MM=month, + DD=day, hh=hour, mm=minute, ss=second, oo=offset) which internally + is expanded into two `-D' options. The first date is the equivalent + of `YYMMDDhhmmss', the second is the first plus `oo' seconds. If the + exclamation mark is used, the two dates are reversed. The intention + is that such a handle is a short form for a time range and can be + easily computed in a commit log mail. So the `-h' option can be used + to easily get the corresponding change diff for branch merging or + backing out. The only restriction is that time-overlapping commits + break the solution, of course. But in practice this is no real + problem. + [Origin: Ralf S. Engelschall] + + RSE_PATCH_IMPORTTOUCH: + This prints the prefix 'T' (for "touched/tagged only") instead + of 'U' (for "updated") on `cvs import' if no modifications were + imported, i.e., no new revision is comitted. This way one can + distinguish those imports from the regular updated ones which also + print 'U' and which actually commit a new revision. + [Origin: Ralf S. Engelschall] + + RSE_PATCH_CVSUSER: + This allows the Unix user RSE_PATCH_CVSUSER_CALLER (per default + "cvs") to use the environment variable CVSUSER to override the + login name CVS uses to identify the caller. This is intended for use + with a CVS setuid wrapper program or for use manually by the CVS + administrator. + [Origin: Ralf S. Engelschall] + + RSE_PATCH_SETXID: + This is a variant of CVS's SETXID_SUPPORT. It allows one to + setuid/setgid the CVS executable. Then CVS deletes these effective + uid/gid for all commands except for the "cvs server" command. For + this and only this it switches the real uid/gid to the effective + uid/gid. This way one can use the repository as a black-box. One + just has to be make sure that only :ext: (for remote) and :fork: + (for local) access is used. + [Origin: Ralf S. Engelschall] + + RSE_PATCH_PSERVERD: + This adds an additional `cvs pserverd' command which is like `cvs + pserver' except that it uses own builtin TCP/IP socket listening and + forking facility. The advantages over using inetd are: pserverd can + listen to particular host addresses/ports (inetd always binds to all + interfaces of a host), it can optionally chroot(2) for a particular + user only (usually "anonymous"), it can optionally force the global + options -l -u for a particular user only (usually "anonymous"), it + can detach into background and run as a real daemon, etc. + [Origin: Ralf S. Engelschall] + + RSE_PATCH_MAPROOT + This adds a global --map-root=/oldpath:/newpath option which + allows one to map virtual/incoming CVSROOT values to real ones. + For instance this can be used together with --allow-root and "cvs + pserverd" to map the intuitive virtual path to the physical path on + the host. + [Origin: Ralf S. Engelschall] + + RSE_PATCH_CHROOT + This adds a global --chroot=/path option which allows one to + chroot(2) to a particular directory before operating. + [Origin: Ralf S. Engelschall] + + RSE_PATCH_HASHFUNC: + This replaces the obscure hash function in src/hash.c with D.J.Berstein's + popular "times 33" function which is faster to compute and still + distributes very well. + [Origin: Ralf S. Engelschall] + + RSE_PATCH_ADDFILEATTR: + Let the default file attributes set on newly added files. + [Origin: Noel Yap] + + RSE_PATCH_CVSPID: + This provides an environment variable $CVSPID which contains the process + id of the parent CVS process. This is usually used inside scripts called + from *info files in order to have a unique session handle (for instance + for a common temporary directory "/tmp/cvs.foo.$CVSPID", etc). + [Origin: Rich Salz ] + + RSE_PATCH_BUGFIX: + This enabled various bugfixes which are still not present in the + official CVS version. + [Origin: Ralf S. Engelschall] + Index: src/add.c =================================================================== RCS file: /v/ossp/pkg/tool/cvs/cvs/cvs/src/add.c,v retrieving revision 1.1.1.15 diff -u -d -r1.1.1.15 add.c --- src/add.c 4 Sep 2005 00:41:55 -0000 1.1.1.15 +++ src/add.c 4 Oct 2005 19:23:39 -0000 @@ -839,6 +839,9 @@ li->type = T_TITLE; li->tag = xstrdup (tag); li->rev_old = li->rev_new = NULL; +#ifdef RSE_PATCH_EXTRAPERCENT + li->date = NULL; +#endif p->data = li; (void) addnode (ulist, p); Update_Logfile (rcsdir, message, NULL, ulist); Index: src/admin.c =================================================================== RCS file: /v/ossp/pkg/tool/cvs/cvs/cvs/src/admin.c,v retrieving revision 1.1.1.16 diff -u -d -r1.1.1.16 admin.c --- src/admin.c 4 Sep 2005 00:27:44 -0000 1.1.1.16 +++ src/admin.c 4 Oct 2005 19:23:39 -0000 @@ -133,6 +133,160 @@ dat->av[dat->ac++] = newelt; } +#ifdef RSE_PATCH_ADMININFO + +static List *admininfo_dlist; +static List *admininfo_flist; + +static void admininfo_dlist_delproc (Node *); +static int admininfo_info_runproc (const char *, const char *, void *); +static int admininfo_flist_runproc (Node *, void *); + +struct admininfo_dlist_st { + List *flist; +}; + +/* file callback function for recursive processing */ +static int +admininfo_fileproc (void *callerdat, struct file_info *finfo) +{ + const char *xdir; + Node *dnode; + Node *fnode; + + /* determine current directory */ + if (finfo->update_dir[0] == '\0') + xdir = "."; + else + xdir = finfo->update_dir; + + /* find directory node in directory list */ + if ((dnode = findnode(admininfo_dlist, xdir)) != NULL) + /* take already existing file list */ + admininfo_flist = ((struct admininfo_dlist_st *)dnode->data)->flist; + else + { + /* create a new file list */ + struct admininfo_dlist_st *dlist; + + admininfo_flist = getlist (); + + dlist = (struct admininfo_dlist_st *) xmalloc (sizeof(struct admininfo_dlist_st)); + dlist->flist = admininfo_flist; + + dnode = getnode (); + dnode->type = UPDATE; + dnode->key = xstrdup (xdir); + dnode->data = (char *)dlist; + dnode->delproc = admininfo_dlist_delproc; + + (void) addnode (admininfo_dlist, dnode); + } + + /* create new file node in file list */ + fnode = getnode (); + fnode->type = UPDATE; + fnode->key = xstrdup (finfo->file); + fnode->data = NULL; + fnode->delproc = NULL; + (void) addnode (admininfo_flist, fnode); + + return 0; +} + +/* delete a directory list node */ +static void +admininfo_dlist_delproc(Node *p) +{ + struct admininfo_dlist_st *dlist; + + dlist = (struct admininfo_dlist_st *)p->data; + dellist (&dlist->flist); + free (dlist); + return; +} + +/* file callback function for recursive processing (when done) */ +static int +admininfo_filesdoneproc( + void *callerdat, + int err, + const char *repos, + const char *update_dir, + List *entries) +{ + Node *dnode; + int n; + + /* find file list for update directory */ + if ((dnode = findnode(admininfo_dlist, update_dir)) != NULL) + admininfo_flist = ((struct admininfo_dlist_st *)dnode->data)->flist; + else + admininfo_flist = (List *)NULL; + if ( (admininfo_flist == NULL) + || (admininfo_flist->list->next == admininfo_flist->list)) + return err; + + /* parse and execute the admininfo configuration */ + if ((n = Parse_Info(CVSROOTADM_ADMININFO, repos, admininfo_info_runproc, PIOPT_ALL, NULL)) > 0) { + error(0, 0, "Pre-admin check failed"); + err += n; + } + + return err; +} + +/* admininfo configuration entry callback */ +static int +admininfo_info_runproc(repository, filter, closure) + const char *repository; + const char *filter; + void *closure; +{ + char *s, *cp; + int rv; + + /* if possible, do an own check to make sure that filter really exists */ + if (filter[0] == '/') { + s = xstrdup(filter); + for (cp = s; *cp; cp++) { + if (isspace((unsigned char)*cp)) { + *cp = '\0'; + break; + } + } + if (!isfile(s)) { + error (0, errno, "cannot find pre-admin filter '%s'", s); + free(s); + return (1); + } + free(s); + } + + /* construct the filter command */ + run_setup(filter); + run_add_arg(repository); + walklist(admininfo_flist, admininfo_flist_runproc, NULL); + + /* execute the filter command */ + rv = run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); + + return rv; +} + +/* file list callback for adding file as another program argument */ +static int +admininfo_flist_runproc( + Node *p, + void *closure) +{ + if (p->key != NULL) + run_add_arg(p->key); + return 0; +} + +#endif /* RSE_PATCH_ADMININFO */ + /* @@ -575,6 +729,20 @@ lock_tree_promotably (argc, argv, 0, W_LOCAL, 0); +#ifdef RSE_PATCH_ADMININFO + /* allow `CVSROOT/CVSROOT/admininfo' filters to check whether the + `cvs admin' operation is authorized for all the specified files + in the repository */ + admininfo_dlist = getlist(); + err = start_recursion(admininfo_fileproc, admininfo_filesdoneproc, + (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL, + argc, argv, 0, W_LOCAL, 0, 0, (char *)NULL, 1, (char *)NULL); + if (err) { + Lock_Cleanup(); + error(1, 0, "correct above errors first!"); + } +#endif + err = start_recursion (admin_fileproc, admin_filesdoneproc, admin_dirproc, NULL, &admin_data, Index: src/checkin.c =================================================================== RCS file: /v/ossp/pkg/tool/cvs/cvs/cvs/src/checkin.c,v retrieving revision 1.1.1.7 diff -u -d -r1.1.1.7 checkin.c --- src/checkin.c 17 Mar 2005 19:42:23 -0000 1.1.1.7 +++ src/checkin.c 3 Oct 2005 14:05:42 -0000 @@ -117,6 +117,16 @@ history_write (type, NULL, vers->vn_rcs, finfo->file, finfo->repository); +#ifdef RSE_PATCH_ADDFILEATTR + if (type == 'A') { + char *attr; + if ((attr = fileattr_getall(NULL)) != NULL) { + fileattr_setall(finfo->file, attr); + free(attr); + } + } +#endif + if (tocvsPath) if (unlink_file_dir (tocvsPath) < 0) error (0, errno, "cannot remove %s", tocvsPath); Index: src/client.c =================================================================== RCS file: /v/ossp/pkg/tool/cvs/cvs/cvs/src/client.c,v retrieving revision 1.1.1.20 diff -u -d -r1.1.1.20 client.c --- src/client.c 2 Oct 2005 15:17:20 -0000 1.1.1.20 +++ src/client.c 5 Oct 2005 17:41:53 -0000 @@ -194,13 +194,33 @@ } } +#ifdef RSE_PATCH_CVSROOT + { + int cvsroot_alias; + cvsroot_type *e; + + cvsroot_alias = 0; + if ((e = cvsroot_lookup(NULL, current_parsed_root->original, NULL)) != NULL) + if (strcmp(e->slavepath, root_string) == 0) + cvsroot_alias = 1; + if ((e = cvsroot_lookup(NULL, NULL, root_string)) != NULL) + if (strcmp(e->masterpath, current_parsed_root->original) == 0) + cvsroot_alias = 1; +#endif + /* Now check the value for root. */ if (root_string && current_parsed_root +#ifdef RSE_PATCH_CVSROOT + && !cvsroot_alias +#endif && strcmp (root_string, original_parsed_root->original)) { /* Don't send this, since the CVSROOTs don't match. */ return 1; } +#ifdef RSE_PATCH_CVSROOT + } +#endif } /* OK, let's send it. */ Index: src/commit.c =================================================================== RCS file: /v/ossp/pkg/tool/cvs/cvs/cvs/src/commit.c,v retrieving revision 1.1.1.20 diff -u -d -r1.1.1.20 commit.c --- src/commit.c 22 Sep 2005 18:20:12 -0000 1.1.1.20 +++ src/commit.c 4 Oct 2005 19:23:40 -0000 @@ -307,6 +307,9 @@ data->type = status; data->tag = xstrdup (vers->tag); data->rev_old = data->rev_new = NULL; +#ifdef RSE_PATCH_EXTRAPERCENT + data->date = xstrdup (vers->ts_user); +#endif node->type = UPDATE; node->delproc = update_delproc; @@ -1046,7 +1049,16 @@ } li->tag = xstrdup (vers->tag); +#ifdef RSE_PATCH_READDNEW + /* If the file was re-added, we want the revision in the commitlog + to be NONE, not the previous dead revision. */ + li->rev_old = status == T_ADDED ? NULL : xstrdup (vers->vn_rcs); +#else li->rev_old = xstrdup (vers->vn_rcs); +#endif +#ifdef RSE_PATCH_EXTRAPERCENT + li->date = xstrdup (vers->ts_user); +#endif li->rev_new = NULL; p->data = li; (void) addnode (ulist, p); @@ -2435,6 +2447,10 @@ free (li->rev_old); if (li->rev_new) free (li->rev_new); +#ifdef RSE_PATCH_EXTRAPERCENT + if (li->date) + free (li->date); +#endif free (li); } Index: src/create_adm.c =================================================================== RCS file: /v/ossp/pkg/tool/cvs/cvs/cvs/src/create_adm.c,v retrieving revision 1.1.1.11 diff -u -d -r1.1.1.11 create_adm.c --- src/create_adm.c 17 Mar 2005 19:42:23 -0000 1.1.1.11 +++ src/create_adm.c 3 Oct 2005 12:50:28 -0000 @@ -28,6 +28,41 @@ or after which CVS might do something non-useful. If WARN is zero, then don't print warnings; all errors are fatal then. */ +#ifdef RSE_PATCH_CVSROOT +static int local_template_cb(const char *repository, const char *template, void *closure) +{ + FILE *fpIN, *fpOUT; + char buf[1024]; + size_t n; + + if ((fpOUT = CVS_FOPEN(CVSADM_TEMPLATE, "w+")) == NULL) + error(1, errno, "cannot open %s for writing", CVSADM_TEMPLATE); + if ((fpIN = CVS_FOPEN(template, "r")) == NULL) + error(1, errno, "cannot open %s for reading", template); + while (!feof(fpIN)) { + n = fread(buf, 1, sizeof buf, fpIN); + if (n == 0) { + if (ferror(fpIN)) + error(0, errno, "cannot read template file %s", template); + break; + } + fwrite(buf, 1, n, fpOUT); + } + fclose(fpIN); + fclose(fpOUT); + return 0; +} + +static void local_template(const char *update_dir, const char *repository) +{ + cvsroot_type *e; + + if ((e = cvsroot_lookup(NULL, NULL, current_parsed_root->original)) != NULL) + Parse_Info(CVSROOTADM_RCSINFO, repository, local_template_cb, PIOPT_ALL, NULL); + return; +} +#endif + int Create_Admin (const char *dir, const char *update_dir, const char *repository, const char *tag, const char *date, int nonbranch, int warn, @@ -163,6 +198,20 @@ /* Create a new CVS/Tag file */ WriteTag (dir, tag, date, nonbranch, update_dir, repository); +#ifdef RSE_PATCH_CVSROOT + /* Under our "cvs root" feature, checkouts are performed + locally (from the repository copy and without C/S), but commits + are performed remotely (to the master repository with C/S). + Unfortunately, CVS "optimizes" processing and doesn't provide + CVS/Template files on non-C/S checkouts. This would mean that + if "cvs root" feature is used, the rcsinfo-configured templates + are never used. So, if and only if we do a non-C/S checkout (or + similar operation which creates CVS/Template) _and_ the current + CVSROOT is known to be a repository copy, we force the creation + of CVS/Template. */ + if (!server_active && !(current_parsed_root->isremote) && dotemplate) + local_template(update_dir, repository); +#endif TRACE (TRACE_FUNCTION, "Create_Admin"); free (reposcopy); Index: src/cvs.h =================================================================== RCS file: /v/ossp/pkg/tool/cvs/cvs/cvs/src/cvs.h,v retrieving revision 1.1.1.20 diff -u -d -r1.1.1.20 cvs.h --- src/cvs.h 2 Oct 2005 15:17:20 -0000 1.1.1.20 +++ src/cvs.h 12 Oct 2005 18:12:24 -0000 @@ -12,6 +12,36 @@ */ /* + * Support for compiling in various RSE extension + */ +#ifdef RSE_PATCHES +#define RSE_PATCH_CVSRC +#define RSE_PATCH_CVSROOT +#define RSE_PATCH_GLOBALOPTION +#define RSE_PATCH_GLOBALOPTION_PARTLY +#define RSE_PATCH_CUSTOMCMD +#define RSE_PATCH_PROLOGEPILOG +#define RSE_PATCH_EXTRAPERCENT +#define RSE_PATCH_READDNEW +#define RSE_PATCH_CONFIGUMASK +#define RSE_PATCH_FASTERUPDATE +#define RSE_PATCH_LOGNAME +#define RSE_PATCH_IMPORTINFO +#define RSE_PATCH_ADMININFO +#define RSE_PATCH_HANDLE +#define RSE_PATCH_IMPORTTOUCH +#define RSE_PATCH_CVSUSER +#define RSE_PATCH_SETXID +#define RSE_PATCH_PSERVERD +#define RSE_PATCH_MAPROOT +#define RSE_PATCH_CHROOT +#define RSE_PATCH_HASHFUNC +#define RSE_PATCH_ADDFILEATTR +#define RSE_PATCH_CVSPID +#define RSE_PATCH_BUGFIX +#endif + +/* * basic information used in all source files * */ @@ -184,6 +214,33 @@ #define CVSROOTADM_VERIFYMSG "verifymsg" #define CVSROOTADM_WRAPPER "cvswrappers" #define CVSROOTADM_WRITERS "writers" +#ifdef RSE_PATCH_ADMININFO +#define CVSROOTADM_ADMININFO "admininfo" +#endif +#ifdef RSE_PATCH_IMPORTINFO +#define CVSROOTADM_IMPORTINFO "importinfo" +#endif + +#ifdef RSE_PATCH_ALTADMINFILES +#define CVSROOTADM_MODULES_ALT "conf_modules" +#define CVSROOTADM_LOGINFO_ALT "hook_logwrite" +#define CVSROOTADM_RCSINFO_ALT "hook_logtempl" +#define CVSROOTADM_COMMITINFO_ALT "hook_commit" +#define CVSROOTADM_TAGINFO_ALT "hook_tag" +#define CVSROOTADM_EDITINFO_ALT "hook_logedit" +#define CVSROOTADM_VERIFYMSG_ALT "hook_logverify" +#define CVSROOTADM_HISTORY_ALT "data_history" +#define CVSROOTADM_VALTAGS_ALT "data_valtags" +#define CVSROOTADM_IGNORE_ALT "conf_ignore" +#define CVSROOTADM_CHECKOUTLIST_ALT "conf_checkout" +#define CVSROOTADM_WRAPPER_ALT "hook_wrapper" +#define CVSROOTADM_NOTIFY_ALT "hook_notify" +#define CVSROOTADM_USERS_ALT "conf_users" +#define CVSROOTADM_READERS_ALT "conf_readers" +#define CVSROOTADM_WRITERS_ALT "conf_writers" +#define CVSROOTADM_PASSWD_ALT "conf_passwd" +#define CVSROOTADM_CONFIG_ALT "conf_global" +#endif #define CVSNULLREPOS "Emptydir" /* an empty directory */ @@ -476,6 +533,27 @@ const char *Short_Repository (const char *repository); void Sanitize_Repository_Name (char *repository); +#ifdef RSE_PATCH_MAPROOT +void root_map_add (char *, char *); +void root_map_free (void); +int root_map_it (char *, char **, int); +#endif + +#ifdef RSE_PATCH_CVSROOT +typedef struct { + char *nickname; + char *masterpath; + char *slavepath; + char *syncprog; +} cvsroot_type; +char *cvsroot_filename(void); +void cvsroot_free(cvsroot_type *); +cvsroot_type *cvsroot_entry_read(FILE *); +void cvsroot_entry_write(FILE *, cvsroot_type *); +cvsroot_type *cvsroot_lookup(char *, char *, char *); +void cvsroot_synchronize(cvsroot_type *, int); +#endif + char *entries_time (time_t unixtime); time_t unix_time_stamp (const char *file); char *time_stamp (const char *file); @@ -613,6 +691,14 @@ void expand_wild (int argc, char **argv, int *pargc, char ***pargv); +#ifdef RSE_PATCH_HANDLE +int handle2dates(char *, time_t *, time_t *); +#endif + +#ifdef SERVER_SUPPORT +int cvs_casecmp (const char *, const char *); +#endif + /* exithandle.c */ void signals_register (RETSIGTYPE (*handler)(int)); void cleanup_register (void (*handler) (void)); @@ -818,6 +904,9 @@ NULL for add or import */ char *rev_new; /* rev number after a commit/modify, add, or import, NULL for remove */ +#ifdef RSE_PATCH_EXTRAPERCENT + char *date; +#endif }; /* Wrappers. */ @@ -858,6 +947,13 @@ int unedit (int argc, char **argv); int editors (int argc, char **argv); int watchers (int argc, char **argv); +#ifdef RSE_PATCH_CVSROOT +int root (int argc, char **argv); +#endif +#ifdef RSE_PATCH_PSERVERD +int pserverd (int argc, char **argv); +int pserver_daemon (int argc, char **argv); +#endif int annotate (int argc, char **argv); int add (int argc, char **argv); int admin (int argc, char **argv); @@ -917,6 +1013,10 @@ void cvs_flushout (void); void cvs_output_tagged (const char *, const char *); +#ifdef RSE_PATCH_BUGFIX +extern int rpl_lstat (const char *file, struct stat *sbuf); +#endif + extern const char *global_session_id; /* From find_names.c. */ Index: src/cvsrc.c =================================================================== RCS file: /v/ossp/pkg/tool/cvs/cvs/cvs/src/cvsrc.c,v retrieving revision 1.1.1.6 diff -u -d -r1.1.1.6 cvsrc.c --- src/cvsrc.c 16 Mar 2005 19:05:33 -0000 1.1.1.6 +++ src/cvsrc.c 3 Oct 2005 12:50:08 -0000 @@ -17,6 +17,203 @@ #include "cvs.h" #include "getline.h" +#ifdef RSE_PATCH_CVSRC + +#include +#include + +static char *strq_start = NULL; + +static char * +strqtok_cchar( + register char *s, + register char *c, + int *backslashed) +{ + register char ch; + + if ((*backslashed = (*s == '\\'))) { + switch (*++s) { + case 'a': + *c = '\a'; + break; + case 'b': + *c = '\b'; + break; + case 'f': + *c = '\f'; + break; + case 'n': + *c = '\n'; + break; + case 'r': + *c = '\r'; + break; + case 't': + *c = '\t'; + break; + case 'v': + *c = '\v'; + break; + case '\\': + *c = '\\'; + break; + case '^': + *c = '^'; + break; + case '\'': + *c = '\''; + break; + case '"': + *c = '"'; + break; + case '?': + *c = '?'; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + ch = 0; + if (isdigit(*s) && *s != '8' && *s != '9') { + ch = *s++ - '0'; + if (isdigit(*s) && *s != '8' && *s != '9') { + ch <<= 3; + ch |= *s++ - '0'; + if (isdigit(*s) && *s != '8' && *s != '9') { + ch <<= 3; + ch |= *s++ - '0'; + } + } + } + s--; + *c = ch; + break; + case 'x': + s++; + for (ch = 0; isxdigit(*s); s++) { + ch <<= 4; + ch |= isdigit(*s) ? *s - '0' : + islower(*s) ? *s + 10 - 'a' : *s + 10 - 'A'; + } + s--; + *c = ch; + break; + default: + *c = *s; + break; + } + } else + *c = *s; + return (*s) ? s+1 : NULL; +} + +static char * +strqtok( + char *s, /* String to tokenize. NULL to continue same str */ + char *delim, /* Token delimiters. Can be changed w/ each call. */ + char *quotemarks, /* Quotation marks. Can be changed w/ each call. */ + char *commentchars, /* Comment characters. Can be changed w/ each call. */ + unsigned int flags) /* flags&01 -> strip quotes; + * flags&02 -> enable backslash escapes; + * flags&04 -> skip all delims before return; + */ +{ + register char *p, *q; + char c; + char leftquote = 0; + char *token; + int backslashed, inquote, intok; + + int stripquote = flags & 01; /* strip quotemarks from tokens */ + int backslash = flags & 02; /* backslash sequences */ + int skipdelim = flags & 04; /* skip seq of delims at end of token */ + + /* New string? */ + if (s) + strq_start = s; + if (!strq_start) + return NULL; + + /* Skip leading delimiters */ + for (p=strq_start; *p && strchr(delim, *p); p++) + ; + if (!(*p) || strchr(commentchars, *p)) + return NULL; + + /* Set `token' to point to returned string. + * Use p and q to walk through the user's string: + * p will follow input characters; + * q will overwrite w/ outputted characters, minus possibly-stripped + * quotes and including nulls after each token. + */ + token = q = p; + inquote = 0; + intok = 1; + if (backslash) { + while (intok && (p = strqtok_cchar(p, &c, &backslashed))) { + if (backslashed) { + *q++ = c; /* treat as plain character */ + } else if (!inquote && *delim && strchr(delim, c)) { + *q = '\0'; /* Reached end of token */ + intok = 0; + } else if (!inquote && *commentchars && strchr(commentchars, c)) { + *q = '\0'; /* Reached end of token */ + *p = '\0'; /* make it act like end of string */ + intok = 0; + } else if (!inquote && *quotemarks && strchr(quotemarks, c)) { + inquote = 1; /* Beginning a quoted segment */ + leftquote = c; /* Save quote char for matching with */ + if (!stripquote) *q++ = c; + } else if (inquote && leftquote == c) { + inquote = 0; /* Ending a quoted segment */ + if (!stripquote) *q++ = c; + } else { + *q++ = c; /* Ordinary character */ + } + } + strq_start = p; /* Where to start next search */ + *q = '\0'; + } else { + while (intok && *p) { + if (!inquote && *delim && strchr(delim, *p)) { + *q = '\0'; /* Reached end of token */ + p++; /* advance p for next token */ + intok = 0; + } else if (!inquote && *commentchars && strchr(commentchars, *p)) { + *q = '\0'; /* Reached end of token */ + *p = '\0'; /* make it act like end of string */ + intok = 0; + } else if (!inquote && *quotemarks && strchr(quotemarks, *p)) { + inquote = 1; /* Beginning a quoted segment */ + leftquote = *p++; /* Save quote char for matching with */ + if (!stripquote) *q++ = leftquote; + } else if (inquote && leftquote == *p) { + inquote = 0; /* Ending a quoted segment */ + p++; + if (!stripquote) *q++ = leftquote; + } else { + *q++ = *p++; + } + } + strq_start = p; /* Where to start next search */ + *q = '\0'; + } + + if (skipdelim && strq_start) { + /* Skip trailing delimiters */ + while (*strq_start && strchr(delim, *strq_start)) + strq_start++; + } + return token; +} + +#endif + /* this file is to be found in the user's home directory */ #ifndef CVSRC_FILENAME @@ -26,13 +223,67 @@ #define GROW 10 +#ifdef RSE_PATCH_CVSRC +static void read_cvsrc_parentdirs (); +static void read_cvsrc_file (); +#endif + /* Read cvsrc, processing options matching CMDNAME ("cvs" for global options, and update *ARGC and *ARGV accordingly. */ +#ifdef RSE_PATCH_CVSRC +void +read_cvsrc (int *argc, char ***argv, const char *cmdname) +{ + /* try to read .cvsrc files from parent directories */ + read_cvsrc_parentdirs(argc, argv, cmdname); + + /* try to read .cvsrc file from home directory */ + read_cvsrc_file(argc, argv, cmdname, get_homedir()); + + return; +} + +/* read .cvsrc files from all parent directories (including the current dir) */ +static void +read_cvsrc_parentdirs (int *argc, char ***argv, const char *cmdname) +{ + char cwd[PATH_MAX]; + char *cp; + int l; + + if (getcwd(cwd, sizeof(cwd)) == NULL) + return; + if ((l = strlen(cwd)) <= 0) + return; + if (cwd[l-1] != '/') { + cwd[l++] = '/'; + cwd[l++] = '\0'; + } + while (cwd[0] != '\0') { + cwd[strlen(cwd)-1] = '\0'; + read_cvsrc_file(argc, argv, cmdname, cwd); + if ((cp = strrchr(cwd, '/')) == NULL) + break; + *(cp+1) = '\0'; + } + return; +} + +/* read .cvsrc file from a particular directory */ +static void +read_cvsrc_file (argc, argv, cmdname, homedir) + int *argc; + char ***argv; + char *cmdname; + char *homedir; +{ +#else void read_cvsrc (int *argc, char ***argv, const char *cmdname) { char *homedir; +#endif char *homeinit; FILE *cvsrcfile; @@ -64,7 +315,9 @@ /* determine filename for ~/.cvsrc */ +#ifndef RSE_PATCH_CVSRC homedir = get_homedir (); +#endif /* 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 @@ -120,9 +373,15 @@ if (found) { /* skip over command in the options line */ +#ifdef RSE_PATCH_CVSRC + for (optstart = strqtok (line + command_len, "\t \n", "\"'", "", 7); + optstart; + optstart = strqtok (NULL, "\t \n", "\"'", "", 7)) +#else for (optstart = strtok (line + command_len, "\t \n"); optstart; optstart = strtok (NULL, "\t \n")) +#endif { new_argv [new_argc++] = xstrdup (optstart); Index: src/diff.c =================================================================== RCS file: /v/ossp/pkg/tool/cvs/cvs/cvs/src/diff.c,v retrieving revision 1.1.1.14 diff -u -d -r1.1.1.14 diff.c --- src/diff.c 27 May 2005 18:07:48 -0000 1.1.1.14 +++ src/diff.c 4 Oct 2005 19:23:40 -0000 @@ -320,7 +320,11 @@ * to diff. */ while ((c = getopt_long (argc, argv, +#if defined(RSE_PATCH_HANDLE) + "+abcdefh:ilnpstuwy0123456789BHNRTC:D:F:I:L:U:W:k:r:", +#else "+abcdefhilnpstuwy0123456789BHNRTC:D:F:I:L:U:W:k:r:", +#endif longopts, &option_index)) != -1) { switch (c) @@ -329,7 +333,11 @@ add_diff_args (0, "side-by-side", NULL); break; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': +#if defined(RSE_PATCH_HANDLE) + case 'i': case 'n': case 'p': case 's': case 't': +#else case 'h': case 'i': case 'n': case 'p': case 's': case 't': +#endif case 'u': case 'w': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': @@ -388,6 +396,17 @@ else diff_date1 = Make_Date (optarg); break; +#if defined(RSE_PATCH_HANDLE) + case 'h': { + time_t t1, t2; + if (!handle2dates(optarg, &t1, &t2)) + error (1, 0, "invalid handle string"); + t1 -= 1; /* subtract one second to have a real difference */ + diff_date1 = date_from_time_t(t1); + diff_date2 = date_from_time_t(t2); + break; + } +#endif case 'N': empty_files = 1; break; Index: src/hash.c =================================================================== RCS file: /v/ossp/pkg/tool/cvs/cvs/cvs/src/hash.c,v retrieving revision 1.1.1.9 diff -u -d -r1.1.1.9 hash.c --- src/hash.c 9 May 2005 18:22:12 -0000 1.1.1.9 +++ src/hash.c 4 Oct 2005 19:23:40 -0000 @@ -28,17 +28,25 @@ hashp (const char *key) { unsigned int h = 0; +#ifndef RSE_PATCH_HASHFUNC unsigned int g; +#endif assert(key != NULL); while (*key != 0) { +#ifdef RSE_PATCH_HASHFUNC + /* D.J. Bernstein's popular times 33 function + (fast and distributes very well) */ + h = ((h << 5) + h) + FOLD_FN_CHAR(*key++); +#else unsigned int c = *key++; /* The FOLD_FN_CHAR is so that findnode_fn works. */ h = (h << 4) + FOLD_FN_CHAR (c); if ((g = h & 0xf0000000) != 0) h = (h ^ (g >> 24)) ^ g; +#endif } return h % HASHSIZE; Index: src/import.c =================================================================== RCS file: /v/ossp/pkg/tool/cvs/cvs/cvs/src/import.c,v retrieving revision 1.1.1.13 diff -u -d -r1.1.1.13 import.c --- src/import.c 4 Sep 2005 00:27:44 -0000 1.1.1.13 +++ src/import.c 11 Jan 2006 19:57:27 -0000 @@ -70,6 +70,152 @@ NULL }; +#ifdef RSE_PATCH_IMPORTINFO + +static char *importinfo_vtag; + +static int +importinfo_descend(thisdir) + char *thisdir; +{ + DIR *dirp; + struct dirent *dp; + int err = 0; + List *dirlist = NULL; + char *fullname = NULL; + + if ((dirp = CVS_OPENDIR(thisdir)) == NULL) { + error(0, errno, "cannot open directory"); + err++; + } + else { + errno = 0; + while ((dp = CVS_READDIR(dirp)) != NULL) { + if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) + goto one_more_time_boys; + if (strcmp(dp->d_name, CVSADM) == 0) + goto one_more_time_boys; + if (ign_name(dp->d_name)) + goto one_more_time_boys; + if (fullname != NULL) { + free(fullname); + fullname = NULL; + } + fullname = xmalloc(strlen(thisdir) + 1 + strlen(dp->d_name)+1); + (void)sprintf(fullname, "%s/%s", thisdir, dp->d_name); + if ( +#ifdef DT_DIR + (dp->d_type == DT_DIR || (dp->d_type == DT_UNKNOWN && isdir (fullname))) +#else + isdir (fullname) +#endif + && !wrap_name_has(dp->d_name, WRAP_TOCVS) + ) { + Node *n; + if (dirlist == NULL) + dirlist = getlist(); + n = getnode(); + n->key = xstrdup(dp->d_name); + addnode(dirlist, n); + } + else if ( +#ifdef DT_DIR + dp->d_type == DT_LNK || (dp->d_type == DT_UNKNOWN && islink (fullname)) +#else + islink (fullname) +#endif + ) { + err++; + } + else { + if (strcmp(thisdir, ".") == 0) { + run_add_arg(dp->d_name); + } + else { + char *p; + p = xmalloc(strlen(thisdir)+1+strlen(dp->d_name)+1); + (void)sprintf(p, "%s/%s", thisdir, dp->d_name); + run_add_arg(p); + free(p); + } + } + one_more_time_boys: + errno = 0; + } + if (fullname != NULL) { + free(fullname); + fullname = NULL; + } + if (errno != 0) { + error(0, errno, "cannot read directory"); + err++; + } + (void)CVS_CLOSEDIR(dirp); + } + if (dirlist != NULL) { + Node *head, *p; + head = dirlist->list; + for (p = head->next; p != head; p = p->next) { + if (strcmp(thisdir, ".") == 0) { + err += importinfo_descend(p->key); + } + else { + char *nextdir; + nextdir = xmalloc(strlen(thisdir)+1+strlen(p->key)+1); + (void)sprintf(nextdir, "%s/%s", thisdir, p->key); + err += importinfo_descend(nextdir); + free(nextdir); + } + } + dellist(&dirlist); + } + return err; +} + +/* importinfo configuration entry callback */ +static int +importinfo_runproc(repository, filter, closure) + char *repository; + char *filter; + void *closure; +{ + char *s, *cp; + int rv; + + /* if possible, do an own check to make sure that filter really exists */ + if (filter[0] == '/') { + s = xstrdup(filter); + for (cp = s; *cp; cp++) { + if (isspace((unsigned char)*cp)) { + *cp = '\0'; + break; + } + } + if (!isfile(s)) { + error (0, errno, "cannot find pre-admin filter '%s'", s); + free(s); + return (1); + } + free(s); + } + + /* construct the filter command */ + run_setup(filter); + run_add_arg(importinfo_vtag); + run_add_arg(repository); + ign_add_file(CVSDOTIGNORE, 1); + wrap_add_file(CVSDOTWRAPPER, 1); + rv = importinfo_descend("."); + if (rv > 0) + return rv; + + /* execute the filter command */ + rv = run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); + + return rv; +} +#endif + int import (int argc, char **argv) { @@ -320,6 +466,12 @@ error (1, 0, "attempt to import the repository"); } +#ifdef RSE_PATCH_IMPORTINFO + importinfo_vtag = argv[1]; + if (Parse_Info(CVSROOTADM_IMPORTINFO, argv[0], importinfo_runproc, PIOPT_ALL, NULL) > 0) + error(1, 0, "Pre-import check failed"); +#endif + ulist = getlist (); p = getnode (); p->type = UPDATE; @@ -329,6 +481,9 @@ li->type = T_TITLE; li->tag = xstrdup (vbranch); li->rev_old = li->rev_new = NULL; +#ifdef RSE_PATCH_EXTRAPERCENT + li->date = NULL; +#endif p->data = li; (void) addnode (ulist, p); do_verify (&message, repository, ulist); @@ -719,7 +874,11 @@ */ if (add_tags (vers->srcfile, vfile, vtag, targc, targv)) retval = 1; +#ifdef RSE_PATCH_IMPORTTOUCH + add_log ('T', vfile); +#else add_log ('U', vfile); +#endif freevers_ts (&vers); return retval; } Index: src/logmsg.c =================================================================== RCS file: /v/ossp/pkg/tool/cvs/cvs/cvs/src/logmsg.c,v retrieving revision 1.1.1.13 diff -u -d -r1.1.1.13 logmsg.c --- src/logmsg.c 4 Sep 2005 00:27:44 -0000 1.1.1.13 +++ src/logmsg.c 4 Oct 2005 19:23:40 -0000 @@ -618,6 +618,9 @@ const char *f; char *d; size_t doff; +#ifdef RSE_PATCH_EXTRAPERCENT + char buf[64]; +#endif if (p->data == NULL) return 1; @@ -628,6 +631,36 @@ { switch (*f++) { +#ifdef RSE_PATCH_EXTRAPERCENT + case 'o': { + li = p->data; + switch (li->type) { + case T_ADDED: buf[0] = 'A'; break; + case T_MODIFIED: buf[0] = 'M'; break; + case T_REMOVED: buf[0] = 'R'; break; + default: buf[0] = '?'; break; + } + buf[1] = '\0'; + arg = buf; + break; + } + case 't': { + li = p->data; + arg = (li->tag ? li->tag : ""); + break; + } + case 'd': { + struct timespec ts; + li = p->data; + if (li->date != NULL) { + if (get_date(&ts, li->date, NULL)) { + ts.tv_sec += 1; /* re-adjust because of fudge */ + sprintf(buf, "%ld", (long)ts.tv_sec); + } + } + break; + } +#endif case 's': arg = p->key; break; Index: src/main.c =================================================================== RCS file: /v/ossp/pkg/tool/cvs/cvs/cvs/src/main.c,v retrieving revision 1.1.1.19 diff -u -d -r1.1.1.19 main.c --- src/main.c 2 Oct 2005 15:17:21 -0000 1.1.1.19 +++ src/main.c 7 Oct 2005 11:17:19 -0000 @@ -28,6 +28,10 @@ const char *program_path; const char *cvs_cmd_name; +#ifdef RSE_PATCH_CHROOT +static char *chrootdir = NULL; +#endif + const char *global_session_id; /* Random session ID */ char *hostname; @@ -57,6 +61,11 @@ mode_t cvsumask = UMASK_DFLT; +#ifdef RSE_PATCH_PROLOGEPILOG +char *cvs_prolog = NULL; +char *cvs_epilog = NULL; +#endif + char *CurDir; /* @@ -156,6 +165,9 @@ { "login", "logon", "lgn", login, 0 }, { "logout", NULL, NULL, logout, 0 }, #endif /* AUTH_CLIENT_SUPPORT */ +#ifdef RSE_PATCH_PSERVERD + { "pserverd", NULL, NULL, pserverd }, +#endif { "ls", "dir", "list", ls, 0 }, #if (defined(AUTH_SERVER_SUPPORT) || defined (HAVE_GSSAPI)) && defined(SERVER_SUPPORT) { "pserver", NULL, NULL, server, CVS_CMD_MODIFIES_REPOSITORY | CVS_CMD_USES_WORK_DIR }, /* placeholder */ @@ -177,9 +189,53 @@ { "version", "ve", "ver", version, 0 }, { "watch", NULL, NULL, watch, CVS_CMD_MODIFIES_REPOSITORY | CVS_CMD_USES_WORK_DIR }, { "watchers", NULL, NULL, watchers, CVS_CMD_USES_WORK_DIR }, +#ifdef RSE_PATCH_CVSROOT + { "root", "ro", "repo", root, CVS_CMD_IGNORE_ADMROOT }, +#endif { NULL, NULL, NULL, NULL, 0 }, }; +#ifdef RSE_PATCH_CUSTOMCMD + +/* the table of custom commands */ +#define CUSTOMCMD_MAX 20 +static int customcmd_num = 0; +struct customcmd { + char *name; + char *command; +}; +static struct customcmd customcmd_tab[CUSTOMCMD_MAX]; + +/* the internal handler function for custom commands */ +static int +customcmd_run(argc, argv) + int argc; + char **argv; +{ + int i; + char *cmd; + + /* support for `cvs -H ' */ + if (argc == -1) { + (void)fprintf(stderr, "Usage: %s %s [command arguments]\n", + program_name, cvs_cmd_name); + exit (EXIT_FAILURE); + } + + /* execute the command */ + cmd = expand_path(argv[0], current_parsed_root && current_parsed_root->directory ? current_parsed_root->directory : "/", false, cvs_cmd_name, 0); + run_setup(cmd); + for (i = 1; i < argc; i++) + run_add_arg(argv[i]); + if (run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL) != 0) + error(1, 0, "program `%s' of custom command `%s' returned non-zero", + cmd, cvs_cmd_name); + free(cmd); + + return 0; +} +#endif + static const char *const usg[] = { /* CVS usage messages never have followed the GNU convention of @@ -249,6 +305,9 @@ " login Prompt for password for authenticating server\n", " logout Removes entry in .cvspass for remote repository\n", #endif /* AUTH_CLIENT_SUPPORT */ +#ifdef RSE_PATCH_PSERVERD + " pserverd Password server daemon\n", +#endif " ls List files available from CVS\n", #if (defined(AUTH_SERVER_SUPPORT) || defined (HAVE_GSSAPI)) && defined(SERVER_SUPPORT) " pserver Password server mode\n", @@ -270,6 +329,9 @@ " version Show current CVS version(s)\n", " watch Set watches\n", " watchers See who is watching a file\n", +#ifdef RSE_PATCH_CVSROOT + " root Maintain repository root locations\n", +#endif "(Specify the --help option for a list of other help options)\n", NULL, }; @@ -299,6 +361,10 @@ #endif " -a Authenticate all net traffic.\n", #endif +#ifdef RSE_PATCH_PROLOGEPILOG + " -P program Run prolog program before processing.\n", + " -E program Run epilog program after processing.\n", +#endif " -s VAR=VAL Set CVS user variable.\n", "(Specify the --help option for a list of other help options)\n", NULL @@ -364,6 +430,20 @@ if (strcmp (cmd_name, cm->fullname) == 0) break; } +#ifdef RSE_PATCH_CUSTOMCMD + { + int i; + unsigned long int ret = 0; + for (i = 0; i < customcmd_num; i++) { + if (strcmp(customcmd_tab[i].name, cmd_name) == 0) { + ret |= CVS_CMD_IGNORE_ADMROOT; + ret &= ~(CVS_CMD_USES_WORK_DIR); + ret &= ~(CVS_CMD_MODIFIES_REPOSITORY); + return ret; + } + } + } +#endif if (!cm->fullname) error (1, 0, "unknown command: %s", cmd_name); return cm->attr; @@ -507,11 +587,30 @@ const struct cmd *cm; int c, err = 0; int free_Editor = 0; +#ifdef RSE_PATCH_CVSROOT + cvsroot_type *cvsroot_sync = NULL; + int cvsroot_cmdline_isreal = 0; +#endif +#if defined(RSE_PATCH_CVSROOT) || defined(RSE_PATCH_CUSTOMCMD) + int standalone_command = 0; +#endif int help = 0; /* Has the user asked for help? This lets us support the `cvs -H cmd' convention to give help for cmd. */ +#if defined(RSE_PATCH_PROLOGEPILOG) ||\ + defined(RSE_PATCH_CUSTOMCMD) + static const char short_options[] = "+QqrwtnRvb:T:e:d:Hfz:s:xa" +#ifdef RSE_PATCH_PROLOGEPILOG + "P:E:" +#endif +#ifdef RSE_PATCH_CUSTOMCMD + "C:" +#endif + ; +#else static const char short_options[] = "+QqrwtnRvb:T:e:d:Hfz:s:xa"; +#endif static struct option long_options[] = { {"help", 0, NULL, 'H'}, @@ -521,6 +620,12 @@ {"help-options", 0, NULL, 4}, #ifdef SERVER_SUPPORT {"allow-root", required_argument, NULL, 3}, +#ifdef RSE_PATCH_MAPROOT + {"map-root", required_argument, NULL, 5}, +#endif +#ifdef RSE_PATCH_CHROOT + {"chroot", required_argument, NULL, 6}, +#endif #endif /* SERVER_SUPPORT */ {0, 0, 0, 0} }; @@ -574,6 +679,10 @@ readonlyfs = 1; logoff = 1; } +#ifdef RSE_PATCH_PROLOGEPILOG + cvs_prolog = getenv("CVSPROLOG"); + cvs_epilog = getenv("CVSEPILOG"); +#endif /* Set this to 0 to force getopt initialization. getopt() sets this to 1 internally. */ @@ -593,6 +702,31 @@ use_cvsrc = 0; } +#ifdef RSE_PATCH_GLOBALOPTION + /* + * Perform a pre-lookup of the command name in order to scan cvsrc + * file also for command dependent global options. For instance a + * "-d" option only for "commit" commands (useful if one uses a + * local repository copy for all cvs commands, but commits have to + * go directly to the master repository). + */ + if (use_cvsrc) { + cvs_cmd_name = argv[optind]; + if (cvs_cmd_name != NULL && cvs_cmd_name[0] != '\0') { + for (cm = cmds; cm->fullname != NULL; cm++) + { + if (cm->nick1 && !strcmp(cvs_cmd_name, cm->nick1)) + break; + if (cm->nick2 && !strcmp(cvs_cmd_name, cm->nick2)) + break; + if (!strcmp(cvs_cmd_name, cm->fullname)) + break; + } + cvs_cmd_name = cm->fullname; + } + } +#endif + #ifdef SERVER_SUPPORT /* Don't try and read a .cvsrc file if we are a server. */ if (optind < argc @@ -619,6 +753,29 @@ if (use_cvsrc) read_cvsrc (&argc, &argv, "cvs"); +#ifdef RSE_PATCH_GLOBALOPTION + if (use_cvsrc) { + if (cvs_cmd_name != NULL && cvs_cmd_name[0] != '\0') { + char *cmd; +#ifdef RSE_PATCH_GLOBALOPTION_PARTLY + if ( strcmp(cvs_cmd_name, "commit") == 0 + || strcmp(cvs_cmd_name, "tag") == 0 + || strcmp(cvs_cmd_name, "rtag") == 0 + || strcmp(cvs_cmd_name, "history") == 0 + || strcmp(cvs_cmd_name, "admin") == 0 + || strcmp(cvs_cmd_name, "import") == 0 + || strcmp(cvs_cmd_name, "rdiff") == 0) { +#endif + cmd = xmalloc(4 + strlen(cvs_cmd_name) + 1); + sprintf(cmd, "cvs/%s", cvs_cmd_name); + read_cvsrc (&argc, &argv, cmd); +#ifdef RSE_PATCH_GLOBALOPTION_PARTLY + } +#endif + } + } +#endif + optind = 0; opterr = 1; @@ -645,6 +802,24 @@ /* --allow-root */ root_allow_add (optarg, gConfigPath); break; +#ifdef RSE_PATCH_MAPROOT + case 5: { + /* --map-root */ + char *cp; + if ((cp = strchr(optarg, ':')) == NULL) + error(1, 0, "invalid argument syntax for --map-root option"); + *cp++ = '\0'; + root_map_add(optarg, cp); + break; + } +#endif +#ifdef RSE_PATCH_CHROOT + case 6: { + /* --chroot */ + chrootdir = strdup(optarg); + break; + } +#endif #endif /* SERVER_SUPPORT */ case 'Q': really_quiet = 1; @@ -708,7 +883,30 @@ case 'd': if (CVSroot_cmdline != NULL) free (CVSroot_cmdline); +#ifdef RSE_PATCH_MAPROOT + { + char *newarg; + if (root_map_it(optarg, &newarg, 0)) + optarg = newarg; + } +#endif +#ifdef RSE_PATCH_CVSROOT + { + cvsroot_type *e; + if ((e = cvsroot_lookup(optarg, NULL, NULL)) != NULL) { + if (!quiet) + fprintf(stderr, "%s: using repository `%s'\n", program_name, e->masterpath); + CVSroot_cmdline = xstrdup(e->masterpath); + cvsroot_free(e); + } + else { + cvsroot_cmdline_isreal = 1; +#endif CVSroot_cmdline = xstrdup (optarg); +#ifdef RSE_PATCH_CVSROOT + } + } +#endif break; case 'H': help = 1; @@ -753,6 +951,28 @@ We will issue an error later if stream authentication is not supported. */ break; +#ifdef RSE_PATCH_PROLOGEPILOG + case 'P': + cvs_prolog = xstrdup(optarg); + break; + case 'E': + cvs_epilog = xstrdup(optarg); + break; +#endif +#ifdef RSE_PATCH_CUSTOMCMD + case 'C': { + char *cp; + if (customcmd_num >= CUSTOMCMD_MAX) + error(1, 0, "maximum number of allowed -C options reached"); + if ((cp = strchr(optarg, ':')) == NULL) + error(1, 0, "invalid argument to option -C (has to be \"name:cmd\")"); + *cp++ = '\0'; + customcmd_tab[customcmd_num].name = xstrdup(optarg); + customcmd_tab[customcmd_num].command = xstrdup(cp); + customcmd_num++; + break; + } +#endif case '?': default: usage (usg); @@ -770,6 +990,28 @@ Using this option to access a repository which some users write to may\n\ cause intermittent sandbox corruption."); } +#ifdef RSE_PATCH_CUSTOMCMD + /* Look up the custom command. */ + cm = NULL; + { + int i; + cvs_cmd_name = argv[0]; + for (i = 0; i < customcmd_num; i++) { + if (strcmp(customcmd_tab[i].name, cvs_cmd_name) == 0) { + struct cmd *ccm; + ccm = (struct cmd *)xmalloc(sizeof(struct cmd)); + ccm->nick1 = NULL; + ccm->nick2 = NULL; + ccm->func = customcmd_run; + ccm->fullname = customcmd_tab[i].name; + argv[0] = customcmd_tab[i].command; + cm = (const struct cmd *)ccm; + standalone_command = 1; + } + } + } + if (cm == NULL) { +#endif /* Calculate the cvs global session ID */ @@ -838,6 +1080,10 @@ else cvs_cmd_name = cm->fullname; /* Global pointer for later use */ +#ifdef RSE_PATCH_CUSTOMCMD + } +#endif + if (help) { argc = -1; /* some functions only check for this */ @@ -865,6 +1111,107 @@ CVSUMASK_ENV, cp); } +#ifdef RSE_PATCH_CVSPID + /* provide the process id of the parent CVS process to + sub-processes (usually scripts called from *info files) in order + to let them have a unique session handle */ + { + char pidbuf[64]; + sprintf(pidbuf, "CVSPID=%lu", (unsigned long)getpid()); + putenv(pidbuf); + } +#endif + +#ifdef RSE_PATCH_SETXID + if ( strcmp(cvs_cmd_name, "kserver") != 0 + && strcmp(cvs_cmd_name, "pserver") != 0 + && strcmp(cvs_cmd_name, "server") == 0) { + uid_t uid, euid; + gid_t gid, egid; + struct passwd *pw1; + struct passwd *pw2; + + /* fetch [e]uid/[e]gid */ + gid = getgid(); + uid = getuid(); + egid = getegid(); + euid = geteuid(); + + /* determine what is intended: + 0. uid == 0 --> error! + 1. uid != 0, euid == 0 --> euid := uid (for chroot operations) + 2. uid != 0, euid != uid != 0 --> uid := euid (for consistent permissions) */ + if (uid == 0) + error(1, 0, "CVS server operating under root privileges not allowed"); + if (euid == 0) { + /* fetch passwd informations */ + pw2 = pw1 = getpwuid(uid); + +#ifdef RSE_PATCH_CHROOT + /* optionally chroot(2) */ + if (chrootdir != NULL) { + if (chdir(chrootdir) == -1) + error(1, errno, "cannot chdir(2) to directory \"%s\"", chrootdir); + if (chroot(chrootdir) == -1) + error(1, errno, "cannot chroot(2) to directory \"%s\"", chrootdir); + } +#endif + + /* delete effective user and group id */ +#if defined(__hpux) + setuid(getuid()); + setgid(getgid()); +#else + seteuid(getuid()); + setegid(getgid()); +#endif + } + else { + /* fetch passwd informations */ + pw1 = getpwuid(uid); + pw2 = getpwuid(euid != uid ? euid : uid); + + /* adjust group id */ + if (gid != egid) + setgid(egid); /* upgrade real to effective gid */ +#if !defined(__hpux) + else + setegid(gid); /* downgrade effective to real gid */ +#endif + + /* adjust user id */ + if (uid != euid) + setuid(euid); /* upgrade real to effective uid */ +#if !defined(__hpux) + else + seteuid(uid); /* downgrade effective to real uid */ +#endif + } + + /* still do not adjust umask */ + umask(0); + + /* remember real user (especially for getcaller()) */ +#ifdef AUTH_SERVER_SUPPORT + CVS_Username = xstrdup(pw1->pw_name); + setenv("LOGNAME", CVS_Username, 1); +#endif + + /* remember running user */ + setenv("USER", pw2->pw_name, 1); + } + else { + /* delete effective user and group id */ +#if defined(__hpux) + setuid(getuid()); + setgid(getgid()); +#else + seteuid(getuid()); + setegid(getgid()); +#endif + } +#endif + /* HOSTNAME & SERVER_HOSTNAME need to be set before they are * potentially used in gserver_authenticate_connection() (called from * pserver_authenticate_connection, below). @@ -898,6 +1245,22 @@ } # endif /* HAVE_KERBEROS */ +#ifdef RSE_PATCH_PSERVERD + if (strcmp(cvs_cmd_name, "pserverd") == 0) { + /* + * perform the socket listening. This returns multiple times, + * i.e., for each connection. But the parent never returns. + */ + pserver_daemon(argc, argv); + + /* + * switch to regular "cvs server" operation. + */ + argc = 0; + cvs_cmd_name = "server"; + } +#endif + # if defined (AUTH_SERVER_SUPPORT) || defined (HAVE_GSSAPI) if (strcmp (cvs_cmd_name, "pserver") == 0) { @@ -917,6 +1280,11 @@ server_active = strcmp (cvs_cmd_name, "server") == 0; +#ifdef RSE_PATCH_CVSROOT + if (strcmp(cvs_cmd_name, "root") == 0) + standalone_command = 1; +#endif + #ifdef SERVER_SUPPORT if (server_active) { @@ -962,7 +1330,11 @@ * in server mode, since the client will send the repository * directory after the connection is made. */ +#if defined(RSE_PATCH_CVSROOT) || defined(RSE_PATCH_CUSTOMCMD) + if (!server_active && !standalone_command) +#else if (!server_active) +#endif { /* First check if a root was set via the command line. */ if (CVSroot_cmdline) @@ -1018,6 +1390,67 @@ error (1, 0, "or set the %s environment variable.", CVSROOT_ENV); } + +#ifdef RSE_PATCH_CVSROOT + if (CVSroot_cmdline == NULL || !cvsroot_cmdline_isreal) { + cvsroot_type *e; + if (lookup_command_attribute(cvs_cmd_name) & CVS_CMD_MODIFIES_REPOSITORY) { + if ((e = cvsroot_lookup(NULL, NULL, CVSroot_parsed->original)) != NULL) { + /* command modifies repository and we still operare on + the slave repository, so switch to the master repository, + because we can only perform modifications there. */ + if (!quiet) { + fprintf(stderr, "%s: switching to MASTER location of repository `%s'\n", program_name, e->nickname); + fprintf(stderr, "%s: %s <-- %s\n", program_name, e->masterpath, e->slavepath); + } +#if 0 + /* NOTICE: memory leak, but free_cvsroot_t is risky to call according to its sources! */ + if (CVSroot_parsed != NULL) + free_cvsroot_t(CVSroot_parsed); +#endif + if (!(CVSroot_parsed = parse_cvsroot (e->masterpath))) + error (1, 0, "Bad CVSROOT: `%s'.", e->masterpath); + if (CVSroot_cmdline != NULL) + free(CVSroot_cmdline); + CVSroot_cmdline = xstrdup(e->masterpath); + cvsroot_sync = e; + cvsroot_update_env = true; + } + else if ((e = cvsroot_lookup(NULL, CVSroot_parsed->original, NULL)) != NULL) { + /* command modifies repository and we already operare on + the master repository, so no need to switch to it, + but remember to sync a possibly existing slave repository. */ + if (e->slavepath != NULL) + cvsroot_sync = e; + } + } + else { + if ((e = cvsroot_lookup(NULL, CVSroot_parsed->original, NULL)) != NULL) { + if (e->slavepath[0] != '\0') { + /* command does not modify repository and we still operare on + the master repository, so switch to the slave repository, + because it is faster by definition. */ + if (!quiet) { + fprintf(stderr, "%s: switching to SLAVE location of repository `%s'\n", program_name, e->nickname); + fprintf(stderr, "%s: %s --> %s\n", program_name, e->masterpath, e->slavepath); + } +#if 0 + /* NOTICE: memory leak, but free_cvsroot_t is risky to call according to its sources! */ + if (CVSroot_parsed != NULL) + free_cvsroot_t(CVSroot_parsed); +#endif + if (!(CVSroot_parsed = parse_cvsroot (e->slavepath))) + error (1, 0, "Bad CVSROOT: `%s'.", e->slavepath); + if (CVSroot_cmdline != NULL) + free(CVSroot_cmdline); + CVSroot_cmdline = xstrdup(e->slavepath); + cvsroot_free(e); + cvsroot_update_env = true; + } + } + } + } +#endif /* RSE_PATCH_CVSROOT */ } /* Here begins the big loop over unique cvsroot values. We @@ -1050,13 +1483,20 @@ end of things. */ while (server_active || +#if defined(RSE_PATCH_CVSROOT) || defined(RSE_PATCH_CUSTOMCMD) + standalone_command || +#endif walklist (root_directories, set_root_directory, NULL)) { /* Fiddling with CVSROOT doesn't make sense if we're running in server mode, since the client will send the repository directory after the connection is made. */ +#if defined(RSE_PATCH_CVSROOT) || defined(RSE_PATCH_CUSTOMCMD) + if (!server_active && !standalone_command) +#else if (!server_active) +#endif { /* Now we're 100% sure that we have a valid CVSROOT variable. Parse it to see if we're supposed to do @@ -1082,7 +1522,12 @@ save_errno = errno; /* If this is "cvs init", the root need not exist yet. */ +#ifdef RSE_PATCH_CVSROOT + if (strcmp (cvs_cmd_name, "init") && + strcmp (cvs_cmd_name, "root") ) +#else if (strcmp (cvs_cmd_name, "init")) +#endif error (1, save_errno, "%s", path); } free (path); @@ -1100,7 +1545,11 @@ predetermine whether CVSROOT/config overrides things from read_cvsrc and other such places or vice versa. That sort of thing probably needs more thought. */ +#if defined(RSE_PATCH_CVSROOT) || defined(RSE_PATCH_CUSTOMCMD) + if (!server_active && !standalone_command && !current_parsed_root->isremote) +#else if (!server_active && !current_parsed_root->isremote) +#endif { /* If there was an error parsing the config file, parse_config already printed an error. We keep going. Why? Because @@ -1130,7 +1579,31 @@ } #endif +#ifdef RSE_PATCH_PROLOGEPILOG + if (cvs_prolog != NULL) { + char *cmd; + cmd = expand_path(cvs_prolog, current_parsed_root && current_parsed_root->directory ? current_parsed_root->directory : "/", false, "prolog", 0); + run_setup(cmd); + run_add_arg("prolog"); + run_add_arg(cvs_cmd_name); + if (CurDir != NULL) + run_add_arg(CurDir); + else + run_add_arg("unknown-cwd"); + if (current_parsed_root != NULL && current_parsed_root->directory != NULL) + run_add_arg(current_parsed_root->directory); + else + run_add_arg("unknown-cvsroot"); + if (run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL) != 0) + error(1, 0, "prolog program `%s' returned non-zero", cmd); + free(cmd); + } +#endif + if ( +#if defined(RSE_PATCH_CVSROOT) || defined(RSE_PATCH_CUSTOMCMD) + !standalone_command && ( +#endif #ifdef SERVER_SUPPORT /* Don't worry about lock_cleanup_setup when the server is * active since we can only go through this loop once in that @@ -1143,6 +1616,9 @@ !current_parsed_root->isremote && #endif !lock_cleanup_setup)) +#if defined(RSE_PATCH_CVSROOT) || defined(RSE_PATCH_CUSTOMCMD) + ) +#endif { /* Set up to clean up any locks we might create on exit. */ cleanup_register (Lock_Cleanup); @@ -1151,7 +1627,32 @@ /* Call our worker function. */ err = (*(cm->func)) (argc, argv); + +#ifdef RSE_PATCH_PROLOGEPILOG + if (cvs_epilog != NULL) { + char *cmd; + cmd = expand_path(cvs_epilog, current_parsed_root && current_parsed_root->directory ? current_parsed_root->directory : "/", false, "epilog", 0); + run_setup(cmd); + run_add_arg("epilog"); + run_add_arg(cvs_cmd_name); + if (CurDir != NULL) + run_add_arg(CurDir); + else + run_add_arg("unknown-cwd"); + if (current_parsed_root != NULL && current_parsed_root->directory != NULL) + run_add_arg(current_parsed_root->directory); + else + run_add_arg("unknown-cvsroot"); + if (run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL) != 0) + error(1, 0, "epilog program `%s' returned non-zero", cmd); + free(cmd); + } +#endif +#if defined(RSE_PATCH_CVSROOT) || defined(RSE_PATCH_CUSTOMCMD) + if (standalone_command) + break; +#endif /* Mark this root directory as done. When the server is active, our list will be empty -- don't try and remove it from the list. */ @@ -1170,6 +1671,10 @@ break; } /* end of loop for cvsroot values */ +#ifdef RSE_PATCH_CVSROOT + if (cvsroot_sync != NULL) + cvsroot_synchronize(cvsroot_sync, 0); +#endif dellist (&root_directories); } /* end of stuff that gets done if the user DOESN'T ask for help */ @@ -1429,6 +1934,12 @@ TRACE (TRACE_FUNCTION, "format_date (%s)", datestr); +#ifdef RSE_PATCH_BUGFIX + /* Special case */ + if (strncmp(datestr, "Result of merge", 15) == 0) + goto as_is; +#endif + /* Convert the date string to seconds since the epoch. */ if (!get_date (&t, datestr, NULL)) { Index: src/mkmodules.c =================================================================== RCS file: /v/ossp/pkg/tool/cvs/cvs/cvs/src/mkmodules.c,v retrieving revision 1.1.1.12 diff -u -d -r1.1.1.12 mkmodules.c --- src/mkmodules.c 24 May 2005 20:59:01 -0000 1.1.1.12 +++ src/mkmodules.c 5 Oct 2005 10:38:19 -0000 @@ -234,6 +234,48 @@ NULL }; +#ifdef RSE_PATCH_ADMININFO +static const char *const admininfo_contents[] = { + "# The \"admininfo\" file is used to control pre-admin checks.\n", + "# The filter on the right is invoked with the repository and a list \n", + "# of files to check. A non-zero exit of the filter program will \n", + "# cause the admin operation to be aborted.\n", + "#\n", + "# The first entry on a line is a regular expression which is tested\n", + "# against the directory that the change is being committed to, relative\n", + "# to the $CVSROOT. For the first match that is found, then the remainder\n", + "# of the line is the name of the filter to run.\n", + "#\n", + "# If the repository name does not match any of the regular expressions in this\n", + "# file, the \"DEFAULT\" line is used, if it is specified.\n", + "#\n", + "# If the name \"ALL\" appears as a regular expression it is always used\n", + "# in addition to the first matching regex or \"DEFAULT\".\n", + NULL +}; +#endif + +#ifdef RSE_PATCH_IMPORTINFO +static const char *const importinfo_contents[] = { + "# The \"importinfo\" file is used to control pre-import checks.\n", + "# The filter on the right is invoked with the repository to check.\n", + "# A non-zero exit of the filter program will cause the import\n", + "# operation to be aborted.\n", + "#\n", + "# The first entry on a line is a regular expression which is tested\n", + "# against the directory that the change is being committed to, relative\n", + "# to the $CVSROOT. For the first match that is found, then the remainder\n", + "# of the line is the name of the filter to run.\n", + "#\n", + "# If the repository name does not match any of the regular expressions in this\n", + "# file, the \"DEFAULT\" line is used, if it is specified.\n", + "#\n", + "# If the name \"ALL\" appears as a regular expression it is always used\n", + "# in addition to the first matching regex or \"DEFAULT\".\n", + NULL +}; +#endif + static const char *const preproxy_contents[] = { "# The \"preproxy\" file is called form the secondary server as soon as\n", "# the secondary server determines that it will be proxying a write\n", @@ -525,6 +567,11 @@ "# repositories. Set it to `never' (the previous CVS behavior) to prevent\n", "# verifymsg scripts from changing the log message.\n", "#RereadLogAfterVerify=always\n", +#ifdef RSE_PATCH_CONFIGUMASK + "\n", + "# Set `UMask' to the octal value of the umask.\n", + "#UMask=002\n", +#endif "\n", "# Set `UserAdminOptions' to the list of `cvs admin' commands (options)\n", "# that users not in the `cvsadmin' group are allowed to run. This\n", @@ -606,6 +653,16 @@ {CVSROOTADM_COMMITINFO, "a %s file can be used to configure 'cvs commit' checking", commitinfo_contents}, +#ifdef RSE_PATCH_ADMININFO + {CVSROOTADM_ADMININFO, + "a %s file can be used to configure 'cvs admin' checking", + admininfo_contents}, +#endif +#ifdef RSE_PATCH_IMPORTINFO + {CVSROOTADM_IMPORTINFO, + "a %s file can be used to configure 'cvs import' checking", + importinfo_contents}, +#endif {CVSROOTADM_IGNORE, "a %s file can be used to specify files to ignore", NULL}, Index: src/parseinfo.c =================================================================== RCS file: /v/ossp/pkg/tool/cvs/cvs/cvs/src/parseinfo.c,v retrieving revision 1.1.1.15 diff -u -d -r1.1.1.15 parseinfo.c --- src/parseinfo.c 6 Sep 2005 04:40:37 -0000 1.1.1.15 +++ src/parseinfo.c 5 Oct 2005 10:48:31 -0000 @@ -646,6 +646,11 @@ } } } +#ifdef RSE_PATCH_CONFIGUMASK + else if (strcmp (line, "UMask") == 0) { + cvsumask = (mode_t)(strtol(p, NULL, 8) & 0777); + } +#endif else if (strcmp (line, "TmpDir") == 0) { if (retval->TmpDir) free (retval->TmpDir); Index: src/repos.c =================================================================== RCS file: /v/ossp/pkg/tool/cvs/cvs/cvs/src/repos.c,v retrieving revision 1.1.1.12 diff -u -d -r1.1.1.12 repos.c --- src/repos.c 26 May 2005 17:48:06 -0000 1.1.1.12 +++ src/repos.c 4 Oct 2005 19:23:40 -0000 @@ -198,3 +198,4 @@ repository[len - 2] = '\0'; } } + Index: src/root.c =================================================================== RCS file: /v/ossp/pkg/tool/cvs/cvs/cvs/src/root.c,v retrieving revision 1.1.1.17 diff -u -d -r1.1.1.17 root.c --- src/root.c 25 Sep 2005 00:38:29 -0000 1.1.1.17 +++ src/root.c 6 Oct 2005 06:49:44 -0000 @@ -122,6 +122,9 @@ goto out; } +#ifdef RSE_PATCH_MAPROOT + root_map_it(ret->original, &ret->original, 0); +#endif out: free (cvsadm); @@ -340,7 +343,86 @@ return parse_config (arg, configPath); } +#ifdef RSE_PATCH_MAPROOT + +typedef struct root_map_st { + char *old; + char *new; +} root_map_t; + +#define ROOT_MAP_MAX 10 +static int root_map_max = 0; +static root_map_t root_map_vec[ROOT_MAP_MAX]; + +void root_map_add(char *old, char *new) +{ + if (root_map_max >= ROOT_MAP_MAX) + return; + root_map_vec[root_map_max].old = xstrdup(old); + root_map_vec[root_map_max].new = xstrdup(new); + root_map_max++; + return; +} + +void root_map_free(void) +{ + while (root_map_max > 0) { + free(root_map_vec[root_map_max].old); + free(root_map_vec[root_map_max].new); + root_map_max--; + } + return; +} + +int root_map_it(char *old, char **new, int prefixonly) +{ + int rv; + int i; + int n; + + if (old == NULL) + return 0; + rv = 0; + for (i = 0; i < root_map_max; i++) { + n = strlen(root_map_vec[i].old); + if (!prefixonly && strcmp(old, root_map_vec[i].old) == 0) { + if (new == NULL) { + /* we assume old is buffer and override it */ + strcpy(old, root_map_vec[i].new); + } + else { + if (old == *new) + /* old and new is same pointer we free before */ + if (old) free(old); + /* provide new allocated buffer */ + *new = xmalloc(strlen(root_map_vec[i].new)+1); + strcpy(*new, root_map_vec[i].new); + } + rv = 1; + break; + } + else if (prefixonly && strncmp(old, root_map_vec[i].old, n) == 0) { + if (new == NULL) { + /* we assume old is buffer and override it */ + sprintf(old, "%s%s", root_map_vec[i].new, old+n); + } + else { + char *oldnew = *new; + /* provide new allocated buffer */ + *new = xmalloc(strlen(root_map_vec[i].new)+strlen(old+n)+1); + sprintf(*new, "%s%s", root_map_vec[i].new, old+n); + if (old == oldnew) + /* old and new is same pointer we free before */ + if (old) free(old); + } + rv = 1; + break; + } + } + return rv; +} +#endif /* RSE_PATCH_MAPROOT */ /* This global variable holds the global -d option. It is NULL if -d was not used, which means that we must get the CVSroot information @@ -1052,3 +1134,471 @@ /* NOTREACHED */ } #endif + +#ifdef RSE_PATCH_CVSROOT + +#include + +#ifndef CVS_ROOT_FILE +#define CVS_ROOT_FILE ".cvsroot" +#endif + +char * +cvsroot_filename( + void) +{ + char *homedir; + char *rootfile; + + /* Environment should override file. */ + if ((rootfile = getenv("CVS_ROOTFILE")) != NULL) + return xstrdup(rootfile); + + /* Construct absolute pathname to user's password file. */ + if ((homedir = get_homedir()) == NULL) { + error(1, 0, "could not find out home directory"); + return NULL; + } + rootfile = (char *)xmalloc(strlen(homedir)+strlen(CVS_ROOT_FILE)+3); + strcpy(rootfile, homedir); + strcat(rootfile, "/"); + strcat(rootfile, CVS_ROOT_FILE); + return rootfile; +} + +void cvsroot_free( + cvsroot_type *e) +{ + if (e != NULL) { + if (e->nickname != NULL) + free(e->nickname); + if (e->masterpath != NULL) + free(e->masterpath); + if (e->slavepath != NULL) + free(e->slavepath); + if (e->syncprog != NULL) + free(e->syncprog); + free(e); + } + return; +} + +cvsroot_type * +cvsroot_entry_read( + FILE *fp) +{ + cvsroot_type *e; + char *nickname; + char *masterpath; + char *slavepath; + char *syncprog; + char *line; + int line_length; + size_t line_chars_allocated; + size_t n; + + e = NULL; + line = NULL; + line_chars_allocated = 0; + while ((line_length = getline(&line, &line_chars_allocated, fp)) >= 0) { + /* parse line */ + line += strspn(line, " \t\n"); + if (line[0] == '#') + continue; + nickname = line; + if ((n = strcspn(line, " \t\n")) == 0) + return NULL; + line += n; + *line++ = '\0'; + line += strspn(line, " \t"); + masterpath = line; + if ((n = strcspn(line, " \t\n")) == 0) + return NULL; + line += n; + *line++ = '\0'; + line += strspn(line, " \t\n"); + slavepath = ""; + syncprog = ""; + if (line[0] != '\0') { + slavepath = line; + n = strcspn(line, " \t\n"); + line += n; + *line++ = '\0'; + if (line[0] != '\0') { + syncprog = line; + n = strcspn(line, " \t\n"); + line += n; + *line++ = '\0'; + } + } + e = (cvsroot_type *)xmalloc(sizeof(cvsroot_type)); + e->nickname = xstrdup(nickname); + e->masterpath = xstrdup(masterpath); + e->slavepath = xstrdup(slavepath); + e->syncprog = xstrdup(syncprog); + break; + } + return e; +} + +void +cvsroot_entry_write( + FILE *fp, + cvsroot_type *e) +{ + if (fp != NULL && e != NULL) { + fprintf(fp, "%s %s", + e->nickname, e->masterpath); + if (e->slavepath[0] != '\0') + fprintf(fp, " %s", e->slavepath); + if (e->syncprog[0] != '\0') + fprintf(fp, " %s", e->syncprog); + fprintf(fp, "\n"); + } + return; +} + +cvsroot_type * +cvsroot_lookup( + char *by_nickname, + char *by_masterpath, + char *by_slavepath) +{ + char *rootfile; + cvsroot_type *e = NULL; + FILE *fp; + + if ((rootfile = cvsroot_filename()) == NULL) + return NULL; + if ((fp = fopen(rootfile, "r")) == NULL) { + free(rootfile); + return NULL; + } + while ((e = cvsroot_entry_read(fp)) != NULL) { + if ( (by_nickname != NULL && strcmp(e->nickname, by_nickname) == 0) + || (by_masterpath != NULL && strcmp(e->masterpath, by_masterpath) == 0) + || (by_slavepath != NULL && strcmp(e->slavepath, by_slavepath) == 0)) + break; + cvsroot_free(e); + } + fclose(fp); + free(rootfile); + return e; +} + +void +cvsroot_synchronize( + cvsroot_type *e, + int force) +{ + char *cmd; + char *arg; + char *rsh; + int smart; + char *syncprog; + + smart = 0; + syncprog = e->syncprog; + if (syncprog[0] == '-') { + smart++; + syncprog++; + } + if (smart && !force) { + if (!really_quiet) { + if (strcasecmp(syncprog, "manual") == 0) { + fprintf(stderr, "%s: synchronize SLAVE with MASTER of repository `%s', please!\n", program_name, e->nickname); + } + else { + fprintf(stderr, "%s: synchronize SLAVE with MASTER of repository `%s'\n", program_name, e->nickname); + fprintf(stderr, "%s: by running the command `%s root -s %s', please.\n", program_name, program_name, e->nickname); + } + } + return; + } + + if (strcasecmp(syncprog, "manual") == 0) { + if (!really_quiet) { + fprintf(stderr, "%s: synchronizing SLAVE with MASTER of repository `%s'\n", program_name, e->nickname); + fprintf(stderr, "%s: has to be performed manually by you!\n", program_name); + } + } + else if (strcasecmp(syncprog, "rsync") == 0 || + strncasecmp(syncprog, "rsync:", 6) == 0) { + if (!really_quiet) { + fprintf(stderr, "%s: synchronizing SLAVE with MASTER of repository `%s':\n", program_name, e->nickname); + fprintf(stderr, "%s: %s --> %s (rsync)\n", program_name, e->masterpath, e->slavepath); + } + run_setup("rsync"); + if (!quiet) + run_add_arg("-v"); + if ((rsh = getenv("CVS_RSH")) != NULL) { + arg = xmalloc(strlen(rsh)+7); + strcpy(arg, "--rsh="); + strcat(arg, rsh); + run_add_arg(arg); + free(arg); + } + run_add_arg("-rlpt"); + run_add_arg("--delete"); + if (strncasecmp(syncprog, "rsync:", 6) == 0) { + char *list = xstrdup(syncprog+6); + for (arg = strtok(list, ","); arg != NULL; arg = strtok(NULL, ",")) { + if (arg[0] == '!') { + run_add_arg("--exclude"); + run_add_arg(arg+1); + } + else { + run_add_arg("--include"); + run_add_arg(arg); + } + } + free(list); + } + arg = xmalloc(strlen(e->masterpath)+2); + strcpy(arg, e->masterpath); + strcat(arg, "/"); + run_add_arg(arg); + free(arg); + arg = xmalloc(strlen(e->slavepath)+2); + strcpy(arg, e->slavepath); + strcat(arg, "/"); + run_add_arg(arg); + free(arg); + if (trace) { + cvs_output(program_name, 0); + cvs_output(" ", 1); + cvs_output(cvs_cmd_name, 0); + cvs_output(": Executing ", 0); + run_print(stdout); + cvs_output("\n", 0); + } + if (run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL) != 0) + error(1, 0, "synchronization program `rsync' returned non-zero"); + } + else { + if (!really_quiet) { + fprintf(stderr, "%s: synchronizing SLAVE with MASTER of repository `%s':\n", program_name, e->nickname); + fprintf(stderr, "%s: %s --> %s (%s)\n", program_name, e->masterpath, e->slavepath, e->syncprog); + } + cmd = expand_path(syncprog, current_parsed_root && current_parsed_root->directory ? current_parsed_root->directory : "/", false, "sync", 0); + run_setup(cmd); + run_add_arg(e->nickname); + run_add_arg(e->masterpath); + run_add_arg(e->slavepath); + if (trace) { + cvs_output(program_name, 0); + cvs_output(" ", 1); + cvs_output(cvs_cmd_name, 0); + cvs_output(": Executing ", 0); + run_print(stdout); + cvs_output("\n", 0); + } + if (run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL) != 0) + error(1, 0, "synchronization program `%s' returned non-zero", cmd); + free(cmd); + } +} + +static const char *const root_usage[] = { + "Usage: %s %s [-v] -e|-E|-l|s [arg ...]\n", + "Options:\n", + " -v Verbose mode.\n", + " -e Edit entry from ~/.cvsroot in batch mode.\n", + " -E Edit entry from ~/.cvsroot in visual mode.\n", + " -l List entries from ~/.cvsroot.\n", + " -s Synchronize entries from ~/.cvsroot.\n", + "Synopsis:\n", + " Add/Modify an entry:\n", + " cvs root -e nickname masterpath [slavepath [syncprog]]\n", + " Delete an entry:\n", + " cvs root -e nickname\n", + " List all or some particular entries:\n", + " cvs [-Q] [-q] root [-v] -l [nickname ...]\n", + " Synchronize all or some particular entries:\n", + " cvs [-Q] [-q] root -s [nickname ...]\n", + "(Specify the --help global option for a list of other help options)\n", + NULL +}; + +int +root( + int argc, + char **argv) +{ + enum { + ROOT_MODE_UNKNOWN, + ROOT_MODE_EDIT_CMDLINE, + ROOT_MODE_EDIT_VISUAL, + ROOT_MODE_LIST, + ROOT_MODE_SYNC + }; + int mode = ROOT_MODE_UNKNOWN; + char *rootfile; + char *rootfilebak = NULL; + FILE *fp; + FILE *fpbak = NULL; + int option; + cvsroot_type *e; + cvsroot_type E; + int doit; + int i; + int rc; + int verbose = 0; + int found = 0; + int oldexists; + + if (argc == -1) + usage(root_usage); + optind = 0; + while ((option = getopt(argc, argv, "veEsl")) != EOF) { + switch ((char)option) { + case 'v': + verbose = 1; + break; + case 'e': + mode = ROOT_MODE_EDIT_CMDLINE; + break; + case 'E': + mode = ROOT_MODE_EDIT_VISUAL; + break; + case 'l': + mode = ROOT_MODE_LIST; + break; + case 's': + mode = ROOT_MODE_SYNC; + break; + case '?': + default: + usage(root_usage); + break; + } + } + argc -= optind; + argv += optind; + if (mode == ROOT_MODE_UNKNOWN) + error(1, 0, "exactly one of the -e, -E, -l or -s options have to given"); + + if (mode == ROOT_MODE_EDIT_CMDLINE) { + if (argc < 1 || argc > 4) + error(1, 0, "option -e requires 1-4 arguments"); + E.nickname = argv[0]; + if (argc >= 2) + E.masterpath = argv[1]; + else + E.masterpath = ""; + if (argc >= 3) + E.slavepath = argv[2]; + else + E.slavepath = ""; + if (argc == 4) + E.syncprog = argv[3]; + else + E.syncprog = ""; + if ((rootfile = cvsroot_filename()) == NULL) + return 0; + oldexists = 0; + if (isfile(rootfile)) { + oldexists = 1; + rootfilebak = xmalloc(strlen(rootfile)+5); + strcpy(rootfilebak, rootfile); + strcat(rootfilebak, ".bak"); + rename(rootfile, rootfilebak); + if ((fpbak = fopen(rootfilebak, "r")) == NULL) { + free(rootfile); + free(rootfilebak); + return 0; + } + } + if ((fp = fopen(rootfile, "w")) == NULL) { + fclose(fpbak); + free(rootfile); + free(rootfilebak); + return 0; + } + if (oldexists) { + found = 0; + while ((e = cvsroot_entry_read(fpbak)) != NULL) { + if (strcmp(e->nickname, E.nickname) == 0) { + cvsroot_free(e); + found = 1; + break; + } + cvsroot_entry_write(fp, e); + cvsroot_free(e); + } + } + if (argc > 1) + cvsroot_entry_write(fp, &E); + if (oldexists) { + if (found) { + while ((e = cvsroot_entry_read(fpbak)) != NULL) { + cvsroot_entry_write(fp, e); + cvsroot_free(e); + } + } + fclose(fpbak); + } + fclose(fp); + } + else if (mode == ROOT_MODE_EDIT_VISUAL) { + if (argc != 0) + error(1, 0, "option -E requires no arguments"); + if ((rootfile = cvsroot_filename()) == NULL) + return 0; + run_setup(Editor); + run_add_arg(rootfile); + if ((rc = run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL|RUN_SIGIGNORE)) != 0) + error (1, rc == -1 ? errno : 0, "warning: editor session failed"); + } + else if (mode == ROOT_MODE_LIST || mode == ROOT_MODE_SYNC) { + if ((rootfile = cvsroot_filename()) == NULL) + return 0; + if ((fp = fopen(rootfile, "r")) == NULL) { + free(rootfile); + return 0; + } + while ((e = cvsroot_entry_read(fp)) != NULL) { + doit = 0; + if (argc == 0) + doit = 1; + else { + for (i = 0; i < argc; i++) { + if (strcmp(argv[i], e->nickname) == 0) { + doit = 1; + break; + } + } + } + if (doit) { + if (mode == ROOT_MODE_LIST) { + if (verbose) + fprintf(stdout, "Repository `%s':\n" + " Master Path: %s\n" + " Slave Path: %s\n" + " Synchronize: %s\n", + e->nickname, e->masterpath, + e->slavepath, e->syncprog); + else + fprintf(stdout, "%s %s %s %s\n", + e->nickname, e->masterpath, + e->slavepath, e->syncprog); + } + else if (mode == ROOT_MODE_SYNC) { + if (e->slavepath[0] == '\0' || e->syncprog[0] == '\0') { + if (argc > 0) + error(1, 0, "repository `%s' has no slave path or sync program defined", e->nickname); + } + else + cvsroot_synchronize(e, 1); + } + } + cvsroot_free(e); + } + fclose(fp); + free(rootfile); + } + return 0; +} + +#endif /* RSE_PATCH_CVSROOT */ + Index: src/server.c =================================================================== RCS file: /v/ossp/pkg/tool/cvs/cvs/cvs/src/server.c,v retrieving revision 1.1.1.21 diff -u -d -r1.1.1.21 server.c --- src/server.c 28 Sep 2005 15:25:59 -0000 1.1.1.21 +++ src/server.c 5 Oct 2005 15:47:13 -0000 @@ -122,6 +122,16 @@ +#ifdef RSE_PATCH_PSERVERD +#include +#include +#include +#include +#ifdef __SVR4 +#include +#endif +#endif + /* While processing requests, this buffer accumulates data to be sent to the client, and then once we are in do_cvs_command, we use it for all the data to be sent. */ @@ -776,6 +786,13 @@ # endif /* PROXY_SUPPORT */ ) return; +#ifdef RSE_PATCH_MAPROOT + { + char *argnew; + if (root_map_it(arg, &argnew, 0)) + arg = argnew; + } +#endif if (!ISABSOLUTE (arg)) { if (alloc_pending (80 + strlen (arg))) @@ -1378,6 +1395,9 @@ * processing of anything other than errors is skipped until later. */ status = buf_read_line (buf_from_net, &repos, NULL); +#ifdef RSE_PATCH_MAPROOT + root_map_it(repos, &repos, 1); +#endif if (status == 0) { if (!ISABSOLUTE (repos)) @@ -6659,7 +6679,11 @@ /* Set LOGNAME, USER and CVS_USER in the environment, in case they are already set to something else. */ +#if defined(RSE_PATCH_LOGNAME) && defined(AUTH_SERVER_SUPPORT) + setenv ("LOGNAME", CVS_Username, 1); +#else setenv ("LOGNAME", username, 1); +#endif setenv ("USER", username, 1); # ifdef AUTH_SERVER_SUPPORT setenv ("CVS_USER", CVS_Username, 1); @@ -7209,6 +7233,10 @@ pserver_read_line (&username, NULL); pserver_read_line (&password, NULL); +#ifdef RSE_PATCH_MAPROOT + root_map_it(repository, &repository, 0); +#endif + /* ... and make sure the protocol ends on the right foot. */ /* See above comment about error handling. */ pserver_read_line (&tmp, NULL); @@ -7959,8 +7987,6 @@ } } - - /* * void cvs_trace(int level, const char *fmt, ...) * @@ -7985,3 +8011,518 @@ va_end (va); } } + +#ifdef RSE_PATCH_PSERVERD + +/* ========================================================================= */ + +#if !defined(SIGCHLD) && defined(SIGCLD) +#define SIGCHLD SIGCLD +#endif + +static void pserver_handshake(void); + +/* + * Main procedure stub. This is called in two contexts: first under "cvs + * -H pserverd" where we just display the usage; second inside the CVS + * main loop where we have to act as the regular "cvs server". + */ + +static const char *const pserverd_usage[] = { + "Usage: %s %s [-v] [-d] [-l addr[:port]] [-p pidfile] [-A user] [-R user:repos:chroot]\n", + "\t-v\tVerbose mode.\n", + "\t-d\tDetach into background and run as a daemon.\n", + "\t-l\tListen to a particular address/port.\n", + "\t-p\tWrite the daemon's PID to a file.\n", + "\t-A\tForce global -l -n -u options for a particular user.\n", + "\t-R\tPerform a chroot(2) for a user/repository pair.\n", + "(Specify the --help global option for a list of other help options)\n", + NULL +}; + +int +pserverd( + int argc, + char **argv) +{ + if (argc == -1) + usage(pserverd_usage); + return server(argc, argv); +} + +/* + * The pserver daemon. This listens on a particular TCP/IP socket for + * connections. If one occurs, it forks and returns to the caller inside + * the child process. The parent process runs forever. + */ + +static struct { + char *user; + char *repos; + char *chroot; +} pserver_chroot; + +static char *pserverd_anonymous_user = NULL; +static int pserverd_verbose = 0; + +static int +tcp_setinaddr( + struct sockaddr_in *addr, + const char *host, + const char *service, + const char *protocol) +{ + struct hostent *hp; + char *end; + long portno; + struct servent *serv; + + memset(addr, 0, sizeof *addr); + addr->sin_family = AF_INET; + + /* set host part of address */ + if (host == NULL) + addr->sin_addr.s_addr = INADDR_ANY; + else { + addr->sin_addr.s_addr = inet_addr(host); + if (addr->sin_addr.s_addr == (unsigned long) -1) { + if ((hp = gethostbyname(host)) == NULL) + return -1; + memcpy(&addr->sin_addr, hp->h_addr, hp->h_length); + addr->sin_family = hp->h_addrtype; + } + } + + /* set port part of address */ + if (service == NULL) + addr->sin_port = htons(0); + else { + portno = strtol(service, &end, 10); + if (portno > 0 && portno <= 65535 && end != service && *end == '\0') + addr->sin_port = htons(portno); + else { + if ((serv = getservbyname(service, protocol)) == NULL) + return -1; + addr->sin_port = serv->s_port; + } + } + return 0; +} + +static int +tcp_listen( + const char *host, + const char *port) +{ + int s; + struct protoent *proto; + struct sockaddr_in server; + int yes = 1; + + if (tcp_setinaddr(&server, host, port, "tcp") < 0) + return -1; + if ((proto = getprotobyname("tcp")) == NULL) + return -1; + if ((s = socket(PF_INET, SOCK_STREAM, proto->p_proto)) < 0) + return -1; + setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&yes, sizeof(yes)); + if (bind(s, (struct sockaddr *)&server, sizeof(server)) < 0) { + close(s); + return -1; + } + if (listen(s, 256) < 0) { + close(s); + return -1; + } + return s; +} + +static int +proc_daemon( + int nochdir, + int noclose) +{ + int fd; + int rc; + + /* + * Ignore tty related signals + */ +#ifdef SIGTTOU + signal(SIGTTOU, SIG_IGN); +#endif +#ifdef SIGTTIN + signal(SIGTTIN, SIG_IGN); +#endif +#ifdef SIGTSTP + signal(SIGTSTP, SIG_IGN); +#endif + + /* + * fork so the parent can exit, this returns control to the command line + * or shell invoking your program. This step is required so that the new + * process is guaranteed not to be a process group leader (The next step, + * setsid, would fail if you're a process group leader). + */ + rc = fork(); + switch (rc) { + case -1: return -1; + case 0: break; + default: _exit(0); /* exit original process */ + } + + /* + * setsid to become a process group and session group leader. Since a + * controlling terminal is associated with a session, and this new session + * has not yet acquired a controlling terminal our process now has no + * controlling terminal, which is a Good Thing for daemons. + */ +#ifdef HAVE_SETSID + if (setsid() == -1) + return -1; +#else + if (setpgid(0, getpid()) == -1) + return -1; +#ifdef TIOCNOTTY +#ifndef _PATH_TTY +#define _PATH_TTY "/dev/tty" +#endif + if ((fd = open(_PATH_TTY, O_RDWR)) == -1) + return -1; + ioctl(fd, TIOCNOTTY, NULL); + close(fd); +#endif +#endif + + /* + * fork again so the parent, (the session group leader), can exit. This + * means that we, as a non-session group leader, can never regain a + * controlling terminal. + */ + rc = fork(); + switch (rc) { + case -1: return -1; + case 0: break; + default: _exit(0); /* exit original process */ + } + + /* + * chdir("/") to ensure that our process doesn't keep any directory in + * use. Failure to do this could make it so that an administrator couldn't + * unmount a filesystem, because it was our current directory. + * [Equivalently, we could change to any directory containing files + * important to the daemon's operation.] + */ + if (!nochdir) + chdir("/"); + + /* + * give us complete control over the permissions of anything we write. We + * don't know what umask we may have inherited. [This step is optional] + */ + umask(0); + + /* + * close fds 0, 1, and 2. This releases the standard in, out, and error we + * inherited from our parent process. We have no way of knowing where + * these fds might have been redirected to. + */ + if (!noclose && (fd = open("/dev/null", O_RDWR, 0)) != -1) { + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + if (fd > 2) + close(fd); + } + return 0; +} + +static void +pserver_daemon_reapchild( + int signo) +{ + pid_t child; + int status; + char buf[128]; + + while ((child = waitpid(-1, &status, WNOHANG)) > (pid_t)0) { + if (pserverd_verbose) { + sprintf(buf, "cvs pserverd[%ld]: child process (pid %ld): terminated.\n", + (long)getpid(), (long)child); + write(STDOUT_FILENO, buf, strlen(buf)); + } + } + signal(signo, &pserver_daemon_reapchild); + return; +} + +int +pserver_daemon( + int argc, + char **argv) +{ + int sd; + pid_t child; + int option; + char *host = NULL; + char *port = "2401"; + char *listen = NULL; + struct sockaddr_in them; + int detach = 0; + char *pidfile = NULL; + FILE *fp; + char *cp; + int len; + int ns; + + /* make sure we are running with root privileges, because + we need it later for chroot, switch_to_user, etc. */ + if (geteuid() != 0) + error(1, 0, "root privileges required for pserver operation"); + + /* process "cvs pserverd" command options */ + optind = 0; + while ((option = getopt(argc, argv, "vl:dp:A:R:")) != EOF) { + switch ((char) option) { + case 'v': + pserverd_verbose = 1; + break; + case 'l': + listen = xstrdup(optarg); + break; + case 'd': + detach = 1; + pserverd_verbose = 0; + break; + case 'p': + pidfile = xstrdup(optarg); + break; + case 'A': + pserverd_anonymous_user = xstrdup(optarg); + break; + case 'R': + cp = xstrdup(optarg); + pserver_chroot.user = cp; + if ((cp = strchr(cp, ':')) == NULL) + error(1, 0, "invalid -R option argument"); + *cp++ = '\0'; + pserver_chroot.repos = cp; + if ((cp = strchr(cp, ':')) == NULL) + error(1, 0, "invalid -R option argument"); + *cp++ = '\0'; + pserver_chroot.chroot = cp; + break; + case '?': + default: + usage(pserverd_usage); + break; + } + } + argc -= optind; + argv += optind; + if (argc < 0) + usage(pserverd_usage); + + /* optionally go into the background as a real daemon */ + if (detach) + proc_daemon(0, 0); + + /* optionally write out the pid */ + if (pidfile != NULL) { + if ((fp = fopen(pidfile, "w")) == NULL) + error(1, 0, "unable to write pid to file %s: %s", + pidfile, strerror(errno)); + fprintf(fp, "%ld\n", (long)getpid()); + fclose(fp); + } + + /* listen on the TCP/IP socket */ + if (listen != NULL) { + if ((port = strrchr(listen, ':')) != NULL) + *(port++) = '\0'; + if (strcasecmp(listen, "*") == 0 || strcmp(listen, "0.0.0.0") == 0) + host = NULL; + else + host = listen; + } + if ((sd = tcp_listen(host, port)) < 0) + error(1, 0, "unable to listen (%s:%s): %s", + host != NULL ? host : "*", port, strerror(errno)); + + /* make sure we reap the childs */ + signal(SIGCHLD, &pserver_daemon_reapchild); + + /* daemon loop */ + for (;;) { + len = sizeof(them); + ns = accept(sd, (struct sockaddr *)&them, &len); + if (ns < 0) { + if (errno == EINTR) + continue; + error(1, 0, "accept(2) failed: %s", strerror(errno)); + } + switch (child = fork()) { + case -1: + error(1, 0, "unable to fork(2): %s", strerror(errno)); + break; + case 0: + /* child */ + close(sd); + signal(SIGCHLD, SIG_DFL); + + /* connect stdin/stdout to socket */ + dup2(ns, STDIN_FILENO); + dup2(ns, STDOUT_FILENO); + + /* + * perform "cvs pserver" authentication handshake. + */ + pserver_handshake(); + + /* + * just return to caller, i.e., the main() procedure + * which in turn will dispatch into "cvs server" code + * for us... + */ + return 0; + break; + default: + /* parent */ + if (pserverd_verbose) + fprintf(stderr, "cvs pserverd[%ld]: child process (pid %ld): started.\n", + (long)getpid(), (long)child); + close(ns); + break; + } + } + exit(0); + return 0; +} + +static void +pserver_handshake( + void) +{ + char *tmp = NULL; + size_t tmp_allocated = 0; + char *repository = NULL; + size_t repository_allocated = 0; + char *username = NULL; + size_t username_allocated = 0; + char *password = NULL; + size_t password_allocated = 0; + char *host_user = NULL; + char *descrambled_password; + int verify_and_exit = 0; + char *chrootdir = NULL; + int on; + +#ifdef SO_KEEPALIVE + /* Set SO_KEEPALIVE on the socket, so that we don't hang forever + if the client dies while we are waiting for input. */ + on = 1; + (void)setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, + (char *)&on, sizeof(on)); +#endif + + /* Make sure the protocol starts off on the right foot... */ + getline(&tmp, &tmp_allocated, stdin); + + if (strcmp (tmp, "BEGIN VERIFICATION REQUEST\n") == 0) + verify_and_exit = 1; + else if (strcmp (tmp, "BEGIN AUTH REQUEST\n") == 0) + ; + else + error (1, 0, "bad auth protocol start: %s", tmp); + + /* Get the three important pieces of information in order. */ + getline(&repository, &repository_allocated, stdin); + getline(&username, &username_allocated, stdin); + getline(&password, &password_allocated, stdin); + + /* Make them pure. */ + strip_trailing_newlines(repository); + strip_trailing_newlines(username); + strip_trailing_newlines(password); + +#ifdef RSE_PATCH_MAPROOT + root_map_it(repository, &repository, 0); +#endif + + /* ... and make sure the protocol ends on the right foot. */ + getline(&tmp, &tmp_allocated, stdin); + if (strcmp (tmp, verify_and_exit ? + "END VERIFICATION REQUEST\n" : "END AUTH REQUEST\n") != 0) + error (1, 0, "bad auth protocol end: %s", tmp); + + if (!root_allow_ok(repository)) + goto i_hate_you; + config = get_root_allow_config(repository, gConfigPath); + + /* We need the real cleartext before we hash it. */ + descrambled_password = descramble(password); + host_user = check_password(username, descrambled_password, repository); + memset(descrambled_password, 0, strlen(descrambled_password)); + free(descrambled_password); + + if (host_user == NULL) { + i_hate_you: + printf("I HATE YOU\n"); + fflush(stdout); + if (pserverd_verbose) + fprintf(stderr, "cvs pserverd[%ld]: status=FAILED user=%s root=%s\n", + (long)getpid(), username, repository); + exit (EXIT_FAILURE); + } + + /* Don't go any farther if we're just responding to "cvs login". */ + if (verify_and_exit) { + printf("I LOVE YOU\n"); + fflush(stdout); + if (pserverd_verbose) + fprintf(stderr, "cvs pserverd[%ld]: status=OK user=%s root=%s (huser=%s)\n", + (long)getpid(), username, repository, host_user); + exit(0); + } + + /* Set Pserver_Repos so that we can check later that the same + repository is sent in later client/server protocol. */ + Pserver_Repos = xmalloc(strlen(repository)+1); + strcpy(Pserver_Repos, repository); + + /* Optionally perform a chroot */ + if (pserver_chroot.user != NULL) { + if ( strcmp(username, pserver_chroot.user) == 0 + && ( strcmp(repository, pserver_chroot.repos) == 0 + || strcmp(pserver_chroot.repos, "*") == 0 )) { + chrootdir = pserver_chroot.chroot; + if (chdir(chrootdir) == -1) + error(1, 0, "failed to chdir(2) to %s: %s", chrootdir, strerror(errno)); + if (chroot(chrootdir) == -1) + error(1, 0, "failed to chroot(2) to %s: %s", chrootdir, strerror(errno)); + } + } + + /* Additionally switch to read-only mode for anonymous user */ + if (pserverd_anonymous_user != NULL) { + if (strcmp(username, pserverd_anonymous_user) == 0) { + logoff = 1; + } + } + + /* Switch to run as this user. */ + switch_to_user(username, host_user); + free(tmp); + free(repository); + free(username); + free(password); + + printf("I LOVE YOU\n"); + fflush(stdout); + if (pserverd_verbose) + fprintf(stderr, "cvs pserverd[%ld]: status=OK user=%s root=%s (huser=%s hroot=%s)\n", + (long)getpid(), username, repository, host_user, chrootdir != NULL ? chrootdir : "/"); + return; +} + +#endif /* RSE_PATCH_PSERVERD */ Index: src/subr.c =================================================================== RCS file: /v/ossp/pkg/tool/cvs/cvs/cvs/src/subr.c,v retrieving revision 1.1.1.18 diff -u -d -r1.1.1.18 subr.c --- src/subr.c 2 Oct 2005 15:17:21 -0000 1.1.1.18 +++ src/subr.c 27 Oct 2005 07:49:14 -0000 @@ -293,6 +293,22 @@ uid_t uid; #endif +#ifdef RSE_PATCH_CVSUSER +#ifndef RSE_PATCH_CVSUSER_CALLER +#define RSE_PATCH_CVSUSER_CALLER "cvs" +#endif + uid = getuid(); + if ((pw = (struct passwd *)getpwnam(RSE_PATCH_CVSUSER_CALLER)) != NULL) { + if (pw->pw_uid == uid) { + char *name; + if ((name = getenv("CVSUSER")) != NULL) { + cache = xstrdup(name); + return cache; + } + } + } +#endif + /* If there is a CVS username, return it. */ #ifdef AUTH_SERVER_SUPPORT if (CVS_Username != NULL) @@ -763,6 +779,72 @@ return backup_name; } +#ifdef RSE_PATCH_HANDLE +/* handle: 2000041317203601 + date1: 2000/04/13 17:20:36 + date2: 2000/04/13 17:20:37 */ +int handle2dates(char *handle, time_t *t1, time_t *t2) +{ + int Y,M,D,h,m,s,o; + char buf[17]; + time_t t; + struct tm tm; + int rev = 0; + int i; + + /* check for correct handle format */ + if (handle == NULL) + return 0; + if (handle[0] == '!') { + handle++; + rev = 1; + } + if (strlen(handle) != 16) + return 0; + for (i = 0; i < 16; i++) + if (!isdigit(handle[i])) + return 0; + + /* parse out handle parts */ + strcpy(buf, handle); + o = atoi(buf+14); + buf[14] = '\0'; + s = atoi(buf+12); + buf[12] = '\0'; + m = atoi(buf+10); + buf[10] = '\0'; + h = atoi(buf+8); + buf[8] = '\0'; + D = atoi(buf+6); + buf[6] = '\0'; + M = atoi(buf+4); + buf[4] = '\0'; + Y = atoi(buf); + + /* assemble parts into a time value */ + memset(&tm, 0, sizeof tm); + tm.tm_sec = s; + tm.tm_min = m; + tm.tm_hour = h; + tm.tm_mday = D; + tm.tm_mon = M - 1; + tm.tm_year = Y - 1900; + t = mktime(&tm); + if (t == -1) + return 0; + + /* output the first and second time */ + if (rev) { + *t2 = t; + *t1 = t + o; + } + else { + *t1 = t; + *t2 = t + o; + } + return 1; +} +#endif /* Index: src/update.c =================================================================== RCS file: /v/ossp/pkg/tool/cvs/cvs/cvs/src/update.c,v retrieving revision 1.1.1.17 diff -u -d -r1.1.1.17 update.c --- src/update.c 22 Sep 2005 18:49:17 -0000 1.1.1.17 +++ src/update.c 4 Oct 2005 19:23:41 -0000 @@ -1716,6 +1716,17 @@ patch can't handle that. */ fail = 1; } +#ifdef RSE_PATCH_FASTERUPDATE + else { + /* + * Don't send a diff if just sending the entire file + * would be smaller... + */ + fseek(e, 0L, SEEK_END); + if (file_info->st_size < ftell(e)) + fail = 1; + } +#endif fclose (e); } Index: src/version.c =================================================================== RCS file: /v/ossp/pkg/tool/cvs/cvs/cvs/src/version.c,v retrieving revision 1.1.1.10 diff -u -d -r1.1.1.10 version.c --- src/version.c 4 Sep 2005 00:27:44 -0000 1.1.1.10 +++ src/version.c 4 Oct 2005 19:23:41 -0000 @@ -62,6 +62,9 @@ some idea of how long ago their version of CVS was released. */ (void) fputs (PACKAGE_STRING, stdout); +#ifdef RSE_PATCHES + (void) fputs (" [RSE]", stdout); +#endif (void) fputs (config_string, stdout); #ifdef CLIENT_SUPPORT