diff options
author | peter <peter@FreeBSD.org> | 1995-12-10 22:31:43 +0000 |
---|---|---|
committer | peter <peter@FreeBSD.org> | 1995-12-10 22:31:43 +0000 |
commit | 2dbe609ba8a4cefd78f3867f74b53b3a507b9883 (patch) | |
tree | abe4b0172b1d26d0885510263d55640aede80f8f | |
parent | 97dcd3ee2c52c29f7f4f4ad1a792395166114f77 (diff) | |
download | FreeBSD-src-2dbe609ba8a4cefd78f3867f74b53b3a507b9883.zip FreeBSD-src-2dbe609ba8a4cefd78f3867f74b53b3a507b9883.tar.gz |
Import CVS-1.6.3-951211.. Basically, this is the cvs-1.6.2 release
plus a couple of minor changes..
Some highlights of the new stuff that was not in the old version:
- remote access support.. full checkout/commit/log/etc..
- much improved dead file support..
- speed improvements
- better $CVSROOT handling
- $Name$ support
- support for a "cvsadmin" group to cut down rampant use of "cvs admin -o"
- safer setuid/setgid support
- many bugs fixed.. :-)
- probably some new ones.. :-(
- more that I cannot remember offhand..
45 files changed, 16307 insertions, 0 deletions
diff --git a/gnu/usr.bin/cvs/BUGS b/gnu/usr.bin/cvs/BUGS new file mode 100644 index 0000000..3ad13a8 --- /dev/null +++ b/gnu/usr.bin/cvs/BUGS @@ -0,0 +1,248 @@ +* `cvs checkout -d nested/dir/path <module>' just doesn't work. The + simpler version -- `cvs checkout -d single-dir <module>' works, + however. + + +* CVS leaves .#mumble files around when a conflict occurs. (Note: + this is intentional and is documented in doc/cvs.texinfo. Of course + whether it is a good idea is a separate question). + + +* pcl-cvs doesn't like it when you try to check in a file which isn't + up-to-date. The messages produced by the server perhaps don't match + what pcl-cvs is looking for. + + +* From: Roland McGrath <roland@gnu.ai.mit.edu> + To: Cyclic CVS Hackers <cyclic-cvs@cyclic.com> + Subject: weird bug + Date: Sat, 25 Mar 1995 16:41:41 -0500 + X-Windows: Even your dog won't like it. + + I just noticed some droppings on my disk from what must be a pretty weird + bug in remote CVS. + + In my home directory on a repository machine I use, I find: + + drwxr-xr-x 4 roland staff 512 Mar 7 14:08 cvs-serv28962 + drwxr-xr-x 4 roland staff 512 Mar 7 14:11 cvs-serv28978 + drwxr-xr-x 4 roland staff 512 Mar 7 15:13 cvs-serv29141 + + OK, so these are leftover cruft from some cvs run that got aborted. + Well, it should clean up after itself, but so what. + + The last one is pretty dull; the real weirdness is the contents of the + first two directories. + + duality 77 # ls -RF cvs-serv28978/ + CVS/ cvs-serv28978/ + + cvs-serv28978/CVS: + Entries Repository + + cvs-serv28978/cvs-serv28978: + arpa/ + + cvs-serv28978/cvs-serv28978/arpa: + CVS/ cvs-serv28978/ + + cvs-serv28978/cvs-serv28978/arpa/CVS: + Entries Repository + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978: + assert/ + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert: + CVS/ cvs-serv28978/ + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/CVS: + Entries Repository + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978: + bare/ + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare: + CVS/ cvs-serv28978/ + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/CVS: + Entries Repository + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978: + conf/ + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf: + CVS/ cvs-serv28978/ + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/CVS: + Entries Repository + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/cvs-serv28978: + crypt/ + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/cvs-serv28978/crypt: + CVS/ cvs-serv28978/ + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/cvs-serv28978/crypt/CVS: + Entries Repository + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/cvs-serv28978/crypt/cvs-serv28978: + csu/ + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/cvs-serv28978/crypt/cvs-serv28978/csu: + CVS/ cvs-serv28978/ + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/cvs-serv28978/crypt/cvs-serv28978/csu/CVS: + Entries Repository + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/cvs-serv28978/crypt/cvs-serv28978/csu/cvs-serv28978: + ctype/ + + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/cvs-serv28978/crypt/cvs-serv28978/csu/cvs-serv28978/ctype: + CVS/ cvs-serv28978/ + + [...] + + ls: cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/cvs-serv28978/crypt/cvs-serv28978/csu/cvs-serv28978/ctype/cvs-serv28978/dirent/cvs-serv28978/elf/cvs-serv28978/gnu/cvs-serv28978/gnulib/cvs-serv28978/grp/cvs-serv28978/hurd/cvs-serv28978/hurd/hurd/cvs-serv28978/inet/cvs-serv28978/inet/arpa/cvs-serv28978/inet/netinet[...]/cvs-serv28978/posix/cvs-serv28978/posix/glob/cvs-serv28978/posix/gnu/cvs-serv28978/posix/sys/cvs-serv28978/protocols/cvs-serv28978/pwd/cvs-serv28978/resolv/cvs-serv28978/resolv/arpa/cvs-serv28978/resolv/sys/cvs-serv28978/resource/cvs-serv28978/resource/sys/cvs-serv28978/rpc/cvs-serv28978/setjmp/cvs-serv28978/signal/cvs-serv28978/signal/sys/cvs-serv28978/socket/cvs-serv28978/socket: File name too long + cvs-serv28978/cvs-serv28978/arpa/cvs-serv28978/assert/cvs-serv28978/bare/cvs-serv28978/conf/cvs-serv28978/crypt/cvs-serv28978/csu/cvs-serv28978/ctype/cvs-serv28978/dirent/cvs-serv28978/elf/cvs-serv28978/gnu/cvs-serv28978/gnulib/cvs-serv28978/grp/cvs-serv28978/hurd/cvs-serv28978/hurd/hurd/cvs-serv28978/inet/cvs-serv28978/inet/arpa/cvs-serv28978/inet/netinet[...]/cvs-serv28978/posix/glob/cvs-serv28978/posix/gnu/cvs-serv28978/posix/sys/cvs-serv28978/protocols/cvs-serv28978/pwd/cvs-serv28978/resolv/cvs-serv28978/resolv/arpa/cvs-serv28978/resolv/sys/cvs-serv28978/resource/cvs-serv28978/resource/sys/cvs-serv28978/rpc/cvs-serv28978/setjmp/cvs-serv28978/signal/cvs-serv28978/signal/sys/cvs-serv28978/socket/cvs-serv28978: + + +* From: billr@mpd.tandem.com (Bill Robertson) + Subject: Problem with rtag and the -D option + Date: Fri, 17 Mar 1995 10:53:29 -0600 (CST) + + I have been trying to use the -D option to specify a date for tagging, but + rtag does not recognize the -D option. It is documented to do so and I've + tested the use of -D with cvs update and cvs diff and it works fine there. + + +* We need some version numbers really badly. Are there some + (and Charles Hannum is just not including them in his reports), or do + we simply have no reliable way to distinguish between the various + versions of rCVS people on the list are running? + + Now that I think of it, version numbers present a problem when + people can update their sources anytime and rebuild. I think the + solution is to increment a minor version number *every* time a bug is + fixed, so we can identify uniquely what someone is running when they + submit a report. This implies recording the version increments in the + ChangeLog; that way we can just look to see where a particular version + lies in relation to the flow of changing code. + + Should we be doing same with Guppy? I guess not -- it's only + important when you have people who are updating directly from your + development tree, which is the case with the remote-cvs folks. + + Thoughts? + + +* (Charles Hannum <mycroft@ai.mit.edu>) has these bugs: + + I just tossed remote CVS at a fairly large source tree that I already + had, and noticed a few problems: + + 1) server.c assumes that /usr/tmp is a valid default for the place to + store files uploaded from the client. There are a number of systems + that now use /var/tmp. These should probably be detected by autoconf. + + 2) The server deals rather ungracefully with the tmp directory + becoming full. + + 3) There's some oddness with relative paths in Repository files that + causes the directory prefix to be added twice; e.g. if I have CVSROOT + set to `machine:/this/dir', and I try to update in a directory whose + Repository file says `src/bin', the server looks in + `/this/dir/machine:/this/dir/src/bin'. + +* From: "Charles M. Hannum" <mycroft@ai.mit.edu> + To: jimb@duality.gnu.ai.mit.edu, roland@duality.gnu.ai.mit.edu + Subject: Serious flaw in remote CVS + Date: Wed, 22 Feb 1995 20:54:36 -0500 + + I just found a major flaw in the current implementation. Because the + sockets are not changed to non-blocking mode, write(2)s can hang. In + some cases, this happens on both sides at the same time, with the + socket buffers full in both directions. This causes a deadlock, + because both processes are stuck in I/O wait and thus never drain + their input queues. + + Until this is fixed, I can't use it. I'll look at the problem myself + at some point, but I don't know when. + + + From: "Charles M. Hannum" <mycroft@ai.mit.edu> + To: remote-cvs@cyclic.com + Cc: jimb@totoro.bio.indiana.edu + Subject: Re: forwarded message from Charles M. Hannum + Date: Wed, 22 Feb 1995 22:07:07 -0500 + + FYI, this happened because the tmp directory on the server became + full. Somehow the server started interpreting the files the client + was sending as commands, and started spewing tons of errors. + Apparently the errors are sent with blocking I/O, or something, and + thus allowed the deadlock to happen. + + +* From: "Charles M. Hannum" <mycroft@ai.mit.edu> + To: remote-cvs@cyclic.com + Subject: Regarding that relative path problem + Date: Thu, 23 Feb 1995 02:41:51 -0500 + + This is actually more serious. If you have `bar.com:/foo' as your CVS + root directory, then: + + 1) When you check things out, the Repository files will contain + `/foo/...' (i.e. without the machine name), which makes little sense. + + 2) If you instead have a relative path, when the Repository file is + read, `bar.com:/foo' is prepended. This is sent to the server, but + confuses it, because it's not expecting the machine name to be + prepended. + + A slightly klugy fix would be to have the client prepend the machine + name when writing a new Repository file, and strip it off before + sending one to the server. This would be backward-compatible with the + current arrangement. + + +* From: "Charles M. Hannum" <mycroft@ai.mit.edu> + To: remote-cvs@cyclic.com + Subject: Still one more bug + Date: Sat, 25 Feb 1995 17:01:15 -0500 + + mycroft@duality [1]; cd /usr/src/lib/libc + mycroft@duality [1]; cvs diff -c2 '-D1 day ago' -Dnow + cvs server: Diffing . + cvs server: Diffing DB + cvs [server aborted]: could not chdir to DB: No such file or directory + mycroft@duality [1]; + + `DB' is an old directory, which no longer has files in it, and is + removed automatically when I use the `-P' option to checkout. + + This error doesn't occur when run locally. + + P.S. Is anyone working on fixing these bugs? + + +* From: Roland McGrath <roland@gnu.ai.mit.edu> + To: Cyclic CVS Hackers <cyclic-cvs@cyclic.com> + Subject: bizarre failure mode + Date: Tue, 7 Mar 95 14:17:28 -0500 + + This is pretty weird: + + CVS_SERVER='TMPDIR=. /usr/local/bin/cvs' ../cvs-build/src/cvs update -q + cvs [server aborted]: could not get working directory: Result too large + [Exit 1] + asylum 29 % grep 'Result too large' /usr/include/sys/errno.h + #define ERANGE 34 /* Result too large */ + + Now, getcwd fails with ERANGE when the buffer is too small. But I don't + know why that would be the case; I don't think there are exceptionally long + directory names involved. It would be robust to notice ERANGE and use a + bigger buffer. But I suspect something weirder is going on. + + The repository in question in duality.gnu.ai.mit.edu:/gd4/gnu/cvsroot/libc. + + Send me a PGP-signed message if you want the password to use the machine + where the problem showed up. diff --git a/gnu/usr.bin/cvs/INSTALL b/gnu/usr.bin/cvs/INSTALL new file mode 100644 index 0000000..898af66 --- /dev/null +++ b/gnu/usr.bin/cvs/INSTALL @@ -0,0 +1,356 @@ +#ident "$CVSid$" + +First, read the README file. If you're still happy... + +CVS has been tested on the following platforms. The most recent +version of CVS reported to have been tested is indicated, but more +recent versions of CVS probably will work too. Please send updates to +this list to info-cvs@prep.ai.mit.edu. + +Alpha: + DEC Alpha running OSF/1 version 1.3 using cc (about 1.4A2) + DEC Alpha running OSF/1 version 2.0 (1.4.90) + DEC Alpha running OSF/1 version 2.1 (about 1.4A2) + DEC Alpha running OSF/1 version 3.0 (1.5.95) (footnote 7) +HPPA: + HP 9000/710 running HP-UX 8.07A using gcc (about 1.4A2) + HP 9000/715 running HP-UX 9.01 (1.6) + HPPA 1.1 running HP-UX A.09.03 (1.5.95) (footnote 8) + NextSTEP 3.3 (1.4.92, a few tweaks needed) +i386 family: + Gateway P5-66 (pentium) running Solaris 2.4 using gcc (about 1.4A2) + PC Clone running UnixWare v1.1.1 using gcc (about 1.4A2) + PC Clone running ISC 4.0.1 (1.5.94) + PC Clone running Fintronic Linux 1.2.5 (1.5) + PC Clone running BSDI 2.0 (1.4.93) (footnote 5) + PC Clone running Windows NT 3.51 (1.6.2 client-only) + FreeBSD 2.0.5, i486, gcc (1.5.95) + NextSTEP 3.3 (1.4.92, a few tweaks needed) + SCO Unix 3.2.4.2 (1.4.93) (footnote 4) + SCO OpenServer 5.0.0, "CC='cc -b elf' configure" +m68k: + Sun 3 running SunOS 4.1.1_U1 w/ bundled K&R /usr/5bin/cc (1.6) + NextSTEP 3.3 (1.4.92, a few tweaks needed) +m88k: + Data General AViiON running dgux 5.4R2.10 (1.5) + Harris Nighthawk 5800 running CX/UX 7.1 (1.5) (footnote 6) +MIPS: + DECstation running Ultrix 4.2a (1.4.90) + DECstation running Ultrix 4.3 (1.5) + SGI running Irix 4.0.5H using gcc and cc (about 1.4A2) (footnote 2) + SGI running Irix 5.3 (1.4.93) + SGI running Irix-6 (about 1.4.90) (footnote 3) + Siemens-Nixdorf RM600 running SINIX-Y (1.6) +PowerPC or RS/6000: + IBM RS/6000 running AIX 3.2.5 (cc=xlc, CVS 1.5) + IBM RS/6000 running AIX 4.1 using gcc and cc (about 1.4A2) (footnote 1) +SPARC: + Sun SPARC running SunOS 4.1.4 w/ bundled K&R /usr/5bin/cc (1.6) + Sun SPARC running SunOS 4.1.3, 4.1.2, and 4.1.1 (1.5) + Sun SPARC running SunOS 4.1.3, w/ bundled K&R cc (1.5.94) + Sun SPARCstation 10 running Solaris 2.3 using gcc and cc (about 1.4A2) + Sun SPARCstation running Solaris 2.4 using gcc and cc (about 1.5.91) + Sun SPARC running Solaris 2.5 (2.5 beta?) (1.6.2) + NextSTEP 3.3 (1.4.92, a few tweaks needed) + +(footnote 1) + AIX 4.1 systems fail to run "configure" due to bugs in their + "/bin/sh" implementation. You might want to try feeding the + configure script to "bash" ported to AIX 4.1. (about 1.4A2). + +(footnote 2) + Some Irix 4.0 systems may core dump in malloc while running + CVS. We believe this is a bug in the Irix malloc. You can + workaround this bug by linking with "-lmalloc" if necessary. + (about 1.4A2). + +(footnote 3) + There are some warnings about pointer casts which can safely be + ignored. (about 1.4.90). + +(footnote 4) Comment out the include of sys/time.h in src/server.c. (1.4.93) + You also may have to make sure TIME_WITH_SYS_TIME is undef'ed. + +(footnote 5) Change /usr/tmp to /var/tmp in src/server.c (2 places) (1.4.93). + +(footnote 6) Build in ucb universe with COFF compiler tools. Put + /usr/local/bin first in PATH while doing a configure, make + and install of GNU diffutils-2.7, rcs-5.7, then cvs-1.5. + +(footnote 7) Manoj Srivastava <srivasta@pilgrim.umass.edu> reports + success with this configure command: + CC=cc CFLAGS='-O2 -Olimit 2000 -std1' ./configure --verbose alpha-dec-osf + +(footnote 8) Manoj Srivastava <srivasta@pilgrim.umass.edu> reports + success with this configure command: + CC=cc CFLAGS='+O2 -Aa -D_HPUX_SOURCE' ./configure --verbose hppa1.1-hp-hpux + +------------------------------------------------------------------------------- + +Installation under Unix: + +1) Run "configure": + + $ ./configure + + You can specify an alternate destination to override the default with + the --prefix option: + + $ ./configure --prefix=/usr/local/gnu + + or some path that is more appropriate for your site. The default prefix + value is "/usr/local", with binaries in sub-directory "bin", manual + pages in sub-directory "man", and libraries in sub-directory "lib". + + This release of CVS also requires RCS commands to be installed in + the user's PATH (or a path you have configured in src/options.h). + If you don't have RCS, you will need to get it from GNU as well. It + is best to get the version 5.7 (or later) version of RCS, available + from prep.ai.mit.edu in the file pub/gnu/rcs-5.7.tar.gz. It is best + (although not essential) to avoid RCS versions 5.6.[5-7] beta + because the rcsmerge therein defaults to -A instead of -E which + affects the way CVS handles conflicts (this is fixed in RCS 5.6.8 + and RCS 5.7). + + Along with RCS, you will want to run GNU diffutils. This will allow + revision control of files with binary data (a real nice feature). + You will need at least version 1.15 of GNU diff for this to work. + The current version of GNU diffutils is 2.7, and it is also + available from prep.ai.mit.edu in the file pub/gnu/diffutils-2.7.tar.gz. + + WARNING: Be sure that you (have) configure(d) RCS to work correctly + with GNU diff to avoid other configuration problems. + + Configure will attempt to discern the location of your most capable + version of diff, and tries to find the GNU Diffutils version first. + You can explicitly tell configure to use the diffutils that's + installed in the same place you intend to install CVS: + + $ ./configure --with-diffutils + + Or, if you've installed it somewhere else, you can give configure + the full pathname: + + $ ./configure --with-diffutils=/usr/gnu/bin/diff + + Configure will also try to find a version of grep that supports the + '-s' option, and tries to find the GNU Grep version first. You can + similarly tell it where to find GNU Grep: + + $ ./configure --with-gnugrep + $ ./configure --with-gnugrep=/usr/gnu/bin/grep + + If you are using the remote client, you will need a version of patch + which understands unidiffs (such as any recent version of GNU + patch). Configure does not yet check to see if you've got this, so + be careful! + + NOTE: The configure program will cache the results of the previous + configure execution. If you need to re-run configure from scratch, you + may need to run "make distclean" first to remove the cached + configuration information. + + Try './configure --help' for further information on its usage. + + NOTE ON CVS's USE OF NDBM: + + By default, CVS uses some built-in ndbm emulation code to allow + CVS to work in a heterogeneous environment. However, if you have + a very large modules database, this may not work well. You will + need to edit src/options.h to turn off the MY_NDBM #define and + re-run configure. If you do this, the following comments apply. + If not, you may safely skip these comments. + + If you configure CVS to use the real ndbm(3) libraries and + you do not have them installed in a "normal" place, you will + probably want to get the GNU version of ndbm (gdbm) and install + that before running the CVS configure script. Be aware that the + GDBM 1.5 release does NOT install the <ndbm.h> header file included + with the release automatically. You may have to install it by hand. + + If you configure CVS to use the ndbm(3) libraries, you cannot + compile CVS with GNU cc (gcc) on Sun-4 SPARC systems. However, gcc + 2.0 may have fixed this limitation if -fpcc-struct-return is + defined. When using gcc on other systems to compile CVS, you *may* + need to specify the -fpcc-struct-return option to gcc (you will + *know* you have to if "cvs checkout" core dumps in some ndbm + function). You can do this as follows: + + $ CC='gcc -fpcc-struct-return' ./configure + + for sh, bash, and ksh users and: + + % setenv CC 'gcc -fpcc-struct-return' + % ./configure + + for csh and tcsh users. + + END OF NOTE FOR NDBM GUNK. + +2) Edit src/options.h. Appropriate things to look at may be the + invocation locations of programs like DIFF, GREP, RM, and SORT. + Also glance at the default values for the environment variables + that CVS uses, in particular, the RCSBIN variable, which holds the + path to where the RCS programs live on your system. The + likelihood is that you don't have to change anything here, except + perhaps adding the -a option to DIFF if you are using GNU diff. + +3) Try to build it: + + $ make + + This will (hopefully) make the needed CVS binaries within the "src" + directory. If something fails for your system, using the "cvsbug" + script submit your "config.status" file together with your host + type, operating system and compiler information, make output, and + anything else you think will be helpful. + + You may also wish to validate the correctness of the new binary by + running the regression tests: + + $ make check + + Note that if your /bin/sh doesn't support shell functions, you'll + have to try something like this, where "/bin/sh5" is replaced by the + pathname of a shell which handles normal shell functions: + + $ make SHELL=/bin/sh5 check + + WARNING: This test can take quite a while to run, esp. if your + disks are slow or over-loaded. + + If you receive any un-expected output from the regression tests, + using the "cvsbug" script please submit your "config.status" file, + together with your host type, operating system and compiler + information, the contents of /tmp/cvs-sanity/check.log, and any + "make check" output. + +4) Install the binaries/documentation: + + $ make install + + Depending on your installation's configuration, you may need to be + root to do this. + +5) Take a look at the CVS documentation. + + $ man cvs + + and + + $ info cvs + + See what it can do for you, and if it fits your environment (or can + possibly be made to fit your environment). If things look good, + continue on... + +6) Setup the master source repository. Choose a directory with ample disk + space available for source files. This is where the RCS ",v" files + will be stored. Note that this should be some shared directory for your + site. It should probably be auto-mounted, if you're running NFS. + + Say you choose "/src/master" as the root of your source repository. + Run the "cvsinit" script to help you set it up. It will ask you to + enter the path to your CVSROOT area. You would enter /src/master in + this example. + + $ ./cvsinit + + The cvsinit script will setup a reasonable CVSROOT area to start with. + It is also valuable to folks who already have a CVSROOT area setup from + using earlier releases of CVS. It assumes that you have installed CVS + already (step 4) and that the RCS programs (co and ci) are in your + PATH. There are many ways to customize CVS for your site. Read the + cvs(5) manual page when you get the chance. + +7) Have all users of the CVS system set the CVSROOT environment + variable appropriately to reflect the placement of your source + repository. If the above example is used, the following commands + can be placed in user's ~/.profile, ~/.bash_profile file; or in the + site-wide /etc/profile: + + CVSROOT=/src/master; export CVSROOT + + for sh/bash/ksh users, or place the following commands in the user's + ~/.cshrc, ~/.login, or /etc/chsrc file: + + setenv CVSROOT /src/master + + for csh/tcsh users. If these environment variables are not already set + in your current shell, set them now (or source the login script you + just edited). You will need to have the CVSROOT environment variable + set to continue on to the next step. + +8) It might be a good idea to jump right in and put the CVS distribution + directly under CVS control. From within the top-level directory of the + CVS distribution (the one that contains this README file) do the + following commands: + + $ make distclean + $ cvs import -m 'CVS 1.6 distribution' cvs CVS CVS-1_6 + +9) Having done step 8, one should be able to checkout a fresh copy of the + CVS distribution and hack away at the sources with the following command: + + $ cd + $ cvs checkout cvs + + This will make the directory "cvs" in your current directory and + populate it with the appropriate CVS files and directories. + +10) Remember to edit the modules file manually when sources are checked in + with "cvs import" or "cvs add". A copy of the modules file for editing + can usually be retrieved with the "cvs checkout modules" command, and + definitely with the "cvs checkout CVSROOT" command. See cvs(5). + +11) Read the NEWS file to see what's new. + +12) Hack away. + +------------------------------------------------------------------------------- + +Detailed information about your interaction with "configure": + +The "configure" script and its interaction with its options and the +environment is described here. For more detailed documentation about +"configure", please refer to the GNU Autoconf documentation. + +Supported options are: + + --srcdir=DIR Useful for compiling on many different + machines sharing one source tree. + --prefix=DIR The root of where to install the + various pieces of CVS (/usr/local). + --exec_prefix=DIR If you want executables in a + host-dependent place and shared + things in a host-independent place. + --with-diffutils[=PATH] Assume use of GNU diffutils is possible. + --with-gnugrep[=PATH] Assume use of GNU grep is possible. + +The following environment variables override configure's default +behaviour: + + CC If not set, tries to use gcc first, + then cc. Also tries to use "-g -O" + as options, backing down to -g + alone if that doesn't work. + INSTALL If not set, tries to use "install", then + "./install-sh" as a final choice. + RANLIB If not set, tries to determine if "ranlib" + is available, choosing "echo" if it doesn't + appear to be. + YACC If not set, tries to determine if "bison" + is available, choosing "yacc" if it doesn't + appear to be. + +------------------------------------------------------------------------------- +Installation under Windows NT: + +You may find interesting information in windows-NT/README. + +1) Using Microsoft Visual C++ version 2.1, open the project `cvsnt.mak', + in the top directory of the CVS distribution. +2) Choose "Build cvs.exe" from the "Project" menu. +3) MSVC will place the executable file cvs.exe in WinDebug, or whatever + your target directory is. +------------------------------------------------------------------------------- diff --git a/gnu/usr.bin/cvs/MINOR-BUGS b/gnu/usr.bin/cvs/MINOR-BUGS new file mode 100644 index 0000000..7b85719 --- /dev/null +++ b/gnu/usr.bin/cvs/MINOR-BUGS @@ -0,0 +1,60 @@ +Low-priority bugs go here. We don't have many yet -- everything is +high-priority at the moment. :-) + + +* From: Jeff Johnson <jbj@brewster.JBJ.ORG> + To: cyclic-cvs@cyclic.com + Subject: Named_Root assumes . on server + Date: Wed, 17 May 1995 11:04:53 -0400 (EDT) + + Problem: + On server, Name_Root() attempts (aggressively) to set CVSADM_Root. + If ~/CVS/Root exists (wrto rsh login), then CVSADM_Root will be + initialized from that file. The sanity check between the root + repository and the invocation will fail if the two values are not + coincidentally the same. + + Workaround: + There's a zillion ways to fix this bugture/featurelet. My current + workaround is to remove ~/CVS/Root on the server. I shall attempt + a better fix as soon as I can determine what appears politically + correct. IMHO, the CVS/Root stuff (and getenv("CVSROOT") also) is + a bit fragile and tedious in an rcmd() driven CCVS environment. + + +* (Jeff Johnson <jbj@jbj.org>) + I tried a "cvs status -v" and received the following: + + ? CVS + ? programs/CVS + ? tests/CVS + cvs server: Examining . + =================================================================== + File: Install.dec Status: Up-to-date + ... + + I claim that CVS dirs should be ignored. + + +* I sometimes get this message: + + Could not look up address for your host. Permission denied. + cvs [update aborted]: premature end of file from server + + The client's response should be cleaned up. + +* In the gb-grep module, update-ChangeLog (and therefore, I assume, + rcs2log) truncates file names --- I get entries for things called + ring/lenstring.h instead of lenstring/lenstring.h. + +* On remote checkout, files don't have the right time/date stamps in + the CVS/Entries files. Doesn't look like the C/S protocol has any + way to send this information along (according to cvsclient.texi). + Perhaps we can spiff it up a bit by using the conflict field for the + stamp on the checkout/update command. Please note that this really + doesn't do very much for us even if we get it done. + +* Does the function that lists the available modules in the repository + belong under the "checkout" function? Perhaps it is more logically + grouped with the "history" function or we should create a new "info" + function? diff --git a/gnu/usr.bin/cvs/NEWS b/gnu/usr.bin/cvs/NEWS new file mode 100644 index 0000000..8965819 --- /dev/null +++ b/gnu/usr.bin/cvs/NEWS @@ -0,0 +1,863 @@ +Changes since 1.6: + +* RCS keyword "Name" supported for "cvs update -r <tag>" and "cvs +checkout -r <tag>". + +* If there is a group whose name matches a compiled in value which +defaults to "cvsadmin", only members of that group can use "cvs +admin". + +* CVS now sets the modes of files in the repository based on the +CVSUMASK environment variable or a compiled in value defaulting to +002. This way other developers will be able to access the files in +the repository regardless of the umask of the developer creating them. + +* The command name .cvsrc now matches the official name of the +command, not the one (possibly an alias) by which it was invoked. If +you had previously relied on "cvs di" and "cvs diff" using different +options, instead use a shell function or alias (for example "alias +cvsdi='cvs diff -u'"). + +Changes from 1.5 to 1.6: + +* Del updated the man page to include all of the new features +of CVS 1.6. + +* "cvs tag" now supports a "-r | -D" option for tagging an already +tagged revision / specific revision of a file. + +* There is a "taginfo" file in CVSROOT that supports filtering and +recording of tag operations. + +* Long options support added, including --help and --version options. + +* "cvs release" no longer cares whether or not the directory being +released has an entry in the `modules' file. + +* The modules file now takes a -e option which is used instead of -o +for "cvs export". If your modules file has a -o option which you want +to be used for "cvs export", change it to specify -e as well as -o. + +* "cvs export" now takes a -k option to set RCS keyword expansion. +This way you can export binary files. If you want the old behavior, +you need to specify -kv. + +* "cvs update", "cvs rdiff", "cvs checkout", "cvs import", "cvs +release", "cvs rtag", and "cvs tag" used to take -q and -Q options +after the command name (e.g. "cvs update -q"). This was confusing +because other commands, such as "cvs ci", did not. So the options +after the command name have been removed and you must now specify, for +example, "cvs -q update", which has been supported since CVS 1.3. + +* New "wrappers" feature. This allows you to set a hook which +transforms files on their way in and out of cvs (apparently on the +NeXT there is some particular usefulness in tarring things up in the +repository). It also allows you to declare files as merge-by-copy +which means that instead of trying to merge the file, CVS will merely +copy the new version. There is a CVSROOT/cvswrappers file and an +optionsl ~/.cvswrappers file to support this feature. + +* You can set CVSROOT to user@host:dir, not just host:dir, if your +username on the server host is different than on the client host. + +* VISUAL is accepted as well as EDITOR. + +* $CVSROOT is expanded in *info files. + +Changes from 1.4A2 to 1.5: + +* Remote implementation. This is very helpful when collaborating on a +project with someone across a wide-area network. This release can +also be used locally, like other CVS versions, if you have no need for +remote access. + +Here are some of the features of the remote implementation: +- It uses reliable transport protocols (TCP/IP) for remote repository + access, not NFS. NFS is unusable over long distances (and sometimes + over short distances) +- It transfers only those files that have changed in the repository or + the working directory. To save transmission time, it will transfer + patches when appropriate, and can compress data for transmission. +- The server never holds CVS locks while waiting for a reply from the client; + this makes the system robust when used over flaky networks. + +The remote features are documented in doc/cvsclient.texi in the CVS +distribution, but the main doc file, cvs.texinfo, has not yet been +updated to include the remote features. + +* Death support. See src/README-rm-add for more information on this. + +* Many speedups, especially from jtc@cygnus.com. + +* CVS 1.2 compatibility code has been removed as a speedup. If you +have working directories checked out by CVS 1.2, CVS 1.3 or 1.4A2 will +try to convert them, but CVS 1.5 and later will not (if the working +directory is up to date and contains no extraneous files, you can just +remove it, and then check out a new working directory). Likewise if +your repository contains a CVSROOT.adm directory instead of a CVSROOT +directory, you need to rename it. + +Fri Oct 21 20:58:54 1994 Brian Berliner <berliner@sun.com> + + * Changes between CVS 1.3 and CVS 1.4 Alpha-2 + + * A new program, "cvsbug", is provided to let you send bug reports + directly to the CVS maintainers. Please use it instead of sending + mail to the info-cvs mailing list. If your build fails, you may + have to invoke "cvsbug" directly from the "src" directory as + "src/cvsbug.sh". + + * A new User's Guide and Tutorial, written by Per Cederqvist + <ceder@signum.se> of Signum Support. See the "doc" directory. A + PostScript version is included as "doc/cvs.ps". + + * The Frequesntly Asked Questions file, FAQ, has been added to the + release. Unfortunately, its contents are likely out-of-date. + + * The "cvsinit" shell script is now installed in the $prefix/bin + directory like the other programs. You can now create new + CVS repositories with great ease. + + * Index: lines are now printed on output from 'diff' and 'rdiff', + in order to facilitate application of patches to multiple subdirs. + + * Support for a ~/.cvsrc file, which allows you to specify options + that are always supposed to be given to a specific command. This + feature shows the non-orthogonality of the option set, since while + there may be an option to turn something on, the option to turn + that same thing off may not exist. + + * You can now list subdirectories that you wish to ignore in a + modules listing, such as: + + gcc -a gnu/gcc, !gnu/gcc/testsuites + + which will check out everything underneath gnu/gcc, except + everything underneath gnu/gcc/testsuites. + + * It is now much harder to accidentally overwrite an existing tag + name, since attempting to move a tag name will result in a error, + unless the -F (force) flag is given to the tag subcommands. + + * Better error checking on matching of the repository used to + check code out from against the repository the current cvs + commnands would use. (Thanks to Mark Baushke <mdb@cisco.com>) + + * Better support for sites with multiple CVSROOT repositories has + been contributed. The file "CVS/Root" in your working directory + is created to hold the full path to the CVS repository and a + simple check is made against your current CVSROOT setting. + + * You can now specify an RCS keyword substitution value when you + import files into the repository. + + * Uses a much newer version of Autoconf, and conforms to the GNU + coding standards much more closely. No, it still doesn't have + long option names. + + * Code cleanup. Many passes through gcc -Wall helped to identify + a number of questionable constructs. Most arbitrary length limits + were removed. + + * Profiling to determine bottlenecks helped to identify the best + places to spend time speeding up the code, which was then done. A + number of performance enhancements in filename matching have sped + up checkouts. + + * Many more contributions have been added to the "contrib" + directory. See the README file in that directory for more + information. + + * "cvs commit" will try harder to not change the file's + modification time after the commit. If the file does not change + as a result of the commit operation, CVS will preserve the + original modification time, thus speeding up future make-type + builds. + + * "cvs commit" now includes any removed files in the (optional) + pre-commit checking program that may be invoked. Previously, only + added and modified files were included. + + * It is now possible to commit a file directly onto the trunk at a + specific revision level by doing "cvs commit -r3.0 file.c", where + "3.0" specifies the revision you wish to create. The file must be + up-to-date with the current head of the trunk for this to succeed. + + * "cvs commit" will now function with a pre-commit program that + has arguments specified in the "commitinfo" file. + + * The "mkmodules" program will now look within the + $CVSROOT/CVSROOT/checkoutlist" file for any additional files that + should be automatically checked out within CVSROOT; mkmodules also + tries harder to preserve any execute bits the files may have + originally had. + + * "cvs diff" is much more accurate about its exit status now. It + now returns the maximum exit status of any invoked diff. + + * The "-I !" option is now supported for the import and update + commands correctly. It will properly clear the ignore list now. + + * Some problems with "cvs import" handling of .cvsignore have been + fixed; as well, some rampant recursion problems with import have + also been fixed. + + * "cvs rdiff" (aka "cvs patch") now tries to set the modify time + of any temporary files it uses to match those specified for the + particular revision. This allows a more accurate patch image to + be created. + + * "cvs status" has improved revision descriptions. "Working + revision" is used for the revision of the working file that you + edit directly; "Repository revision" is the revision of the file + with the $CVSROOT source repository. Also, the output is clearer + with regard to sticky and branch revisions. + + * CVS no longer dumps core when given a mixture of directories and + files in sub-directories (as in "cvs ci file1 dir1/file2"). + Instead, arguments are now clumped into their respective directory + and operated on in chunks, together. + + * If the CVSEDITOR environment variable is set, that editor is + used for log messages instead of the EDITOR environment variable. + This makes it easy to substitute intelligent programs to make more + elaborate log messages. Contributed by Mark D Baushke + (mdb@cisco.com). + + * Command argument changes: + cvs: The "-f" option has been added to ignore + the ~/.cvsrc file. + commit: Renamed the "-f logfile" option to the + "-F logfile" option. Added the "-f" + option to force a commit of the specified + files (this disables recursion). + history: Added "-t timezone" option to force any + date-specific output into the specified + timezone. + import: Added "-d" option to use the file's + modification time as the time of the + import. Added "-k sub" option to set the + default RCS keyword substitution mode for + newly-created files. + remove: Added "-f" option to force the file's + automatic removal if it still exists in + the working directory (use with caution). + rtag: Added "-F" option to move the tag if it + already exists -- new default is to NOT + move tags automatically. + tag: Added "-F" option to move the tag if it + already exists -- new default is to NOT + move tags automatically. + +Tue Apr 7 15:55:25 1992 Brian Berliner (berliner at sun.com) + + * Changes between CVS 1.3 Beta-3 and official CVS 1.3! + + * A new shell script is provided, "./cvsinit", which can be run at + install time to help setup your $CVSROOT area. This can greatly + ease your entry into CVS usage. + + * The INSTALL file has been updated to include the machines on + which CVS has compiled successfully. I think CVS 1.3 is finally + portable. Thanks to all the Beta testers! + + * Support for the "editinfo" file was contributed. This file + (located in $CVSROOT/CVSROOT) can be used to specify a special + "editor" to run on a per-directory basis within the repository, + instead of the usual user's editor. As such, it can verify that + the log message entered by the user is of the appropriate form + (contains a bugid and test validation, for example). + + * The manual pages cvs(1) and cvs(5) have been updated. + + * The "mkmodules" command now informs you when your modules file + has duplicate entries. + + * The "add" command now preserves any per-directory sticky tag when + you add a new directory to your checked-out sources. + + * The "admin" command is now a fully recursive interface to the + "rcs" program which operates on your checked-out sources. It no + longer requires you to specify the full path to the RCS file. + + * The per-file sticky tags can now be effectively removed with + "cvs update -A file", even if you had checked out the whole + directory with a per-directory sticky tag. This allows a great + deal of flexibility in managing the revisions that your checked-out + sources are based upon (both per-directory and per-file sticky + tags). + + * The "cvs -n commit" command now works, to show which files are + out-of-date and will cause the real commit to fail, or which files + will fail any pre-commit checks. Also, the "cvs -n import ..." + command will now show you what it would've done without actually + doing it. + + * Doing "cvs commit modules" to checkin the modules file will no + properly run the "mkmodules" program (assuming you have setup your + $CVSROOT/CVSROOT/modules file to do so). + + * The -t option in the modules file (which specifies a program to + run when you do a "cvs rtag" operation on a module) now gets the + symbolic tag as the second argument when invoked. + + * When the source repository is locked by another user, that user's + login name will be displayed as the holder of the lock. + + * Doing "cvs checkout module/file.c" now works even if + module/file.c is in the Attic (has been removed from main-line + development). + + * Doing "cvs commit */Makefile" now works as one would expect. + Rather than trying to commit everything recursively, it will now + commit just the files specified. + + * The "cvs remove" command is now fully recursive. To schedule a + file for removal, all you have to do is "rm file" and "cvs rm". + With no arguments, "cvs rm" will schedule all files that have been + physically removed for removal from the source repository at the + next "cvs commit". + + * The "cvs tag" command now prints "T file" for each file that was + tagged by this invocation and "D file" for each file that had the + tag removed (as with "cvs tag -d"). + + * The -a option has been added to "cvs rtag" to force it to clean + up any old, matching tags for files that have been removed (in the + Attic) that may not have been touched by this tag operation. This + can help keep a consistent view with your tag, even if you re-use + it frequently. + +Sat Feb 29 16:02:05 1992 Brian Berliner (berliner at sun.com) + + * Changes between CVS 1.3 Beta-2 and CVS 1.3 Beta-3 + + * Many portability fixes, thanks to all the Beta testers! With any + luck, this Beta release will compile correctly on most anything. + Hey, what are we without our dreams. + + * CVS finally has support for doing isolated development on a + branch off the current (or previous!) revisions. This is also + extremely nice for generating patches for previously released + software while development is progressing on the next release. + Here's an example of creating a branch to fix a patch with the 2.0 + version of the "foo" module, even though we are already well into + the 3.0 release. Do: + + % cvs rtag -b -rFOO_2_0 FOO_2_0_Patch foo + % cvs checkout -rFOO_2_0_Patch foo + % cd foo + [[ hack away ]] + % cvs commit + + A physical branch will be created in the RCS file only when you + actually commit the change. As such, forking development at some + random point in time is extremely light-weight -- requiring just a + symbolic tag in each file until a commit is done. To fork + development at the currently checked out sources, do: + + % cvs tag -b Personal_Hack + % cvs update -rPersonal_Hack + [[ hack away ]] + % cvs commit + + Now, if you decide you want the changes made in the Personal_Hack + branch to be merged in with other changes made in the main-line + development, you could do: + + % cvs commit # to make Personal_Hack complete + % cvs update -A # to update sources to main-line + % cvs update -jPersonal_Hack # to merge Personal_Hack + + to update your checked-out sources, or: + + % cvs checkout -jPersonal_Hack module + + to checkout a fresh copy. + + To support this notion of forked development, CVS reserves + all even-numbered branches for its own use. In addition, CVS + reserves the ".0" and ".1" branches. So, if you intend to do your + own branches by hand with RCS, you should use odd-numbered branches + starting with ".3", as in "1.1.3", "1.1.5", 1.2.9", .... + + * The "cvs commit" command now supports a fully functional -r + option, allowing you to commit your changes to a specific numeric + revision or symbolic tag with full consistency checks. Numeric + tags are useful for bringing your sources all up to some revision + level: + + % cvs commit -r2.0 + + For symbolic tags, you can only commit to a tag that references a + branch in the RCS file. One created by "cvs rtag -b" or from + "cvs tag -b" is appropriate (see below). + + * Roland Pesch <pesch@cygnus.com> and K. Richard Pixley + <rich@cygnus.com> were kind enough to contribute two new manual + pages for CVS: cvs(1) and cvs(5). Most of the new CVS 1.3 features + are now documented, with the exception of the new branch support + added to commit/rtag/tag/checkout/update. + + * The -j options of checkout/update have been added. The "cvs join" + command has been removed. + + With one -j option, CVS will merge the changes made between the + resulting revision and the revision that it is based on (e.g., if + the tag refers to a branch, CVS will merge all changes made in + that branch into your working file). + + With two -j options, CVS will merge in the changes between the two + respective revisions. This can be used to "remove" a certain delta + from your working file. E.g., If the file foo.c is based on + revision 1.6 and I want to remove the changes made between 1.3 and + 1.5, I might do: + + % cvs update -j1.5 -j1.3 foo.c # note the order... + + In addition, each -j option can contain on optional date + specification which, when used with branches, can limit the chosen + revision to one within a specific date. An optional date is + specified by adding a colon (:) to the tag, as in: + + -jSymbolic_Tag:Date_Specifier + + An example might be what "cvs import" tells you to do when you have + just imported sources that have conflicts with local changes: + + % cvs checkout -jTAG:yesterday -jTAG module + + which tells CVS to merge in the changes made to the branch + specified by TAG in the last 24 hours. If this is not what is + intended, substitute "yesterday" for whatever format of date that + is appropriate, like: + + % cvs checkout -jTAG:'1 week ago' -jTAG module + + * "cvs diff" now supports the special tags "BASE" and "HEAD". So, + the command: + + % cvs diff -u -rBASE -rHEAD + + will effectively show the changes made by others (in unidiff + format) that will be merged into your working sources with your + next "cvs update" command. "-rBASE" resolves to the revision that + your working file is based on. "-rHEAD" resolves to the current + head of the branch or trunk that you are working on. + + * The -P option of "cvs checkout" now means to Prune empty + directories, as with "update". The default is to not remove empty + directories. However, if you do "checkout" with any -r options, -P + will be implied. I.e., checking out with a tag will cause empty + directories to be pruned automatically. + + * The new file INSTALL describes how to install CVS, including + detailed descriptions of interfaces to "configure". + + * The example loginfo file in examples/loginfo has been updated to + use the perl script included in contrib/log.pl. The nice thing + about this log program is that it records the revision numbers of + your change in the log message. + + Example files for commitinfo and rcsinfo are now included in the + examples directory. + + * All "#if defined(__STDC__) && __STDC__ == 1" lines have been + changed to be "#if __STDC__" to fix some problems with the former. + + * The lib/regex.[ch] files have been updated to the 1.3 release of + the GNU regex package. + + * The ndbm emulation routines included with CVS 1.3 Beta-2 in the + src/ndbm.[ch] files has been moved into the src/myndbm.[ch] files + to avoid any conflict with the system <ndbm.h> header file. If + you had a previous CVS 1.3 Beta release, you will want to "cvs + remove ndbm.[ch]" form your copy of CVS as well. + + * "cvs add" and "cvs remove" are a bit more verbose, telling you + what to do to add/remove your file permanently. + + * We no longer mess with /dev/tty in "commit" and "add". + + * More things are quiet with the -Q option set. + + * New src/config.h option: If CVS_BADROOT is set, CVS will not + allow people really logged in as "root" to commit changes. + + * "cvs diff" exits with a status of 0 if there were no diffs, 1 if + there were diffs, and 2 if there were errors. + + * "cvs -n diff" is now supported so that you can still run diffs + even while in the middle of committing files. + + * Handling of the CVS/Entries file is now much more robust. + + * The default file ignore list now includes "*.so". + + * "cvs import" did not expand '@' in the log message correctly. It + does now. Also, import now uses the ignore file facility + correctly. + + Import will now tell you whether there were conflicts that need to + be resolved, and how to resolve them. + + * "cvs log" has been changed so that you can "log" things that are + not a part of the current release (in the Attic). + + * If you don't change the editor message on commit, CVS now prompts + you with the choice: + + !)reuse this message unchanged for remaining dirs + + which allows you to tell CVS that you have no intention of changing + the log message for the remainder of the commit. + + * It is no longer necessary to have CVSROOT set if you are using + the -H option to get Usage information on the commands. + + * Command argument changes: + checkout: -P handling changed as described above. + New -j option (up to 2 can be specified) + for doing rcsmerge kind of things on + checkout. + commit: -r option now supports committing to a + numeric or symbolic tags, with some + restrictions. Full consistency checks will + be done. + Added "-f logfile" option, which tells + commit to glean the log message from the + specified file, rather than invoking the + editor. + rtag: Added -b option to create a branch tag, + useful for creating a patch for a previous + release, or for forking development. + tag: Added -b option to create a branch tag, + useful for creating a patch for a previous + release, or for forking development. + update: New -j option (up to 2 can be specified) + for doing rcsmerge kind of things on + update. + +Thu Jan 9 10:51:35 MST 1992 Jeff Polk (polk at BSDI.COM) + + * Changes between CVS 1.3 Beta-1 and CVS 1.3 Beta-2 + + * Thanks to K. Richard Pixley at Cygnus we now have function + prototypes in all the files + + * Some small changes to configure for portability. There have + been other portability problems submitted that have not been fixed + (Brian will be working on those). Additionally all __STDC__ + tests have been modified to check __STDC__ against the constant 1 + (this is what the Second edition of K&R says must be true). + + * Lots of additional error checking for forked processes (run_exec) + (thanks again to K. Richard Pixley) + + * Lots of miscellaneous bug fixes - including but certainly not + limited to: + various commit core dumps + various update core dumps + bogus results from status with numeric sticky tags + commitprog used freed memory + Entries file corruption caused by No_Difference + commit to revision broken (now works if branch exists) + ignore file processing broken for * and ! + ignore processing didn't handle memory reasonably + miscellaneous bugs in the recursion processor + file descriptor leak in ParseInfo + CVSROOT.adm->CVSROOT rename bug + lots of lint fixes + + * Reformatted all the code in src (with GNU indent) and then + went back and fixed prototypes, etc since indent gets confused. The + rationale is that it is better to do it sooner than later and now + everything is consistent and will hopefully stay that way. + The basic options to indent were: "-bad -bbb -bap -cdb -d0 -bl -bli0 + -nce -pcs -cs -cli4 -di1 -nbc -psl -lp -i4 -ip4 -c41" and then + miscellaneous formatting fixes were applied. Note also that the + "-nfc1" or "-nfca" may be appropriate in files where comments have + been carefully formatted (e.g, modules.c). + +Sat Dec 14 20:35:22 1991 Brian Berliner (berliner at sun.com) + + * Changes between CVS 1.2 and CVS 1.3 Beta are described here. + + * Lots of portability work. CVS now uses the GNU "configure" + script to dynamically determine the features provided by your + system. It probably is not foolproof, but it is better than + nothing. Please let me know of any portability problems. Some + file names were changed to fit within 14-characters. + + * CVS has a new RCS parser that is much more flexible and + extensible. It should read all known RCS ",v" format files. + + * Most of the commands now are fully recursive, rather than just + operating on the current directory alone. This includes "commit", + which makes it real easy to do an "atomic" commit of all the + changes made to a CVS hierarchy of sources. Most of the commands + also correctly handle file names that are in directories other than + ".", including absolute path names. Commands now accept the "-R" + option to force recursion on (though it is always the default now) + and the "-l" option to force recursion off, doing just "." and not + any sub-directories. + + * CVS supports many of the features provided with the RCS 5.x + distribution - including the new "-k" keyword expansion options. I + recommend using RCS 5.x (5.6 is the current official RCS version) + and GNU diff 1.15 (or later) distributions with CVS. + + * Checking out files with symbolic tags/dates is now "sticky", in + that CVS remembers the tag/date used for each file (and directory) + and will use that tag/date automatically on the next "update" call. + This stickyness also holds for files checked out with the the new + RCS 5.x "-k" options. + + * The "cvs diff" command now recognizes all of the rcsdiff 5.x + options. Unidiff format is available by installing the GNU + diff 1.15 distribution. + + * The old "CVS.adm" directories created on checkout are now called + "CVS" directories, to look more like "RCS" and "SCCS". Old CVS.adm + directories are automagically converted to CVS directories. The + old "CVSROOT.adm" directory within the source repository is + automagically changed into a "CVSROOT" directory as well. + + * Symbolic links in the source repository are fully supported ONLY + if you use RCS 5.6 or later and (of course) your system supports + symlinks. + + * A history database has been contributed which maintains the + history of certain CVS operations, as well as providing a wide array + of querying options. + + * The "cvs" program has a "-n" option which can be used with the + "update" command to show what would be updated without actually + doing the update, like: "cvs -n update". All usage statements + have been cleaned up and made more verbose. + + * The module database parsing has been rewritten. The new format + is compatible with the old format, but with much more + functionality. It allows modules to be created that grab pieces or + whole directories from various different parts of your source + repository. Module-relative specifications are also correctly + recognized now, like "cvs checkout module/file.c". + + * A configurable template can be specified such that on a "commit", + certain directories can supply a template that the user must fill + before completing the commit operation. + + * A configurable pre-commit checking program can be specified which + will run to verify that a "commit" can happen. This feature can be + used to restrict certain users from changing certain pieces of the + source repository, or denying commits to the entire source + repository. + + * The new "cvs export" command is much like "checkout", but + establishes defaults suitable for exporting code to others (expands + out keywords, forces the use of a symbolic tag, and does not create + "CVS" directories within the checked out sources. + + * The new "cvs import" command replaces the deprecated "checkin" + shell script and is used to import sources into CVS control. It is + also much faster for the first-time import. Some algorithmic + improvements have also been made to reduce the number of + conflicting files on next-time imports. + + * The new "cvs admin" command is basically an interface to the + "rcs" program. (Not yet implemented very well). + + * Signal handling (on systems with BSD or POSIX signals) is much + improved. Interrupting CVS now works with a single interrupt! + + * CVS now invokes RCS commands by direct fork/exec rather than + calling system(3). This improves performance by removing a call to + the shell to parse the arguments. + + * Support for the .cvsignore file has been contributed. CVS will + now show "unknown" files as "? filename" as the result of an "update" + command. The .cvsignore file can be used to add files to the + current list of ignored files so that they won't show up as unknown. + + * Command argument changes: + cvs: Added -l to turn off history logging. + Added -n to show what would be done without actually + doing anything. + Added -q/-Q for quiet and really quiet settings. + Added -t to show debugging trace. + add: Added -k to allow RCS 5.x -k options to be specified. + admin: New command; an interface to rcs(1). + checkout: Added -A to reset sticky tags/date/options. + Added -N to not shorten module paths. + Added -R option to force recursion. + Changed -p (prune empty directories) to -P option. + Changed -f option; forcing tags match is now default. + Added -p option to checkout module to standard output. + Added -s option to cat the modules db with status. + Added -d option to checkout in the specified directory. + Added -k option to use RCS 5.x -k support. + commit: Removed -a option; use -l instead. + Removed -f option. + Added -l option to disable recursion. + Added -R option to force recursion. + If no files specified, commit is recursive. + diff: Now recognizes all RCS 5.x rcsdiff options. + Added -l option to disable recursion. + Added -R option to force recursion. + history: New command; displays info about CVS usage. + import: Replaces "checkin" shell script; imports sources + under CVS control. Ignores files on the ignore + list (see -I option or .cvsignore description above). + export: New command; like "checkout", but w/special options + turned on by default to facilitate exporting sources. + join: Added -B option to join from base of the branch; + join now defaults to only joining with the top two + revisions on the branch. + Added -k option for RCS 5.x -k support. + log: Supports all RCS 5.x options. + Added -l option to disable recursion. + Added -R option to force recursion. + patch: Changed -f option; forcing tags match is now default. + Added -c option to force context-style diffs. + Added -u option to support unidiff-style diffs. + Added -V option to support RCS specific-version + keyword expansion formats. + Added -R option to force recursion. + remove: No option changes. It's a bit more verbose. + rtag: Equivalent to the old "cvs tag" command. + No option changes. It's a lot faster for re-tag. + status: New output formats with more information. + Added -l option to disable recursion. + Added -R option to force recursion. + Added -v option to show symbolic tags for files. + tag: Functionality changed to tag checked out files + rather than modules; use "rtag" command to get the + old "cvs tag" behaviour. + update: Added -A to reset sticky tags/date/options. + Changed -p (prune empty directories) to -P option. + Changed -f option; forcing tags match is now default. + Added -p option to checkout module to standard output. + Added -I option to add files to the ignore list. + Added -R option to force recursion. + + Major Contributors: + + * Jeff Polk <polk@bsdi.com> rewrote most of the grody code of CVS + 1.2. He made just about everything dynamic (by using malloc), + added a generic hashed list manager, re-wrote the modules database + parsing in a compatible - but extended way, generalized directory + hierarchy recursion for virtually all the commands (including + commit!), generalized the loginfo file to be used for pre-commit + checks and commit templates, wrote a new and flexible RCS parser, + fixed an uncountable number of bugs, and helped in the design of + future CVS features. If there's anything gross left in CVS, it's + probably my fault! + + * David G. Grubbs <dgg@odi.com> contributed the CVS "history" and + "release" commands. As well as the ever-so-useful "-n" option of + CVS which tells CVS to show what it would do, without actually + doing it. He also contributed support for the .cvsignore file. + + * Paul Sander, HaL Computer Systems, Inc. <paul@hal.com> wrote and + contributed the code in lib/sighandle.c. I added support for + POSIX, BSD, and non-POSIX/non-BSD systems. + + * Free Software Foundation contributed the "configure" script and + other compatibility support in the "lib" directory, which will help + make CVS much more portable. + + * Many others have contributed bug reports and enhancement requests. + Some have even submitted actual code which I have not had time yet + to integrate into CVS. Maybe for the next release. + + * Thanks to you all! + +Wed Feb 6 10:10:58 1991 Brian Berliner (berliner at sun.com) + + * Changes from CVS 1.0 Patchlevel 1 to CVS 1.0 Patchlevel 2; also + known as "Changes from CVS 1.1 to CVS 1.2". + + * Major new support with this release is the ability to use the + recently-posted RCS 5.5 distribution with CVS 1.2. See below for + other assorted bug-fixes that have been thrown in. + + * ChangeLog (new): Added Emacs-style change-log file to CVS 1.2 + release. Chronological description of changes between release. + + * README: Small fixes to installation instructions. My email + address is now "berliner@sun.com". + + * src/Makefile: Removed "rcstime.h". Removed "depend" rule. + + * src/partime.c: Updated to RCS 5.5 version with hooks for CVS. + * src/maketime.c: Updated to RCS 5.5 version with hooks for CVS. + * src/rcstime.h: Removed from the CVS 1.2 distribution. + Thanks to Paul Eggert <eggert@twinsun.com> for these changes. + + * src/checkin.csh: Support for RCS 5.5 parsing. + Thanks to Paul Eggert <eggert@twinsun.com> for this change. + + * src/collect_sets.c (Collect_Sets): Be quieter if "-f" option is + specified. When checking out files on-top-of other files that CVS + doesn't know about, run a diff in the hopes that they are really + the same file before aborting. + + * src/commit.c (branch_number): Fix for RCS 5.5 parsing. + Thanks to Paul Eggert <eggert@twinsun.com> for this change. + + * src/commit.c (do_editor): Bug fix - fprintf missing argument + which sometimes caused core dumps. + + * src/modules.c (process_module): Properly NULL-terminate + update_dir[] in all cases. + + * src/no_difference.c (No_Difference): The wrong RCS revision was + being registered in certain (strange) cases. + + * src/patch.c (get_rcsdate): New algorithm. No need to call + maketime() any longer. + Thanks to Paul Eggert <eggert@twinsun.com> for this change. + + * src/patchlevel.h: Increased patch level to "2". + + * src/subr.c (isdir, islink): Changed to compare stat mode bits + correctly. + + * src/tag.c (tag_file): Added support for following symbolic links + that are in the master source repository when tagging. Made tag + somewhat quieter in certain cases. + + * src/update.c (update_process_lists): Unlink the user's file if it + was put on the Wlist, meaning that the user's file is not modified + and its RCS file has been removed by someone else. + + * src/update.c (update): Support for "cvs update dir" to correctly + just update the argument directory "dir". + + * src/cvs.h: Fixes for RCS 5.5 parsing. + * src/version_number.c (Version_Number): Fixes for parsing RCS 5.5 + and older RCS-format files. + Thanks to Paul Eggert <eggert@twinsun.com> for these changes. + + * src/version_number.c (Version_Number): Bug fixes for "-f" option. + Bug fixes for parsing with certain branch numbers. RCS + revision/symbol parsing is much more solid now. + +Wed Feb 14 10:01:33 1990 Brian Berliner (berliner at sun.com) + + * Changes from CVS 1.0 Patchlevel 0 to CVS 1.0 Patchlevel 1; also + known as "Changes from CVS 1.0 to CVS 1.1". + + * src/patch.c (get_rcsdate): Portability fix. Replaced call to + timelocal() with call to maketime(). + +Mon Nov 19 23:15:11 1990 Brian Berliner (berliner at prisma.com) + + * Sent CVS 1.0 release to comp.sources.unix moderator and FSF. + + * Special thanks to Dick Grune <dick@cs.vu.nl> for his work on the + 1986 version of CVS and making it available to the world. Dick's + version is available on uunet.uu.net in the + comp.sources.unix/volume6/cvs directory. + +$CVSid: @(#)ChangeLog 1.35 94/10/22 $ diff --git a/gnu/usr.bin/cvs/PROJECTS b/gnu/usr.bin/cvs/PROJECTS new file mode 100644 index 0000000..de76576 --- /dev/null +++ b/gnu/usr.bin/cvs/PROJECTS @@ -0,0 +1,59 @@ +This is a list of projects for CVS. In general, unlike the things in +the TODO file, these need more analysis to determine if and how +worthwhile each task is. + +I haven't gone through TODO, but it's likely that it has entries that +are actually more appropriate for this list. + +0. Improved Efficency + +* CVS uses a single doubly linked list/hash table data structure for + all of its lists. Since the back links are only used for deleting + list nodes it might be beneficial to use singly linked lists or a + tree structure. Most likely, a single list implementation will not + be appropriate for all uses. + + One easy change would be to remove the "type" field out of the list + and node structures. I have found it to be of very little use when + debugging, and each instance eats up a word of memory. This can add + up and be a problem on memory-starved machines. + + Profiles have shown that on fast machines like the Alpha, fsortcmp() + is one of the hot spots. + +* Dynamically allocated character strings are created, copied, and + destroyed throughout CVS. The overhead of malloc()/strcpy()/free() + needs to be measured. If significant, it could be minimized by using a + reference counted string "class". + +* File modification time is stored as a character string. It might be + worthwile to use a time_t internally if the time to convert a time_t + (from struct stat) to a string is greater that the time to convert a + ctime style string (from the entries file) to a time_t. time_t is + an machine-dependant type (although it's pretty standard on UN*X + systems), so we would have to have different conversion routines. + Profiles show that both operations are called about the same number + of times. + +* stat() is one of the largest performance bottlenecks on systems + without the 4.4BSD filesystem. By spliting information out of + the filesystem (perhaps the "rename database") we should be + able to improve performance. + +* Parsing RCS files is very expensive. This might be unnecessary if + RCS files are only used as containers for revisions, and tag, + revision, and date information was available in easy to read + (and modify) indexes. This becomes very apparent with files + with several hundred revisions. + +* A RCS "library", so CVS could operate on RCS files directly. + + CVS parses RCS files in order to determine if work needs to be done, + and then RCS parses the files again when it is performing the work. + This would be much faster if CVS could do whatever is necessary + by itself. + +1. Improved testsuite/sanity check script + +* Need to use a code coverage tool to determine how much the sanity + script tests, and fill in the holes. diff --git a/gnu/usr.bin/cvs/contrib/ccvs-rsh.pl b/gnu/usr.bin/cvs/contrib/ccvs-rsh.pl new file mode 100644 index 0000000..8cfc674 --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/ccvs-rsh.pl @@ -0,0 +1,97 @@ +#!/usr/bin/perl + +# The version of the remote shell program on some Linuxes, at least, +# misuses GNU getopt in such a way that it plucks arguments to rsh +# that look like command-line switches from anywhere in rsh's +# arguments. This is the Wrong Thing to do, and causes older versions +# of CCVS to break. + +# In addition, if we live behind a firewall and have to construct a +# "pipeline" of rshes through different machines in order to get to +# the outside world, each rshd along the way undoes the hard work CCVS +# does to put the command to be executed at the far end into a single +# argument. Sigh. + +# This script is a very minimal wrapper to rsh which makes sure that +# the commands to be executed remotely are packed into a single +# argument before we call exec(). It works on the idea of a "proxy +# chain", which is a set of machines you go through to get to the CCVS +# server machine. + +# Each host you go through before you reach the CCVS server machine +# should have a copy of this script somewhere (preferably accessible +# directly from your PATH envariable). In addition, each host you go +# through before you reach the firewall should have the CVS_PROXY_HOST +# envariable set to the next machine in the chain, and CVS_PROXY_USER +# set if necessary. + +# This really isn't as complex as it sounds. Honest. + +# Bryan O'Sullivan <bos@serpentine.com> April 1995 + +$usage = "usage: ccvs-rsh hostname [-l username] command [...]\n"; + +if ($#ARGV < 1) { + print STDERR $usage; + exit 1; +} + +# Try to pick a sane version of the remote shell command to run. This +# only understands BSD and Linux machines; if your remote shell is +# called "remsh" under some System V (e.g. HP-SUX), you should edit +# the line manually to suit yourself. + +$rsh = (-x "/usr/ucb/rsh") ? "/usr/ucb/rsh" : "/usr/bin/rsh"; + +# If you are not rshing directly to the CCVS server machine, make the +# following variable point at ccvs-rsh on the next machine in the +# proxy chain. If it's accessible through the PATH envariable, you +# can just set this to "ccvs-rsh". + +$ccvs_rsh = "ccvs-rsh"; + +# There shouldn't be any user-serviceable parts beyond this point. + +$host = $ARGV[0]; + +if ($ARGV[1] eq "-l") { + if ($#ARGV < 3) { + print STDERR $usage; + exit 1; + } + $user = $ARGV[2]; + $cbase = 3; +} else { + $cbase = 1; +} + +# You might think you shoul be able to do something like +# $command = join(' ', $ARGV[$cbase..$#ARGV]); +# to achieve the effect of the following block of code, but it doesn't +# work under Perl 4 on Linux, at least. Sigh. + +$command = $ARGV[$cbase]; +for ($cbase++; $cbase <= $#ARGV; $cbase++) { + $command .= " " . $ARGV[$cbase]; +} + +if (defined $ENV{"CVS_PROXY_HOST"}) { + $command = (defined $user) + ? "$ccvs_rsh $host -l $user $command" + : "$ccvs_rsh $host $command"; + + if (defined $ENV{"CVS_PROXY_USER"}) { + exec ($rsh, $ENV{"CVS_PROXY_HOST"}, "-l", $ENV{"CVS_PROXY_USER"}, + $command); + } else { + exec ($rsh, $ENV{"CVS_PROXY_HOST"}, $command); + } +} elsif (defined $user) { + exec ($rsh, $host, "-l", $user, $command); +} else { + if (defined $ENV{"CVS_PROXY_USER"}) { + exec ($rsh, $host, "-l", $ENV{"CVS_PROXY_USER"}, $command); + } else { + exec ($rsh, $host, $command); + } +} diff --git a/gnu/usr.bin/cvs/contrib/clmerge.pl b/gnu/usr.bin/cvs/contrib/clmerge.pl new file mode 100644 index 0000000..ac81371 --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/clmerge.pl @@ -0,0 +1,152 @@ +#! xPERL_PATHx + +# Merge conflicted ChangeLogs +# tromey Mon Aug 15 1994 + +# Usage is: +# +# cl-merge [-i] file ... +# +# With -i, it works in place (backups put in a ~ file). Otherwise the +# merged ChangeLog is printed to stdout. + +# Please report any bugs to me. I wrote this yesterday, so there are no +# guarantees about its performance. I recommend checking its output +# carefully. If you do send a bug report, please include the failing +# ChangeLog, so I can include it in my test suite. +# +# Tom +# --- +# tromey@busco.lanl.gov Member, League for Programming Freedom +# Sadism and farce are always inexplicably linked. +# -- Alexander Theroux + + +# Month->number mapping. Used for sorting. +%months = ('Jan', 0, + 'Feb', 1, + 'Mar', 2, + 'Apr', 3, + 'May', 4, + 'Jun', 5, + 'Jul', 6, + 'Aug', 7, + 'Sep', 8, + 'Oct', 9, + 'Nov', 10, + 'Dec', 11); + +# If '-i' is given, do it in-place. +if ($ARGV[0] eq '-i') { + shift (@ARGV); + $^I = '~'; +} + +$lastkey = ''; +$lastval = ''; +$conf = 0; +%conflist = (); + +$tjd = 0; + +# Simple state machine. The states: +# +# 0 Not in conflict. Just copy input to output. +# 1 Beginning an entry. Next non-blank line is key. +# 2 In entry. Entry beginner transitions to state 1. +while (<>) { + if (/^<<<</ || /^====/) { + # Start of a conflict. + + # Copy last key into array. + if ($lastkey ne '') { + $conflist{$lastkey} = $lastval; + + $lastkey = ''; + $lastval = ''; + } + + $conf = 1; + } elsif (/^>>>>/) { + # End of conflict. Output. + + # Copy last key into array. + if ($lastkey ne '') { + $conflist{$lastkey} = $lastval; + + $lastkey = ''; + $lastval = ''; + } + + foreach (reverse sort clcmp keys %conflist) { + print STDERR "doing $_" if $tjd; + print $_; + print $conflist{$_}; + } + + $lastkey = ''; + $lastval = ''; + $conf = 0; + %conflist = (); + } elsif ($conf == 1) { + # Beginning an entry. Skip empty lines. Error if not a real + # beginner. + if (/^$/) { + # Empty line; just skip at this point. + } elsif (/^[MTWFS]/) { + # Looks like the name of a day; assume opener and move to + # "in entry" state. + $lastkey = $_; + $conf = 2; + print STDERR "found $_" if $tjd; + } else { + die ("conflict crosses entry boundaries: $_"); + } + } elsif ($conf == 2) { + # In entry. Copy into variable until we see beginner line. + if (/^[MTWFS]/) { + # Entry beginner line. + + # Copy last key into array. + if ($lastkey ne '') { + $conflist{$lastkey} = $lastval; + + $lastkey = ''; + $lastval = ''; + } + + $lastkey = $_; + print STDERR "found $_" if $tjd; + $lastval = ''; + } else { + $lastval .= $_; + } + } else { + # Just copy. + print; + } +} + +# Compare ChangeLog time strings like <=>. +# +# 0 1 2 3 +# Thu Aug 11 13:22:42 1994 Tom Tromey (tromey@creche.colorado.edu) +# 0123456789012345678901234567890 +# +sub clcmp { + # First check year. + $r = substr ($a, 20, 4) <=> substr ($b, 20, 4); + + # Now check month. + $r = $months{substr ($a, 4, 3)} <=> $months{substr ($b, 4, 3)} if !$r; + + # Now check day. + $r = substr ($a, 8, 2) <=> substr ($b, 8, 2) if !$r; + + # Now check time (3 parts). + $r = substr ($a, 11, 2) <=> substr ($b, 11, 2) if !$r; + $r = substr ($a, 14, 2) <=> substr ($b, 14, 2) if !$r; + $r = substr ($a, 17, 2) <=> substr ($b, 17, 2) if !$r; + + $r; +} diff --git a/gnu/usr.bin/cvs/contrib/cvscheck.sh b/gnu/usr.bin/cvs/contrib/cvscheck.sh new file mode 100644 index 0000000..96dba6e --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/cvscheck.sh @@ -0,0 +1,84 @@ +#! /bin/sh +# $Id: cvscheck.sh,v 1.1 1995/07/10 02:26:29 kfogel Exp $ +# +# cvscheck - identify files added, changed, or removed +# in CVS working directory +# +# Contributed by Lowell Skoog <fluke!lowell@uunet.uu.net> +# +# This program should be run in a working directory that has been +# checked out using CVS. It identifies files that have been added, +# changed, or removed in the working directory, but not "cvs +# committed". It also determines whether the files have been "cvs +# added" or "cvs removed". For directories, it is only practical to +# determine whether they have been added. + +name=cvscheck +changes=0 + +# If we can't run CVS commands in this directory +cvs status . > /dev/null 2>&1 +if [ $? != 0 ] ; then + + # Bail out + echo "$name: there is no version here; bailing out" 1>&2 + exit 1 +fi + +# Identify files added to working directory +for file in .* * ; do + + # Skip '.' and '..' + if [ $file = '.' -o $file = '..' ] ; then + continue + fi + + # If a regular file + if [ -f $file ] ; then + if cvs status $file | grep -s '^From:[ ]*New file' ; then + echo "file added: $file - not CVS committed" + changes=`expr $changes + 1` + elif cvs status $file | grep -s '^From:[ ]*no entry for' ; then + echo "file added: $file - not CVS added, not CVS committed" + changes=`expr $changes + 1` + fi + + # Else if a directory + elif [ -d $file -a $file != CVS.adm ] ; then + + # Move into it + cd $file + + # If CVS commands don't work inside + cvs status . > /dev/null 2>&1 + if [ $? != 0 ] ; then + echo "directory added: $file - not CVS added" + changes=`expr $changes + 1` + fi + + # Move back up + cd .. + fi +done + +# Identify changed files +changedfiles=`cvs diff | egrep '^diff' | awk '{print $3}'` +for file in $changedfiles ; do + echo "file changed: $file - not CVS committed" + changes=`expr $changes + 1` +done + +# Identify files removed from working directory +removedfiles=`cvs status | egrep '^File:[ ]*no file' | awk '{print $4}'` + +# Determine whether each file has been cvs removed +for file in $removedfiles ; do + if cvs status $file | grep -s '^From:[ ]*-' ; then + echo "file removed: $file - not CVS committed" + else + echo "file removed: $file - not CVS removed, not CVS committed" + fi + changes=`expr $changes + 1` +done + +exit $changes diff --git a/gnu/usr.bin/cvs/contrib/descend.sh b/gnu/usr.bin/cvs/contrib/descend.sh new file mode 100644 index 0000000..e6a7880 --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/descend.sh @@ -0,0 +1,116 @@ +#! /bin/sh +# $Id: descend.sh,v 1.1 1995/07/10 02:26:32 kfogel Exp $ +# +# descend - walk down a directory tree and execute a command at each node + +fullname=$0 +name=descend +usage="Usage: $name [-afqrv] command [directory ...]\n +\040\040-a\040\040All: descend into directories starting with '.'\n +\040\040-f\040\040Force: ignore errors during descent\n +\040\040-q\040\040Quiet: don't print directory names\n +\040\040-r\040\040Restricted: don't descend into RCS, CVS.adm, SCCS directories\n +\040\040-v\040\040Verbose: print command before executing it" + +# Scan for options +while getopts afqrv option; do + case $option in + a) + alldirs=$option + options=$options" "-$option + ;; + f) + force=$option + options=$options" "-$option + ;; + q) + verbose= + quiet=$option + options=$options" "-$option + ;; + r) + restricted=$option + options=$options" "-$option + ;; + v) + verbose=$option + quiet= + options=$options" "-$option + ;; + \?) + /usr/5bin/echo $usage 1>&2 + exit 1 + ;; + esac +done +shift `expr $OPTIND - 1` + +# Get command to execute +if [ $# -lt 1 ] ; then + /usr/5bin/echo $usage 1>&2 + exit 1 +else + command=$1 + shift +fi + +# If no directory specified, use '.' +if [ $# -lt 1 ] ; then + default_dir=. +fi + +# For each directory specified +for dir in $default_dir "$@" ; do + + # Spawn sub-shell so we return to starting directory afterward + (cd $dir + + # Execute specified command + if [ -z "$quiet" ] ; then + echo In directory `hostname`:`pwd` + fi + if [ -n "$verbose" ] ; then + echo $command + fi + eval "$command" || if [ -z "$force" ] ; then exit 1; fi + + # Collect dot file names if necessary + if [ -n "$alldirs" ] ; then + dotfiles=.* + else + dotfiles= + fi + + # For each file in current directory + for file in $dotfiles * ; do + + # Skip '.' and '..' + if [ "$file" = "." -o "$file" = ".." ] ; then + continue + fi + + # If a directory but not a symbolic link + if [ -d "$file" -a ! -h "$file" ] ; then + + # If not skipping this type of directory + if [ \( "$file" != "RCS" -a \ + "$file" != "SCCS" -a \ + "$file" != "CVS" -a \ + "$file" != "CVS.adm" \) \ + -o -z "$restricted" ] ; then + + # Recursively descend into it + $fullname $options "$command" "$file" \ + || if [ -z "$force" ] ; then exit 1; fi + fi + + # Else if a directory AND a symbolic link + elif [ -d "$file" -a -h "$file" ] ; then + + if [ -z "$quiet" ] ; then + echo In directory `hostname`:`pwd`/$file: symbolic link: skipping + fi + fi + done + ) || if [ -z "$force" ] ; then exit 1; fi +done diff --git a/gnu/usr.bin/cvs/contrib/dirfns.shar b/gnu/usr.bin/cvs/contrib/dirfns.shar new file mode 100644 index 0000000..8324c41 --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/dirfns.shar @@ -0,0 +1,481 @@ +echo 'directory.3': +sed 's/^X//' >'directory.3' <<'!' +X.TH DIRECTORY 3 imported +X.DA 9 Oct 1985 +X.SH NAME +Xopendir, readdir, telldir, seekdir, rewinddir, closedir \- high-level directory operations +X.SH SYNOPSIS +X.B #include <sys/types.h> +X.br +X.B #include <ndir.h> +X.PP +X.SM +X.B DIR +X.B *opendir(filename) +X.br +X.B char *filename; +X.PP +X.SM +X.B struct direct +X.B *readdir(dirp) +X.br +X.B DIR *dirp; +X.PP +X.SM +X.B long +X.B telldir(dirp) +X.br +X.B DIR *dirp; +X.PP +X.SM +X.B seekdir(dirp, loc) +X.br +X.B DIR *dirp; +X.br +X.B long loc; +X.PP +X.SM +X.B rewinddir(dirp) +X.br +X.B DIR *dirp; +X.PP +X.SM +X.B closedir(dirp) +X.br +X.B DIR *dirp; +X.SH DESCRIPTION +XThis library provides high-level primitives for directory scanning, +Xsimilar to those available for 4.2BSD's (very different) directory system. +X.\"The purpose of this library is to simulate +X.\"the new flexible length directory names of 4.2bsd UNIX +X.\"on top of the old directory structure of v7. +XIt incidentally provides easy portability to and from 4.2BSD (insofar +Xas such portability is not compromised by other 4.2/VAX dependencies). +X.\"It allows programs to be converted immediately +X.\"to the new directory access interface, +X.\"so that they need only be relinked +X.\"when moved to 4.2bsd. +X.\"It is obtained with the loader option +X.\".BR \-lndir . +X.PP +X.I Opendir +Xopens the directory named by +X.I filename +Xand associates a +X.I directory stream +Xwith it. +X.I Opendir +Xreturns a pointer to be used to identify the +X.I directory stream +Xin subsequent operations. +XThe pointer +X.SM +X.B NULL +Xis returned if +X.I filename +Xcannot be accessed or is not a directory. +X.PP +X.I Readdir +Xreturns a pointer to the next directory entry. +XIt returns +X.B NULL +Xupon reaching the end of the directory or detecting +Xan invalid +X.I seekdir +Xoperation. +X.PP +X.I Telldir +Xreturns the current location associated with the named +X.I directory stream. +X.PP +X.I Seekdir +Xsets the position of the next +X.I readdir +Xoperation on the +X.I directory stream. +XThe new position reverts to the one associated with the +X.I directory stream +Xwhen the +X.I telldir +Xoperation was performed. +XValues returned by +X.I telldir +Xare good only for the lifetime of the DIR pointer from +Xwhich they are derived. +XIf the directory is closed and then reopened, +Xthe +X.I telldir +Xvalue may be invalidated +Xdue to undetected directory compaction in 4.2BSD. +XIt is safe to use a previous +X.I telldir +Xvalue immediately after a call to +X.I opendir +Xand before any calls to +X.I readdir. +X.PP +X.I Rewinddir +Xresets the position of the named +X.I directory stream +Xto the beginning of the directory. +X.PP +X.I Closedir +Xcauses the named +X.I directory stream +Xto be closed, +Xand the structure associated with the DIR pointer to be freed. +X.PP +XA +X.I direct +Xstructure is as follows: +X.PP +X.RS +X.nf +Xstruct direct { +X /* unsigned */ long d_ino; /* inode number of entry */ +X unsigned short d_reclen; /* length of this record */ +X unsigned short d_namlen; /* length of string in d_name */ +X char d_name[MAXNAMLEN + 1]; /* name must be no longer than this */ +X}; +X.fi +X.RE +X.PP +XThe +X.I d_reclen +Xfield is meaningless in non-4.2BSD systems and should be ignored. +XThe use of a +X.I long +Xfor +X.I d_ino +Xis also a 4.2BSDism; +X.I ino_t +X(see +X.IR types (5)) +Xshould be used elsewhere. +XThe macro +X.I DIRSIZ(dp) +Xgives the minimum memory size needed to hold the +X.I direct +Xvalue pointed to by +X.IR dp , +Xwith the minimum necessary allocation for +X.IR d_name . +X.PP +XThe preferred way to search the current directory for entry ``name'' is: +X.PP +X.RS +X.nf +X len = strlen(name); +X dirp = opendir("."); +X if (dirp == NULL) { +X fprintf(stderr, "%s: can't read directory .\\n", argv[0]); +X return NOT_FOUND; +X } +X while ((dp = readdir(dirp)) != NULL) +X if (dp->d_namlen == len && strcmp(dp->d_name, name) == 0) { +X closedir(dirp); +X return FOUND; +X } +X closedir(dirp); +X return NOT_FOUND; +X.RE +X.\".SH LINKING +X.\"This library is accessed by specifying ``-lndir'' as the +X.\"last argument to the compile line, e.g.: +X.\".PP +X.\" cc -I/usr/include/ndir -o prog prog.c -lndir +X.SH "SEE ALSO" +Xopen(2), +Xclose(2), +Xread(2), +Xlseek(2) +X.SH HISTORY +XWritten by +XKirk McKusick at Berkeley (ucbvax!mckusick). +XMiscellaneous bug fixes from elsewhere. +XThe size of the data structure has been decreased to avoid excessive +Xspace waste under V7 (where filenames are 14 characters at most). +XFor obscure historical reasons, the include file is also available +Xas +X.IR <ndir/sys/dir.h> . +XThe Berkeley version lived in a separate library (\fI\-lndir\fR), +Xwhereas ours is +Xpart of the C library, although the separate library is retained to +Xmaximize compatibility. +X.PP +XThis manual page has been substantially rewritten to be informative in +Xthe absence of a 4.2BSD manual. +X.SH BUGS +XThe +X.I DIRSIZ +Xmacro actually wastes a bit of space due to some padding requirements +Xthat are an artifact of 4.2BSD. +X.PP +XThe returned value of +X.I readdir +Xpoints to a static area that will be overwritten by subsequent calls. +X.PP +XThere are some unfortunate name conflicts with the \fIreal\fR V7 +Xdirectory structure definitions. +! +echo 'dir.h': +sed 's/^X//' >'dir.h' <<'!' +X/* dir.h 4.4 82/07/25 */ +X +X/* +X * A directory consists of some number of blocks of DIRBLKSIZ +X * bytes, where DIRBLKSIZ is chosen such that it can be transferred +X * to disk in a single atomic operation (e.g. 512 bytes on most machines). +X * +X * Each DIRBLKSIZ byte block contains some number of directory entry +X * structures, which are of variable length. Each directory entry has +X * a struct direct at the front of it, containing its inode number, +X * the length of the entry, and the length of the name contained in +X * the entry. These are followed by the name padded to a 4 byte boundary +X * with null bytes. All names are guaranteed null terminated. +X * The maximum length of a name in a directory is MAXNAMLEN. +X * +X * The macro DIRSIZ(dp) gives the amount of space required to represent +X * a directory entry. Free space in a directory is represented by +X * entries which have dp->d_reclen >= DIRSIZ(dp). All DIRBLKSIZ bytes +X * in a directory block are claimed by the directory entries. This +X * usually results in the last entry in a directory having a large +X * dp->d_reclen. When entries are deleted from a directory, the +X * space is returned to the previous entry in the same directory +X * block by increasing its dp->d_reclen. If the first entry of +X * a directory block is free, then its dp->d_ino is set to 0. +X * Entries other than the first in a directory do not normally have +X * dp->d_ino set to 0. +X */ +X#define DIRBLKSIZ 512 +X#ifdef VMUNIX +X#define MAXNAMLEN 255 +X#else +X#define MAXNAMLEN 14 +X#endif +X +Xstruct direct { +X /* unsigned */ long d_ino; /* inode number of entry */ +X unsigned short d_reclen; /* length of this record */ +X unsigned short d_namlen; /* length of string in d_name */ +X char d_name[MAXNAMLEN + 1]; /* name must be no longer than this */ +X}; +X +X/* +X * The DIRSIZ macro gives the minimum record length which will hold +X * the directory entry. This requires the amount of space in struct direct +X * without the d_name field, plus enough space for the name with a terminating +X * null byte (dp->d_namlen+1), rounded up to a 4 byte boundary. +X */ +X#undef DIRSIZ +X#define DIRSIZ(dp) \ +X ((sizeof (struct direct) - (MAXNAMLEN+1)) + (((dp)->d_namlen+1 + 3) &~ 3)) +X +X#ifndef KERNEL +X/* +X * Definitions for library routines operating on directories. +X */ +Xtypedef struct _dirdesc { +X int dd_fd; +X long dd_loc; +X long dd_size; +X char dd_buf[DIRBLKSIZ]; +X} DIR; +X#ifndef NULL +X#define NULL 0 +X#endif +Xextern DIR *opendir(); +Xextern struct direct *readdir(); +Xextern long telldir(); +X#ifdef void +Xextern void seekdir(); +Xextern void closedir(); +X#endif +X#define rewinddir(dirp) seekdir((dirp), (long)0) +X#endif KERNEL +! +echo 'makefile': +sed 's/^X//' >'makefile' <<'!' +XDIR = closedir.o opendir.o readdir.o seekdir.o telldir.o +XCFLAGS=-O -I. -Dvoid=int +XDEST=.. +X +Xall: $(DIR) +X +Xmv: $(DIR) +X mv $(DIR) $(DEST) +X +Xcpif: dir.h +X cp dir.h /usr/include/ndir.h +X +Xclean: +X rm -f *.o +! +echo 'closedir.c': +sed 's/^X//' >'closedir.c' <<'!' +Xstatic char sccsid[] = "@(#)closedir.c 4.2 3/10/82"; +X +X#include <sys/types.h> +X#include <dir.h> +X +X/* +X * close a directory. +X */ +Xvoid +Xclosedir(dirp) +X register DIR *dirp; +X{ +X close(dirp->dd_fd); +X dirp->dd_fd = -1; +X dirp->dd_loc = 0; +X free((char *)dirp); +X} +! +echo 'opendir.c': +sed 's/^X//' >'opendir.c' <<'!' +X/* Copyright (c) 1982 Regents of the University of California */ +X +Xstatic char sccsid[] = "@(#)opendir.c 4.4 11/12/82"; +X +X#include <sys/types.h> +X#include <sys/stat.h> +X#include <dir.h> +X +X/* +X * open a directory. +X */ +XDIR * +Xopendir(name) +X char *name; +X{ +X register DIR *dirp; +X register int fd; +X struct stat statbuf; +X char *malloc(); +X +X if ((fd = open(name, 0)) == -1) +X return NULL; +X if (fstat(fd, &statbuf) == -1 || !(statbuf.st_mode & S_IFDIR)) { +X close(fd); +X return NULL; +X } +X if ((dirp = (DIR *)malloc(sizeof(DIR))) == NULL) { +X close (fd); +X return NULL; +X } +X dirp->dd_fd = fd; +X dirp->dd_loc = 0; +X dirp->dd_size = 0; /* so that telldir will work before readdir */ +X return dirp; +X} +! +echo 'readdir.c': +sed 's/^X//' >'readdir.c' <<'!' +X/* Copyright (c) 1982 Regents of the University of California */ +X +Xstatic char sccsid[] = "@(#)readdir.c 4.3 8/8/82"; +X +X#include <sys/types.h> +X#include <dir.h> +X +X/* +X * read an old stlye directory entry and present it as a new one +X */ +X#define ODIRSIZ 14 +X +Xstruct olddirect { +X ino_t od_ino; +X char od_name[ODIRSIZ]; +X}; +X +X/* +X * get next entry in a directory. +X */ +Xstruct direct * +Xreaddir(dirp) +X register DIR *dirp; +X{ +X register struct olddirect *dp; +X static struct direct dir; +X +X for (;;) { +X if (dirp->dd_loc == 0) { +X dirp->dd_size = read(dirp->dd_fd, dirp->dd_buf, +X DIRBLKSIZ); +X if (dirp->dd_size <= 0) { +X dirp->dd_size = 0; +X return NULL; +X } +X } +X if (dirp->dd_loc >= dirp->dd_size) { +X dirp->dd_loc = 0; +X continue; +X } +X dp = (struct olddirect *)(dirp->dd_buf + dirp->dd_loc); +X dirp->dd_loc += sizeof(struct olddirect); +X if (dp->od_ino == 0) +X continue; +X dir.d_ino = dp->od_ino; +X strncpy(dir.d_name, dp->od_name, ODIRSIZ); +X dir.d_name[ODIRSIZ] = '\0'; /* insure null termination */ +X dir.d_namlen = strlen(dir.d_name); +X dir.d_reclen = DIRBLKSIZ; +X return (&dir); +X } +X} +! +echo 'seekdir.c': +sed 's/^X//' >'seekdir.c' <<'!' +Xstatic char sccsid[] = "@(#)seekdir.c 4.9 3/25/83"; +X +X#include <sys/param.h> +X#include <dir.h> +X +X/* +X * seek to an entry in a directory. +X * Only values returned by "telldir" should be passed to seekdir. +X */ +Xvoid +Xseekdir(dirp, loc) +X register DIR *dirp; +X long loc; +X{ +X long curloc, base, offset; +X struct direct *dp; +X extern long lseek(); +X +X curloc = telldir(dirp); +X if (loc == curloc) +X return; +X base = loc & ~(DIRBLKSIZ - 1); +X offset = loc & (DIRBLKSIZ - 1); +X (void) lseek(dirp->dd_fd, base, 0); +X dirp->dd_size = 0; +X dirp->dd_loc = 0; +X while (dirp->dd_loc < offset) { +X dp = readdir(dirp); +X if (dp == NULL) +X return; +X } +X} +! +echo 'telldir.c': +sed 's/^X//' >'telldir.c' <<'!' +Xstatic char sccsid[] = "@(#)telldir.c 4.1 2/21/82"; +X +X#include <sys/types.h> +X#include <dir.h> +X +X/* +X * return a pointer into a directory +X */ +Xlong +Xtelldir(dirp) +X DIR *dirp; +X{ +X long lseek(); +X +X return (lseek(dirp->dd_fd, 0L, 1) - dirp->dd_size + dirp->dd_loc); +X} +! +echo done diff --git a/gnu/usr.bin/cvs/contrib/rcs-to-cvs.sh b/gnu/usr.bin/cvs/contrib/rcs-to-cvs.sh new file mode 100644 index 0000000..3af83d7 --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/rcs-to-cvs.sh @@ -0,0 +1,185 @@ +#! /bin/sh +# +# $Id: rcs-to-cvs.sh,v 1.2 1995/07/15 03:40:34 jimb Exp $ +# Based on the CVS 1.0 checkin csh script. +# Contributed by Per Cederqvist <ceder@signum.se>. +# Rewritten in sh by David MacKenzie <djm@cygnus.com>. +# +# Copyright (c) 1989, Brian Berliner +# +# You may distribute under the terms of the GNU General Public License. +# +############################################################################# +# +# Check in sources that previously were under RCS or no source control system. +# +# The repository is the directory where the sources should be deposited. +# +# Traverses the current directory, ensuring that an +# identical directory structure exists in the repository directory. It +# then checks the files in in the following manner: +# +# 1) If the file doesn't yet exist, check it in as revision 1.1 +# +# The script also is somewhat verbose in letting the user know what is +# going on. It prints a diagnostic when it creates a new file, or updates +# a file that has been modified on the trunk. +# +# Bugs: doesn't put the files in branch 1.1.1 +# doesn't put in release and vendor tags +# +############################################################################# + +usage="Usage: rcs-to-cvs [-v] [-m message] [-f message_file] repository" +vbose=0 +message="" +message_file=/usr/tmp/checkin.$$ +got_one=0 + +if [ $# -lt 1 ]; then + echo "$usage" >&2 + exit 1 +fi + +while [ $# -ne 0 ]; do + case "$1" in + -v) + vbose=1 + ;; + -m) + shift + echo $1 > $message_file + got_one=1 + ;; + -f) + shift + message_file=$1 + got_one=2 + ;; + *) + break + esac + shift +done + +if [ $# -lt 1 ]; then + echo "$usage" >&2 + exit 1 +fi + +repository=$1 +shift + +if [ -z "$CVSROOT" ]; then + echo "Please the environmental variable CVSROOT to the root" >&2 + echo " of the tree you wish to update" >&2 + exit 1 +fi + +if [ $got_one -eq 0 ]; then + echo "Please Edit this file to contain the RCS log information" >$message_file + echo "to be associated with this directory (please remove these lines)">>$message_file + ${EDITOR-/usr/ucb/vi} $message_file + got_one=1 +fi + +# Ya gotta share. +umask 0 + +update_dir=${CVSROOT}/${repository} +[ ! -d ${update_dir} ] && mkdir $update_dir + +if [ -d SCCS ]; then + echo SCCS files detected! >&2 + exit 1 +fi +if [ -d RCS ]; then + co RCS/* +fi + +for name in * .[a-zA-Z0-9]* +do + case "$name" in + RCS | *~ | \* | .\[a-zA-Z0-9\]\* ) continue ;; + esac + echo $name + if [ $vbose -ne 0 ]; then + echo "Updating ${repository}/${name}" + fi + if [ -d "$name" ]; then + if [ ! -d "${update_dir}/${name}" ]; then + echo "WARNING: Creating new directory ${repository}/${name}" + mkdir "${update_dir}/${name}" + if [ $? -ne 0 ]; then + echo "ERROR: mkdir failed - aborting" >&2 + exit 1 + fi + fi + cd "$name" + if [ $? -ne 0 ]; then + echo "ERROR: Couldn\'t cd to $name - aborting" >&2 + exit 1 + fi + if [ $vbose -ne 0 ]; then + $0 -v -f $message_file "${repository}/${name}" + else + $0 -f $message_file "${repository}/${name}" + fi + if [ $? -ne 0 ]; then + exit 1 + fi + cd .. + else # if not directory + if [ ! -f "$name" ]; then + echo "WARNING: $name is neither a regular file" + echo " nor a directory - ignored" + continue + fi + file="${update_dir}/${name},v" + comment="" + if grep -s '\$Log.*\$' "${name}"; then # If $Log keyword + myext=`echo $name | sed 's,.*\.,,'` + [ "$myext" = "$name" ] && myext= + case "$myext" in + c | csh | e | f | h | l | mac | me | mm | ms | p | r | red | s | sh | sl | cl | ml | el | tex | y | ye | yr | "" ) + ;; + + * ) + echo "For file ${file}:" + grep '\$Log.*\$' "${name}" + echo -n "Please insert a comment leader for file ${name} > " + read comment + ;; + esac + fi + if [ ! -f "$file" ]; then # If not exists in repository + if [ ! -f "${update_dir}/Attic/${name},v" ]; then + echo "WARNING: Creating new file ${repository}/${name}" + if [ -f RCS/"${name}",v ]; then + echo "MSG: Copying old rcs file." + cp RCS/"${name}",v "$file" + else + if [ -n "${comment}" ]; then + rcs -q -i -c"${comment}" -t${message_file} -m'.' "$file" + fi + ci -q -u1.1 -t${message_file} -m'.' "$file" + if [ $? -ne 0 ]; then + echo "ERROR: Initial check-in of $file failed - aborting" >&2 + exit 1 + fi + fi + else + file="${update_dir}/Attic/${name},v" + echo "WARNING: IGNORED: ${repository}/Attic/${name}" + continue + fi + else # File existed + echo "ERROR: File exists in repository: Ignored: $file" + continue + fi + fi +done + +[ $got_one -eq 1 ] && rm -f $message_file + +exit 0 diff --git a/gnu/usr.bin/cvs/contrib/rcs2log.sh b/gnu/usr.bin/cvs/contrib/rcs2log.sh new file mode 100644 index 0000000..ccea907 --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/rcs2log.sh @@ -0,0 +1,592 @@ +#! /bin/sh + +# RCS to ChangeLog generator + +# Generate a change log prefix from RCS files and the ChangeLog (if any). +# Output the new prefix to standard output. +# You can edit this prefix by hand, and then prepend it to ChangeLog. + +# Ignore log entries that start with `#'. +# Clump together log entries that start with `{topic} ', +# where `topic' contains neither white space nor `}'. + +# Author: Paul Eggert <eggert@twinsun.com> + +# $Id: rcs2log.sh,v 1.2 1995/07/28 19:48:45 eggert Exp $ + +# Copyright 1992, 1993, 1994, 1995 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; see the file COPYING. If not, write to +# the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +tab=' ' +nl=' +' + +# Parse options. + +# defaults +: ${AWK=awk} +: ${TMPDIR=/tmp} +hostname= # name of local host (if empty, will deduce it later) +indent=8 # indent of log line +length=79 # suggested max width of log line +logins= # login names for people we know fullnames and mailaddrs of +loginFullnameMailaddrs= # login<tab>fullname<tab>mailaddr triplets +recursive= # t if we want recursive rlog +rlog_options= # options to pass to rlog +tabwidth=8 # width of horizontal tab + +while : +do + case $1 in + -i) indent=${2?}; shift;; + -h) hostname=${2?}; shift;; + -l) length=${2?}; shift;; + -[nu]) # -n is obsolescent; it is replaced by -u. + case $1 in + -n) case ${2?}${3?}${4?} in + *"$tab"* | *"$nl"*) + echo >&2 "$0: -n '$2' '$3' '$4': tabs, newlines not allowed" + exit 1 + esac + loginFullnameMailaddrs=$loginFullnameMailaddrs$nl$2$tab$3$tab$4 + shift; shift; shift;; + -u) + # If $2 is not tab-separated, use colon for separator. + case ${2?} in + *"$nl"*) + echo >&2 "$0: -u '$2': newlines not allowed" + exit 1;; + *"$tab"*) + t=$tab;; + *) + t=: + esac + case $2 in + *"$t"*"$t"*"$t"*) + echo >&2 "$0: -u '$2': too many fields" + exit 1;; + *"$t"*"$t"*) + ;; + *) + echo >&2 "$0: -u '$2': not enough fields" + exit 1 + esac + loginFullnameMailaddrs=$loginFullnameMailaddrs$nl$2 + shift + esac + logins=$logins$nl$login + ;; + -r) rlog_options=$rlog_options$nl${2?}; shift;; + -R) recursive=t;; + -t) tabwidth=${2?}; shift;; + -*) echo >&2 "$0: usage: $0 [options] [file ...] +Options: + [-h hostname] [-i indent] [-l length] [-R] [-r rlog_option] + [-t tabwidth] [-u 'login<TAB>fullname<TAB>mailaddr']..." + exit 1;; + *) break + esac + shift +done + +month_data=' + m[0]="Jan"; m[1]="Feb"; m[2]="Mar" + m[3]="Apr"; m[4]="May"; m[5]="Jun" + m[6]="Jul"; m[7]="Aug"; m[8]="Sep" + m[9]="Oct"; m[10]="Nov"; m[11]="Dec" + + # days in non-leap year thus far, indexed by month (0-12) + mo[0]=0; mo[1]=31; mo[2]=59; mo[3]=90 + mo[4]=120; mo[5]=151; mo[6]=181; mo[7]=212 + mo[8]=243; mo[9]=273; mo[10]=304; mo[11]=334 + mo[12]=365 +' + + +# Put rlog output into $rlogout. + +# If no rlog options are given, +# log the revisions checked in since the first ChangeLog entry. +case $rlog_options in +'') + date=1970 + if test -s ChangeLog + then + # Add 1 to seconds to avoid duplicating most recent log. + e=' + /^... ... [ 0-9][0-9] [ 0-9][0-9]:[0-9][0-9]:[0-9][0-9] [0-9]+ /{ + '"$month_data"' + year = $5 + for (i=0; i<=11; i++) if (m[i] == $2) break + dd = $3 + hh = substr($0,12,2) + mm = substr($0,15,2) + ss = substr($0,18,2) + ss++ + if (ss == 60) { + ss = 0 + mm++ + if (mm == 60) { + mm = 0 + hh++ + if (hh == 24) { + hh = 0 + dd++ + monthdays = mo[i+1] - mo[i] + if (i == 1 && year%4 == 0 && (year%100 != 0 || year%400 == 0)) monthdays++ + if (dd == monthdays + 1) { + dd = 1 + i++ + if (i == 12) { + i = 0 + year++ + } + } + } + } + } + # Output comma instead of space to avoid CVS 1.5 bug. + printf "%d/%02d/%02d,%02d:%02d:%02d\n", year,i+1,dd,hh,mm,ss + exit + } + ' + d=`$AWK "$e" <ChangeLog` || exit + case $d in + ?*) date=$d + esac + fi + datearg="-d>$date" +esac + +# If CVS is in use, examine its repository, not the normal RCS files. +if test ! -f CVS/Repository +then + rlog=rlog + repository= +else + rlog='cvs log' + repository=`sed 1q <CVS/Repository` || exit + test ! -f CVS/Root || CVSROOT=`cat <CVS/Root` || exit + case $CVSROOT in + *:/*) + # remote repository + ;; + *) + # local repository + case $repository in + /*) ;; + *) repository=${CVSROOT?}/$repository + esac + if test ! -d "$repository" + then + echo >&2 "$0: $repository: bad repository (see CVS/Repository)" + exit 1 + fi + esac +fi + +# With no arguments, examine all files under the RCS directory. +case $# in +0) + case $repository in + '') + oldIFS=$IFS + IFS=$nl + case $recursive in + t) + RCSdirs=`find . -name RCS -type d -print` + filesFromRCSfiles='s|,v$||; s|/RCS/|/|; s|^\./||' + files=` + { + case $RCSdirs in + ?*) find $RCSdirs -type f -print + esac + find . -name '*,v' -print + } | + sort -u | + sed "$filesFromRCSfiles" + `;; + *) + files= + for file in RCS/.* RCS/* .*,v *,v + do + case $file in + RCS/. | RCS/..) continue;; + RCS/.\* | RCS/\* | .\*,v | \*,v) test -f "$file" || continue + esac + files=$files$nl$file + done + case $files in + '') exit 0 + esac + esac + set x $files + shift + IFS=$oldIFS + esac +esac + +llogout=$TMPDIR/rcs2log$$l +rlogout=$TMPDIR/rcs2log$$r +trap exit 1 2 13 15 +trap "rm -f $llogout $rlogout; exit 1" 0 + +case $rlog_options in +?*) $rlog $rlog_options ${1+"$@"} >$rlogout;; +'') $rlog "$datearg" ${1+"$@"} >$rlogout +esac || exit + + +# Get the full name of each author the logs mention, and set initialize_fullname +# to awk code that initializes the `fullname' awk associative array. +# Warning: foreign authors (i.e. not known in the passwd file) are mishandled; +# you have to fix the resulting output by hand. + +initialize_fullname= +initialize_mailaddr= + +case $loginFullnameMailaddrs in +?*) + case $loginFullnameMailaddrs in + *\"* | *\\*) + sed 's/["\\]/\\&/g' >$llogout <<EOF || exit +$loginFullnameMailaddrs +EOF + loginFullnameMailaddrs=`cat $llogout` + esac + + oldIFS=$IFS + IFS=$nl + for loginFullnameMailaddr in $loginFullnameMailaddrs + do + case $loginFullnameMailaddr in + *"$tab"*) IFS=$tab;; + *) IFS=: + esac + set x $loginFullnameMailaddr + login=$2 + fullname=$3 + mailaddr=$4 + initialize_fullname="$initialize_fullname + fullname[\"$login\"] = \"$fullname\"" + initialize_mailaddr="$initialize_mailaddr + mailaddr[\"$login\"] = \"$mailaddr\"" + done + IFS=$oldIFS +esac + +case $llogout in +?*) sort -u -o $llogout <<EOF || exit +$logins +EOF +esac +output_authors='/^date: / { + if ($2 ~ /^[0-9]*[-\/][0-9][0-9][-\/][0-9][0-9]$/ && $3 ~ /^[0-9][0-9]:[0-9][0-9]:[0-9][0-9][-+0-9:]*;$/ && $4 == "author:" && $5 ~ /^[^;]*;$/) { + print substr($5, 1, length($5)-1) + } +}' +authors=` + $AWK "$output_authors" <$rlogout | + case $llogout in + '') sort -u;; + ?*) sort -u | comm -23 - $llogout + esac +` +case $authors in +?*) + cat >$llogout <<EOF || exit +$authors +EOF + initialize_author_script='s/["\\]/\\&/g; s/.*/author[\"&\"] = 1/' + initialize_author=`sed -e "$initialize_author_script" <$llogout` + awkscript=' + BEGIN { + alphabet = "abcdefghijklmnopqrstuvwxyz" + ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + '"$initialize_author"' + } + { + if (author[$1]) { + fullname = $5 + if (fullname ~ /[0-9]+-[^(]*\([0-9]+\)$/) { + # Remove the junk from fullnames like "0000-Admin(0000)". + fullname = substr(fullname, index(fullname, "-") + 1) + fullname = substr(fullname, 1, index(fullname, "(") - 1) + } + if (fullname ~ /,[^ ]/) { + # Some sites put comma-separated junk after the fullname. + # Remove it, but leave "Bill Gates, Jr" alone. + fullname = substr(fullname, 1, index(fullname, ",") - 1) + } + abbr = index(fullname, "&") + if (abbr) { + a = substr($1, 1, 1) + A = a + i = index(alphabet, a) + if (i) A = substr(ALPHABET, i, 1) + fullname = substr(fullname, 1, abbr-1) A substr($1, 2) substr(fullname, abbr+1) + } + + # Quote quotes and backslashes properly in full names. + # Do not use gsub; traditional awk lacks it. + quoted = "" + rest = fullname + for (;;) { + p = index(rest, "\\") + q = index(rest, "\"") + if (p) { + if (q && q<p) p = q + } else { + if (!q) break + p = q + } + quoted = quoted substr(rest, 1, p-1) "\\" substr(rest, p, 1) + rest = substr(rest, p+1) + } + + printf "fullname[\"%s\"] = \"%s%s\"\n", $1, quoted, rest + author[$1] = 0 + } + } + ' + + initialize_fullname=` + (cat /etc/passwd; ypmatch $authors passwd) 2>/dev/null | + $AWK -F: "$awkscript" + `$initialize_fullname +esac + + +# Function to print a single log line. +# We don't use awk functions, to stay compatible with old awk versions. +# `Log' is the log message (with \n replaced by \r). +# `files' contains the affected files. +printlogline='{ + + # Following the GNU coding standards, rewrite + # * file: (function): comment + # to + # * file (function): comment + if (Log ~ /^\([^)]*\): /) { + i = index(Log, ")") + files = files " " substr(Log, 1, i) + Log = substr(Log, i+3) + } + + # If "label: comment" is too long, break the line after the ":". + sep = " " + if ('"$length"' <= '"$indent"' + 1 + length(files) + index(Log, CR)) sep = "\n" indent_string + + # Print the label. + printf "%s*%s:", indent_string, files + + # Print each line of the log, transliterating \r to \n. + while ((i = index(Log, CR)) != 0) { + logline = substr(Log, 1, i-1) + if (logline ~ /[^'"$tab"' ]/) { + printf "%s%s\n", sep, logline + } else { + print "" + } + sep = indent_string + Log = substr(Log, i+1) + } +}' + +case $hostname in +'') + hostname=`( + hostname || uname -n || uuname -l || cat /etc/whoami + ) 2>/dev/null` || { + echo >&2 "$0: cannot deduce hostname" + exit 1 + } +esac + + +# Process the rlog output, generating ChangeLog style entries. + +# First, reformat the rlog output so that each line contains one log entry. +# Transliterate \n to \r so that multiline entries fit on a single line. +# Discard irrelevant rlog output. +$AWK <$rlogout ' + BEGIN { repository = "'"$repository"'" } + /^RCS file:/ { + if (repository != "") { + filename = $3 + if (substr(filename, 1, length(repository) + 1) == repository "/") { + filename = substr(filename, length(repository) + 2) + } + if (filename ~ /,v$/) { + filename = substr(filename, 1, length(filename) - 2) + } + } + } + /^Working file:/ { if (repository == "") filename = $3 } + /^date: /, /^(-----------*|===========*)$/ { + if ($0 ~ /^branches: /) { next } + if ($0 ~ /^date: [0-9][- +\/0-9:]*;/) { + date = $2 + if (date ~ /-/) { + # An ISO format date. Replace all "-"s with "/"s. + newdate = "" + while ((i = index(date, "-")) != 0) { + newdate = newdate substr(date, 1, i-1) "/" + date = substr(date, i+1) + } + date = newdate date + } + # Ignore any time zone; ChangeLog has no room for it. + time = substr($3, 1, 8) + author = substr($5, 1, length($5)-1) + printf "%s %s %s %s %c", filename, date, time, author, 13 + next + } + if ($0 ~ /^(-----------*|===========*)$/) { print ""; next } + printf "%s%c", $0, 13 + } +' | + +# Now each line is of the form +# FILENAME YYYY/MM/DD HH:MM:SS AUTHOR \rLOG +# where \r stands for a carriage return, +# and each line of the log is terminated by \r instead of \n. +# Sort the log entries, first by date+time (in reverse order), +# then by author, then by log entry, and finally by file name (just in case). +sort +1 -3r +3 +0 | + +# Finally, reformat the sorted log entries. +$AWK ' + BEGIN { + # Some awk variants do not understand "\r" or "\013", so we have to + # put a carriage return directly in the file. + CR="
" # <-- There is a single CR between the " chars here. + + # Initialize the fullname and mailaddr associative arrays. + '"$initialize_fullname"' + '"$initialize_mailaddr"' + + # Initialize indent string. + indent_string = "" + i = '"$indent"' + if (0 < '"$tabwidth"') + for (; '"$tabwidth"' <= i; i -= '"$tabwidth"') + indent_string = indent_string "\t" + while (1 <= i--) + indent_string = indent_string " " + + # Set up date conversion tables. + # RCS uses a nice, clean, sortable format, + # but ChangeLog wants the traditional, ugly ctime format. + + # January 1, 0 AD (Gregorian) was Saturday = 6 + EPOCH_WEEKDAY = 6 + # Of course, there was no 0 AD, but the algorithm works anyway. + + w[0]="Sun"; w[1]="Mon"; w[2]="Tue"; w[3]="Wed" + w[4]="Thu"; w[5]="Fri"; w[6]="Sat" + + '"$month_data"' + } + + { + newlog = substr($0, 1 + index($0, CR)) + + # Ignore log entries prefixed by "#". + if (newlog ~ /^#/) { next } + + if (Log != newlog || date != $2 || author != $4) { + + # The previous log and this log differ. + + # Print the old log. + if (date != "") '"$printlogline"' + + # Logs that begin with "{clumpname} " should be grouped together, + # and the clumpname should be removed. + # Extract the new clumpname from the log header, + # and use it to decide whether to output a blank line. + newclumpname = "" + sep = "\n" + if (date == "") sep = "" + if (newlog ~ /^\{[^'"$tab"' }]*}['"$tab"' ]/) { + i = index(newlog, "}") + newclumpname = substr(newlog, 1, i) + while (substr(newlog, i+1) ~ /^['"$tab"' ]/) i++ + newlog = substr(newlog, i+1) + if (clumpname == newclumpname) sep = "" + } + printf sep + clumpname = newclumpname + + # Get ready for the next log. + Log = newlog + if (files != "") + for (i in filesknown) + filesknown[i] = 0 + files = "" + } + if (date != $2 || author != $4) { + # The previous date+author and this date+author differ. + # Print the new one. + date = $2 + author = $4 + + # Convert nice RCS date like "1992/01/03 00:03:44" + # into ugly ctime date like "Fri Jan 3 00:03:44 1992". + # Calculate day of week from Gregorian calendar. + i = index($2, "/") + year = substr($2, 1, i-1) + 0 + monthday = substr($2, i+1) + i = index(monthday, "/") + month = substr(monthday, 1, i-1) + 0 + day = substr(monthday, i+1) + 0 + leap = 0 + if (2 < month && year%4 == 0 && (year%100 != 0 || year%400 == 0)) leap = 1 + days_since_Sunday_before_epoch = EPOCH_WEEKDAY + year * 365 + int((year + 3) / 4) - int((year + 99) / 100) + int((year + 399) / 400) + mo[month-1] + leap + day - 1 + + # Print "date fullname (email address)". + # Get fullname and email address from associative arrays; + # default to author and author@hostname if not in arrays. + if (fullname[author]) + auth = fullname[author] + else + auth = author + printf "%s %s %2d %s %d %s ", w[days_since_Sunday_before_epoch%7], m[month-1], day, $3, year, auth + if (mailaddr[author]) + printf "<%s>\n\n", mailaddr[author] + else + printf "<%s@%s>\n\n", author, "'"$hostname"'" + } + if (! filesknown[$1]) { + filesknown[$1] = 1 + if (files == "") files = " " $1 + else files = files ", " $1 + } + } + END { + # Print the last log. + if (date != "") { + '"$printlogline"' + printf "\n" + } + } +' && + + +# Exit successfully. + +exec rm -f $llogout $rlogout diff --git a/gnu/usr.bin/cvs/contrib/rcs2sccs.sh b/gnu/usr.bin/cvs/contrib/rcs2sccs.sh new file mode 100644 index 0000000..af70138 --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/rcs2sccs.sh @@ -0,0 +1,143 @@ +#! /bin/sh +# +# +# OrigId: rcs2sccs,v 1.12 90/10/04 20:52:23 kenc Exp Locker: kenc +# $Id: rcs2sccs.sh,v 1.1 1995/07/10 02:26:45 kfogel Exp $ + +############################################################ +# Error checking +# +if [ ! -d SCCS ] ; then + mkdir SCCS +fi + +logfile=/tmp/rcs2sccs_$$_log +rm -f $logfile +tmpfile=/tmp/rcs2sccs_$$_tmp +rm -f $tmpfile +emptyfile=/tmp/rcs2sccs_$$_empty +echo -n "" > $emptyfile +initialfile=/tmp/rcs2sccs_$$_init +echo "Initial revision" > $initialfile +sedfile=/tmp/rcs2sccs_$$_sed +rm -f $sedfile +revfile=/tmp/rcs2sccs_$$_rev +rm -f $revfile +commentfile=/tmp/rcs2sccs_$$_comment +rm -f $commentfile + +# create the sed script +cat > $sedfile << EOF +s,;Id;,%Z%%M% %I% %E%,g +s,;SunId;,%Z%%M% %I% %E%,g +s,;RCSfile;,%M%,g +s,;Revision;,%I%,g +s,;Date;,%E%,g +s,;Id:.*;,%Z%%M% %I% %E%,g +s,;SunId:.*;,%Z%%M% %I% %E%,g +s,;RCSfile:.*;,%M%,g +s,;Revision:.*;,%I%,g +s,;Date:.*;,%E%,g +EOF +sed -e 's/;/\\$/g' $sedfile > $tmpfile +cp $tmpfile $sedfile +############################################################ +# Loop over every RCS file in RCS dir +# +for vfile in *,v; do + # get rid of the ",v" at the end of the name + file=`echo $vfile | sed -e 's/,v$//'` + + # work on each rev of that file in ascending order + firsttime=1 + rlog $file | grep "^revision [0-9][0-9]*\." | awk '{print $2}' | sed -e 's/\./ /g' | sort -n -u +0 +1 +2 +3 +4 +5 +6 +7 +8 | sed -e 's/ /./g' > $revfile + for rev in `cat $revfile`; do + if [ $? != 0 ]; then + echo ERROR - revision + exit + fi + # get file into current dir and get stats + date=`rlog -r$rev $file | grep "^date: " | awk '{print $2; exit}' | sed -e 's/^19//'` + time=`rlog -r$rev $file | grep "^date: " | awk '{print $3; exit}' | sed -e 's/;//'` + author=`rlog -r$rev $file | grep "^date: " | awk '{print $5; exit}' | sed -e 's/;//'` + date="$date $time" + echo "" + rlog -r$rev $file | sed -e '/^branches: /d' -e '1,/^date: /d' -e '/^===========/d' -e 's/$/\\/' | awk '{if ((total += length($0) + 1) < 510) print $0}' > $commentfile + echo "==> file $file, rev=$rev, date=$date, author=$author" + rm -f $file + co -r$rev $file >> $logfile 2>&1 + if [ $? != 0 ]; then + echo ERROR - co + exit + fi + echo checked out of RCS + + # add SCCS keywords in place of RCS keywords + sed -f $sedfile $file > $tmpfile + if [ $? != 0 ]; then + echo ERROR - sed + exit + fi + echo performed keyword substitutions + rm -f $file + cp $tmpfile $file + + # check file into SCCS + if [ "$firsttime" = "1" ]; then + firsttime=0 + echo about to do sccs admin + echo sccs admin -n -i$file $file < $commentfile + sccs admin -n -i$file $file < $commentfile >> $logfile 2>&1 + if [ $? != 0 ]; then + echo ERROR - sccs admin + exit + fi + echo initial rev checked into SCCS + else + case $rev in + *.*.*.*) + brev=`echo $rev | sed -e 's/\.[0-9]*$//'` + sccs admin -fb $file 2>>$logfile + echo sccs get -e -p -r$brev $file + sccs get -e -p -r$brev $file >/dev/null 2>>$logfile + ;; + *) + echo sccs get -e -p $file + sccs get -e -p $file >/dev/null 2>> $logfile + ;; + esac + if [ $? != 0 ]; then + echo ERROR - sccs get + exit + fi + sccs delta $file < $commentfile >> $logfile 2>&1 + if [ $? != 0 ]; then + echo ERROR - sccs delta -r$rev $file + exit + fi + echo checked into SCCS + fi + sed -e "s;^d D $rev ../../.. ..:..:.. [^ ][^ ]*;d D $rev $date $author;" SCCS/s.$file > $tmpfile + rm -f SCCS/s.$file + cp $tmpfile SCCS/s.$file + chmod 444 SCCS/s.$file + sccs admin -z $file + if [ $? != 0 ]; then + echo ERROR - sccs admin -z + exit + fi + done + rm -f $file +done + + +############################################################ +# Clean up +# +echo cleaning up... +rm -f $tmpfile $emptyfile $initialfile $sedfile $commentfile +echo =================================================== +echo " Conversion Completed Successfully" +echo =================================================== + +rm -f *,v diff --git a/gnu/usr.bin/cvs/contrib/sccs2rcs.csh b/gnu/usr.bin/cvs/contrib/sccs2rcs.csh new file mode 100644 index 0000000..0f31893 --- /dev/null +++ b/gnu/usr.bin/cvs/contrib/sccs2rcs.csh @@ -0,0 +1,277 @@ +#! xCSH_PATHx -f +# +# Sccs2rcs is a script to convert an existing SCCS +# history into an RCS history without losing any of +# the information contained therein. +# It has been tested under the following OS's: +# SunOS 3.5, 4.0.3, 4.1 +# Ultrix-32 2.0, 3.1 +# +# Things to note: +# + It will NOT delete or alter your ./SCCS history under any circumstances. +# +# + Run in a directory where ./SCCS exists and where you can +# create ./RCS +# +# + /usr/local/bin is put in front of the default path. +# (SCCS under Ultrix is set-uid sccs, bad bad bad, so +# /usr/local/bin/sccs here fixes that) +# +# + Date, time, author, comments, branches, are all preserved. +# +# + If a command fails somewhere in the middle, it bombs with +# a message -- remove what it's done so far and try again. +# "rm -rf RCS; sccs unedit `sccs tell`; sccs clean" +# There is no recovery and exit is far from graceful. +# If a particular module is hanging you up, consider +# doing it separately; move it from the current area so that +# the next run will have a better chance or working. +# Also (for the brave only) you might consider hacking +# the s-file for simpler problems: I've successfully changed +# the date of a delta to be in sync, then run "sccs admin -z" +# on the thing. +# +# + After everything finishes, ./SCCS will be moved to ./old-SCCS. +# +# This file may be copied, processed, hacked, mutilated, and +# even destroyed as long as you don't tell anyone you wrote it. +# +# Ken Cox +# Viewlogic Systems, Inc. +# kenstir@viewlogic.com +# ...!harvard!cg-atla!viewlog!kenstir +# +# Various hacks made by Brian Berliner before inclusion in CVS contrib area. +# +# $Id: sccs2rcs.csh,v 1.1 1995/07/10 02:26:48 kfogel Exp $ + + +#we'll assume the user set up the path correctly +# for the Pmax, /usr/ucb/sccs is suid sccs, what a pain +# /usr/local/bin/sccs should override /usr/ucb/sccs there +set path = (/usr/local/bin $path) + + +############################################################ +# Error checking +# +if (! -w .) then + echo "Error: ./ not writeable by you." + exit 1 +endif +if (! -d SCCS) then + echo "Error: ./SCCS directory not found." + exit 1 +endif +set edits = (`sccs tell`) +if ($#edits) then + echo "Error: $#edits file(s) out for edit...clean up before converting." + exit 1 +endif +if (-d RCS) then + echo "Warning: RCS directory exists" + if (`ls -a RCS | wc -l` > 2) then + echo "Error: RCS directory not empty + exit 1 + endif +else + mkdir RCS +endif + +sccs clean + +set logfile = /tmp/sccs2rcs_$$_log +rm -f $logfile +set tmpfile = /tmp/sccs2rcs_$$_tmp +rm -f $tmpfile +set emptyfile = /tmp/sccs2rcs_$$_empty +echo -n "" > $emptyfile +set initialfile = /tmp/sccs2rcs_$$_init +echo "Initial revision" > $initialfile +set sedfile = /tmp/sccs2rcs_$$_sed +rm -f $sedfile +set revfile = /tmp/sccs2rcs_$$_rev +rm -f $revfile + +# the quotes surround the dollar signs to fool RCS when I check in this script +set sccs_keywords = (\ + '%W%[ ]*%G%'\ + '%W%[ ]*%E%'\ + '%W%'\ + '%Z%%M%[ ]*%I%[ ]*%G%'\ + '%Z%%M%[ ]*%I%[ ]*%E%'\ + '%M%[ ]*%I%[ ]*%G%'\ + '%M%[ ]*%I%[ ]*%E%'\ + '%M%'\ + '%I%'\ + '%G%'\ + '%E%'\ + '%U%') +set rcs_keywords = (\ + '$'Id'$'\ + '$'Id'$'\ + '$'Id'$'\ + '$'SunId'$'\ + '$'SunId'$'\ + '$'Id'$'\ + '$'Id'$'\ + '$'RCSfile'$'\ + '$'Revision'$'\ + '$'Date'$'\ + '$'Date'$'\ + '') + + +############################################################ +# Get some answers from user +# +echo "" +echo "Do you want to be prompted for a description of each" +echo "file as it is checked in to RCS initially?" +echo -n "(y=prompt for description, n=null description) [y] ?" +set ans = $< +if ((_$ans == _) || (_$ans == _y) || (_$ans == _Y)) then + set nodesc = 0 +else + set nodesc = 1 +endif +echo "" +echo "The default keyword substitutions are as follows and are" +echo "applied in the order specified:" +set i = 1 +while ($i <= $#sccs_keywords) +# echo ' '\"$sccs_keywords[$i]\"' ==> '\"$rcs_keywords[$i]\" + echo " $sccs_keywords[$i] ==> $rcs_keywords[$i]" + @ i = $i + 1 +end +echo "" +echo -n "Do you want to change them [n] ?" +set ans = $< +if ((_$ans != _) && (_$ans != _n) && (_$ans != _N)) then + echo "You can't always get what you want." + echo "Edit this script file and change the variables:" + echo ' $sccs_keywords' + echo ' $rcs_keywords' +else + echo "good idea." +endif + +# create the sed script +set i = 1 +while ($i <= $#sccs_keywords) + echo "s,$sccs_keywords[$i],$rcs_keywords[$i],g" >> $sedfile + @ i = $i + 1 +end + +onintr ERROR + +############################################################ +# Loop over every s-file in SCCS dir +# +foreach sfile (SCCS/s.*) + # get rid of the "s." at the beginning of the name + set file = `echo $sfile:t | sed -e "s/^..//"` + + # work on each rev of that file in ascending order + set firsttime = 1 + sccs prs $file | grep "^D " | awk '{print $2}' | sed -e 's/\./ /g' | sort -n -u +0 +1 +2 +3 +4 +5 +6 +7 +8 | sed -e 's/ /./g' > $revfile + foreach rev (`cat $revfile`) + if ($status != 0) goto ERROR + + # get file into current dir and get stats + set date = `sccs prs -r$rev $file | grep "^D " | awk '{printf("19%s %s", $3, $4); exit}'` + set author = `sccs prs -r$rev $file | grep "^D " | awk '{print $5; exit}'` + echo "" + echo "==> file $file, rev=$rev, date=$date, author=$author" + sccs edit -r$rev $file >>& $logfile + if ($status != 0) goto ERROR + echo checked out of SCCS + + # add RCS keywords in place of SCCS keywords + sed -f $sedfile $file > $tmpfile + if ($status != 0) goto ERROR + echo performed keyword substitutions + cp $tmpfile $file + + # check file into RCS + if ($firsttime) then + set firsttime = 0 + if ($nodesc) then + echo about to do ci + echo ci -f -r$rev -d"$date" -w$author -t$emptyfile $file + ci -f -r$rev -d"$date" -w$author -t$emptyfile $file < $initialfile >>& $logfile + if ($status != 0) goto ERROR + echo initial rev checked into RCS without description + else + echo "" + echo Enter a brief description of the file $file \(end w/ Ctrl-D\): + cat > $tmpfile + ci -f -r$rev -d"$date" -w$author -t$tmpfile $file < $initialfile >>& $logfile + if ($status != 0) goto ERROR + echo initial rev checked into RCS + endif + else + # get RCS lock + set lckrev = `echo $rev | sed -e 's/\.[0-9]*$//'` + if ("$lckrev" =~ [0-9]*.*) then + # need to lock the brach -- it is OK if the lock fails + rcs -l$lckrev $file >>& $logfile + else + # need to lock the trunk -- must succeed + rcs -l $file >>& $logfile + if ($status != 0) goto ERROR + endif + echo got lock + sccs prs -r$rev $file | grep "." > $tmpfile + # it's OK if grep fails here and gives status == 1 + # put the delta message in $tmpfile + ed $tmpfile >>& $logfile <<EOF +/COMMENTS +1,.d +w +q +EOF + ci -f -r$rev -d"$date" -w$author $file < $tmpfile >>& $logfile + if ($status != 0) goto ERROR + echo checked into RCS + endif + sccs unedit $file >>& $logfile + if ($status != 0) goto ERROR + end + rm -f $file +end + + +############################################################ +# Clean up +# +echo cleaning up... +mv SCCS old-SCCS +rm -f $tmpfile $emptyfile $initialfile $sedfile +echo =================================================== +echo " Conversion Completed Successfully" +echo "" +echo " SCCS history now in old-SCCS/" +echo =================================================== +set exitval = 0 +goto cleanup + +ERROR: +foreach f (`sccs tell`) + sccs unedit $f +end +echo "" +echo "" +echo Danger\! Danger\! +echo Some command exited with a non-zero exit status. +echo Log file exists in $logfile. +echo "" +echo Incomplete history in ./RCS -- remove it +echo Original unchanged history in ./SCCS +set exitval = 1 + +cleanup: +# leave log file +rm -f $tmpfile $emptyfile $initialfile $sedfile $revfile + +exit $exitval diff --git a/gnu/usr.bin/cvs/cvs/README-rm-add b/gnu/usr.bin/cvs/cvs/README-rm-add new file mode 100644 index 0000000..17c721f --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/README-rm-add @@ -0,0 +1,48 @@ +WHAT THE "DEATH SUPPORT" FEATURES DO: + +(this really should be in the main manual, but noone has gotten around +to updating it). + +CVS with death support can record when a file is active, or alive, and +when it is removed, or dead. With this facility you can record the +history of a file, including the fact that at some point in its life +the file was removed and then later added. + +First, the following now works as expected: + + touch foo + cvs add foo ; cvs ci -m "added" foo + rm foo + cvs rm foo ; cvs ci -m "removed" foo + touch foo + cvs add foo ; cvs ci -m "resurrected" foo + +Second, files can now be added or removed in a branch and later merged +into the trunk. + + cvs update -A + touch a b c + cvs add a b c ; cvs ci -m "added" a b c + cvs tag -b branchtag + cvs update -r branchtag + touch d ; cvs add d + rm a ; cvs rm a + cvs ci -m "added d, removed a" + cvs update -A + cvs update -jbranchtag + +Added and removed files may also be merged between branches. + +Files removed in the trunk may be merged into branches. + +Files added on the trunk are a special case. They cannot be merged +into a branch. Instead, simply branch the file by hand. + +I also extended the "cvs update -j" semantic slightly. Like before, +if you use two -j options, the changes made between the first and the +second will be merged into your working files. This has not changed. + +If you use only one -j option, it is used as the second -j option. +The first is assumed to be the greatest common ancestor revision +between the revision specified by the -j and the BASE revision of your +working file. diff --git a/gnu/usr.bin/cvs/cvs/client.c b/gnu/usr.bin/cvs/cvs/client.c new file mode 100644 index 0000000..dd96db8 --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/client.c @@ -0,0 +1,3542 @@ +/* CVS client-related stuff. */ + +#include "cvs.h" + +#ifdef CLIENT_SUPPORT + +#include "update.h" /* Things shared with update.c */ +#include "md5.h" + +#ifdef AUTH_CLIENT_SUPPORT +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +char *get_cvs_password PROTO((char *user, char *host, char *cvsrooot)); +#endif /* AUTH_CLIENT_SUPPORT */ + +#if HAVE_KERBEROS +#define CVS_PORT 1999 + +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#include <krb.h> + +extern char *krb_realmofhost (); +#ifndef HAVE_KRB_GET_ERR_TEXT +#define krb_get_err_text(status) krb_err_txt[status] +#endif /* HAVE_KRB_GET_ERR_TEXT */ +#endif /* HAVE_KERBEROS */ + +static void add_prune_candidate PROTO((char *)); + +/* All the commands. */ +int add PROTO((int argc, char **argv)); +int admin PROTO((int argc, char **argv)); +int checkout PROTO((int argc, char **argv)); +int commit PROTO((int argc, char **argv)); +int diff PROTO((int argc, char **argv)); +int history PROTO((int argc, char **argv)); +int import PROTO((int argc, char **argv)); +int cvslog PROTO((int argc, char **argv)); +int patch PROTO((int argc, char **argv)); +int release PROTO((int argc, char **argv)); +int cvsremove PROTO((int argc, char **argv)); +int rtag PROTO((int argc, char **argv)); +int status PROTO((int argc, char **argv)); +int tag PROTO((int argc, char **argv)); +int update PROTO((int argc, char **argv)); + +/* All the response handling functions. */ +static void handle_ok PROTO((char *, int)); +static void handle_error PROTO((char *, int)); +static void handle_valid_requests PROTO((char *, int)); +static void handle_checked_in PROTO((char *, int)); +static void handle_new_entry PROTO((char *, int)); +static void handle_checksum PROTO((char *, int)); +static void handle_copy_file PROTO((char *, int)); +static void handle_updated PROTO((char *, int)); +static void handle_merged PROTO((char *, int)); +static void handle_patched PROTO((char *, int)); +static void handle_removed PROTO((char *, int)); +static void handle_remove_entry PROTO((char *, int)); +static void handle_set_static_directory PROTO((char *, int)); +static void handle_clear_static_directory PROTO((char *, int)); +static void handle_set_sticky PROTO((char *, int)); +static void handle_clear_sticky PROTO((char *, int)); +static void handle_set_checkin_prog PROTO((char *, int)); +static void handle_set_update_prog PROTO((char *, int)); +static void handle_module_expansion PROTO((char *, int)); +static void handle_m PROTO((char *, int)); +static void handle_e PROTO((char *, int)); + +#endif /* CLIENT_SUPPORT */ + +#if defined(CLIENT_SUPPORT) || defined(SERVER_SUPPORT) + +/* Shared with server. */ + +/* + * Return a malloc'd, '\0'-terminated string + * corresponding to the mode in SB. + */ +char * +#ifdef __STDC__ +mode_to_string (mode_t mode) +#else /* ! __STDC__ */ +mode_to_string (mode) + mode_t mode; +#endif /* __STDC__ */ +{ + char buf[18], u[4], g[4], o[4]; + int i; + + i = 0; + if (mode & S_IRUSR) u[i++] = 'r'; + if (mode & S_IWUSR) u[i++] = 'w'; + if (mode & S_IXUSR) u[i++] = 'x'; + u[i] = '\0'; + + i = 0; + if (mode & S_IRGRP) g[i++] = 'r'; + if (mode & S_IWGRP) g[i++] = 'w'; + if (mode & S_IXGRP) g[i++] = 'x'; + g[i] = '\0'; + + i = 0; + if (mode & S_IROTH) o[i++] = 'r'; + if (mode & S_IWOTH) o[i++] = 'w'; + if (mode & S_IXOTH) o[i++] = 'x'; + o[i] = '\0'; + + sprintf(buf, "u=%s,g=%s,o=%s", u, g, o); + return xstrdup(buf); +} + +/* + * Change mode of FILENAME to MODE_STRING. + * Returns 0 for success or errno code. + */ +int +change_mode (filename, mode_string) + char *filename; + char *mode_string; +{ + char *p; + mode_t mode = 0; + + p = mode_string; + while (*p != '\0') + { + if ((p[0] == 'u' || p[0] == 'g' || p[0] == 'o') && p[1] == '=') + { + int can_read = 0, can_write = 0, can_execute = 0; + char *q = p + 2; + while (*q != ',' && *q != '\0') + { + if (*q == 'r') + can_read = 1; + else if (*q == 'w') + can_write = 1; + else if (*q == 'x') + can_execute = 1; + ++q; + } + if (p[0] == 'u') + { + if (can_read) + mode |= S_IRUSR; + if (can_write) + mode |= S_IWUSR; + if (can_execute) + { +#ifdef EXECUTE_PERMISSION_LOSES + KFF_DEBUG (printf ("*** S_IXUSR in change_mode().\n")); +#else /* ! EXECUTE_PERMISSION_LOSES */ + mode |= S_IXUSR; +#endif /* EXECUTE_PERMISSION_LOSES */ + } + } + else if (p[0] == 'g') + { + if (can_read) + mode |= S_IRGRP; + if (can_write) + mode |= S_IWGRP; + if (can_execute) + { +#ifdef EXECUTE_PERMISSION_LOSES + KFF_DEBUG (printf ("*** S_IXGRP in change_mode().\n")); +#else /* ! EXECUTE_PERMISSION_LOSES */ + mode |= S_IXGRP; +#endif /* EXECUTE_PERMISSION_LOSES */ + } + } + else if (p[0] == 'o') + { + if (can_read) + mode |= S_IROTH; + if (can_write) + mode |= S_IWOTH; + if (can_execute) + { +#ifdef EXECUTE_PERMISSION_LOSES + KFF_DEBUG (printf ("*** S_IXOTH in change_mode().\n")); +#else /* ! EXECUTE_PERMISSION_LOSES */ + mode |= S_IXOTH; +#endif /* EXECUTE_PERMISSION_LOSES */ + } + } + } + /* Skip to the next field. */ + while (*p != ',' && *p != '\0') + ++p; + if (*p == ',') + ++p; + } + if (chmod (filename, mode) < 0) + return errno; + return 0; +} + +#endif /* CLIENT_SUPPORT or SERVER_SUPPORT */ + +#ifdef CLIENT_SUPPORT + +/* The host part of CVSROOT. */ +static char *server_host; +/* The user part of CVSROOT */ +static char *server_user; +/* The repository part of CVSROOT. */ +static char *server_cvsroot; + +int client_active; + +int client_prune_dirs; + +/* Set server_host and server_cvsroot. */ +static void +parse_cvsroot () +{ + char *p; + + server_host = xstrdup (CVSroot); + server_cvsroot = strchr (server_host, ':'); + *server_cvsroot = '\0'; + ++server_cvsroot; + + if ( (p = strchr (server_host, '@')) == NULL) { + server_user = NULL; + } else { + server_user = server_host; + server_host = p; + ++server_host; + *p = '\0'; + } + + client_active = 1; +} + +/* Stream to write to the server. */ +FILE *to_server; +/* Stream to read from the server. */ +FILE *from_server; + +#if ! RSH_NOT_TRANSPARENT +/* Process ID of rsh subprocess. */ +static int rsh_pid = -1; +#endif /* ! RSH_NOT_TRANSPARENT */ + + +/* + * Read a line from the server. + * + * Space for the result is malloc'd and should be freed by the caller. + * + * Returns number of bytes read. If EOF_OK, then return 0 on end of file, + * else end of file is an error. + */ +static int +read_line (resultp, eof_ok) + char **resultp; + int eof_ok; +{ + int c; + char *result; + int input_index = 0; + int result_size = 80; + + fflush (to_server); + result = (char *) xmalloc (result_size); + + while (1) + { + c = getc (from_server); + + if (c == EOF) + { + free (result); + if (ferror (from_server)) + error (1, errno, "reading from server"); + /* It's end of file. */ + if (eof_ok) + return 0; + else + error (1, 0, "premature end of file from server"); + } + + if (c == '\n') + break; + + result[input_index++] = c; + while (input_index + 1 >= result_size) + { + result_size *= 2; + result = (char *) xrealloc (result, result_size); + } + } + + if (resultp) + *resultp = result; + + /* Terminate it just for kicks, but we *can* deal with embedded NULs. */ + result[input_index] = '\0'; + + if (resultp == NULL) + free (result); + return input_index; +} + +#endif /* CLIENT_SUPPORT */ + + +#if defined(CLIENT_SUPPORT) || defined(SERVER_SUPPORT) + +/* + * Zero if compression isn't supported or requested; non-zero to indicate + * a compression level to request from gzip. + */ +int gzip_level; + +int filter_through_gzip (fd, dir, level, pidp) + int fd, dir, level; + pid_t *pidp; +{ + static char buf[5] = "-"; + static char *gzip_argv[3] = { "gzip", buf }; + + sprintf (buf+1, "%d", level); + return filter_stream_through_program (fd, dir, &gzip_argv[0], pidp); +} + +int filter_through_gunzip (fd, dir, pidp) + int fd, dir; + pid_t *pidp; +{ + static char *gunzip_argv[2] = { "gunzip" }; + return filter_stream_through_program (fd, dir, &gunzip_argv[0], pidp); +} + +#endif /* CLIENT_SUPPORT or SERVER_SUPPORT */ + +#ifdef CLIENT_SUPPORT + +/* + * The Repository for the top level of this command (not necessarily + * the CVSROOT, just the current directory at the time we do it). + */ +static char *toplevel_repos; + +/* Working directory when we first started. */ +char toplevel_wd[PATH_MAX]; + +static void +handle_ok (args, len) + char *args; + int len; +{ + return; +} + +static void +handle_error (args, len) + char *args; + int len; +{ + int something_printed; + + /* + * First there is a symbolic error code followed by a space, which + * we ignore. + */ + char *p = strchr (args, ' '); + if (p == NULL) + { + error (0, 0, "invalid data from cvs server"); + return; + } + ++p; + len -= p - args; + something_printed = 0; + for (; len > 0; --len) + { + something_printed = 1; + putc (*p++, stderr); + } + if (something_printed) + putc ('\n', stderr); +} + +static void +handle_valid_requests (args, len) + char *args; + int len; +{ + char *p = args; + char *q; + struct request *rq; + do + { + q = strchr (p, ' '); + if (q != NULL) + *q++ = '\0'; + for (rq = requests; rq->name != NULL; ++rq) + { + if (strcmp (rq->name, p) == 0) + break; + } + if (rq->name == NULL) + /* + * It is a request we have never heard of (and thus never + * will want to use). So don't worry about it. + */ + ; + else + { + if (rq->status == rq_enableme) + { + /* + * Server wants to know if we have this, to enable the + * feature. + */ + if (fprintf(to_server, "%s\n", rq->name) < 0) + error (1, errno, "writing to server"); + if (!strcmp("UseUnchanged",rq->name)) + use_unchanged = 1; + } + else + rq->status = rq_supported; + } + p = q; + } while (q != NULL); + for (rq = requests; rq->name != NULL; ++rq) + { + if (rq->status == rq_essential) + error (1, 0, "request `%s' not supported by server", rq->name); + else if (rq->status == rq_optional) + rq->status = rq_not_supported; + } +} + +static int use_directory = -1; + +static char *get_short_pathname PROTO((const char *)); + +static char * +get_short_pathname (name) + const char *name; +{ + const char *retval; + if (use_directory) + return (char *) name; + if (strncmp (name, toplevel_repos, strlen (toplevel_repos)) != 0) + error (1, 0, "server bug: name `%s' doesn't specify file in `%s'", + name, toplevel_repos); + retval = name + strlen (toplevel_repos) + 1; + if (retval[-1] != '/') + error (1, 0, "server bug: name `%s' doesn't specify file in `%s'", + name, toplevel_repos); + return (char *) retval; +} + +/* + * Do all the processing for PATHNAME, where pathname consists of the + * repository and the filename. The parameters we pass to FUNC are: + * DATA is just the DATA parameter which was passed to + * call_in_directory; ENT_LIST is a pointer to an entries list (which + * we manage the storage for); SHORT_PATHNAME is the pathname of the + * file relative to the (overall) directory in which the command is + * taking place; and FILENAME is the filename portion only of + * SHORT_PATHNAME. When we call FUNC, the curent directory points to + * the directory portion of SHORT_PATHNAME. */ + +static char *last_dirname; + +static void +call_in_directory (pathname, func, data) + char *pathname; + void (*func) PROTO((char *data, List *ent_list, char *short_pathname, + char *filename)); + char *data; +{ + static List *last_entries; + + char *dirname; + char *filename; + /* Just the part of pathname relative to toplevel_repos. */ + char *short_pathname = get_short_pathname (pathname); + char *p; + + /* + * Do the whole descent in parallel for the repositories, so we + * know what to put in CVS/Repository files. I'm not sure the + * full hair is necessary since the server does a similar + * computation; I suspect that we only end up creating one + * directory at a time anyway. + * + * Also note that we must *only* worry about this stuff when we + * are creating directories; `cvs co foo/bar; cd foo/bar; cvs co + * CVSROOT; cvs update' is legitimate, but in this case + * foo/bar/CVSROOT/CVS/Repository is not a subdirectory of + * foo/bar/CVS/Repository. + */ + char *reposname; + char *short_repos; + char *reposdirname; + char *rdirp; + int reposdirname_absolute; + + reposname = NULL; + if (use_directory) + read_line (&reposname, 0); + + reposdirname_absolute = 0; + if (reposname != NULL) + { + if (strncmp (reposname, toplevel_repos, strlen (toplevel_repos)) != 0) + { + reposdirname_absolute = 1; + short_repos = reposname; + } + else + { + short_repos = reposname + strlen (toplevel_repos) + 1; + if (short_repos[-1] != '/') + { + reposdirname_absolute = 1; + short_repos = reposname; + } + } + } + else + { + short_repos = short_pathname; + } + reposdirname = xstrdup (short_repos); + p = strrchr (reposdirname, '/'); + if (p == NULL) + { + reposdirname = xrealloc (reposdirname, 2); + reposdirname[0] = '.'; reposdirname[1] = '\0'; + } + else + *p = '\0'; + + dirname = xstrdup (short_pathname); + p = strrchr (dirname, '/'); + if (p == NULL) + { + dirname = xrealloc (dirname, 2); + dirname[0] = '.'; dirname[1] = '\0'; + } + else + *p = '\0'; + if (client_prune_dirs) + add_prune_candidate (dirname); + + filename = strrchr (short_repos, '/'); + if (filename == NULL) + filename = short_repos; + else + ++filename; + + if (reposname != NULL) + { + /* This is the use_directory case. */ + + short_pathname = xmalloc (strlen (pathname) + strlen (filename) + 5); + strcpy (short_pathname, pathname); + strcat (short_pathname, filename); + } + + if (last_dirname == NULL + || strcmp (last_dirname, dirname) != 0) + { + if (last_dirname) + free (last_dirname); + last_dirname = dirname; + + if (toplevel_wd[0] == '\0') + if (getwd (toplevel_wd) == NULL) + error (1, 0, + "could not get working directory: %s", toplevel_wd); + + if (chdir (toplevel_wd) < 0) + error (1, errno, "could not chdir to %s", toplevel_wd); + if (chdir (dirname) < 0) + { + char *dir; + char *dirp; + + if (! existence_error (errno)) + error (1, errno, "could not chdir to %s", dirname); + + /* Directory does not exist, we need to create it. */ + dir = xmalloc (strlen (dirname) + 1); + dirp = dirname; + rdirp = reposdirname; + + /* This algorithm makes nested directories one at a time + and create CVS administration files in them. For + example, we're checking out foo/bar/baz from the + repository: + + 1) create foo, point CVS/Repository to <root>/foo + 2) .. foo/bar .. <root>/foo/bar + 3) .. foo/bar/baz .. <root>/foo/bar/baz + + As you can see, we're just stepping along DIRNAME (with + DIRP) and REPOSDIRNAME (with RDIRP) respectively. + + We need to be careful when we are checking out a + module, however, since DIRNAME and REPOSDIRNAME are not + going to be the same. Since modules will not have any + slashes in their names, we should watch the output of + STRCHR to decide whether or not we should use STRCHR on + the RDIRP. That is, if we're down to a module name, + don't keep picking apart the repository directory name. */ + + do + { + dirp = strchr (dirp, '/'); + if (dirp) + { + strncpy (dir, dirname, dirp - dirname); + dir[dirp - dirname] = '\0'; + /* Skip the slash. */ + ++dirp; + if (rdirp == NULL) + error (0, 0, + "internal error: repository string too short."); + else + rdirp = strchr (rdirp, '/'); + } + else + { + /* If there are no more slashes in the dir name, + we're down to the most nested directory -OR- to + the name of a module. In the first case, we + should be down to a DIRP that has no slashes, + so it won't help/hurt to do another STRCHR call + on DIRP. It will definitely hurt, however, if + we're down to a module name, since a module + name can point to a nested directory (that is, + DIRP will still have slashes in it. Therefore, + we should set it to NULL so the routine below + copies the contents of REMOTEDIRNAME onto the + root repository directory (does this if rdirp + is set to NULL, because we used to do an extra + STRCHR call here). */ + + rdirp = NULL; + strcpy (dir, dirname); + } + + if (CVS_MKDIR (dir, 0777) < 0) + { + /* Now, let me get this straight. In IBM C/C++ + * under OS/2, the error string for EEXIST is: + * + * "The file already exists", + * + * and the error string for EACCESS is: + * + * "The file or directory specified is read-only". + * + * Nonetheless, mkdir() will set EACCESS if the + * directory *exists*, according both to the + * documentation and its actual behavior. + * + * I'm sure that this made sense, to someone, + * somewhere, sometime. Just not me, here, now. + */ +#ifdef EACCESS + if ((errno != EACCESS) && (errno != EEXIST)) + error (1, errno, "cannot make directory %s", dir); +#else /* ! defined(EACCESS) */ + if ((errno != EEXIST)) + error (1, errno, "cannot make directory %s", dir); +#endif /* defined(EACCESS) */ + + /* It already existed, fine. Just keep going. */ + } + else if (strcmp (command_name, "export") == 0) + /* Don't create CVSADM directories if this is export. */ + ; + else + { + /* + * Put repository in CVS/Repository. For historical + * (pre-CVS/Root) reasons, this is an absolute pathname, + * but what really matters is the part of it which is + * relative to cvsroot. + */ + char *repo; + char *r; + + repo = xmalloc (strlen (reposdirname) + + strlen (toplevel_repos) + + 80); + if (reposdirname_absolute) + r = repo; + else + { + strcpy (repo, toplevel_repos); + strcat (repo, "/"); + r = repo + strlen (repo); + } + + if (rdirp) + { + strncpy (r, reposdirname, rdirp - reposdirname); + r[rdirp - reposdirname] = '\0'; + } + else + strcpy (r, reposdirname); + + Create_Admin (dir, dir, repo, + (char *)NULL, (char *)NULL); + free (repo); + } + + if (rdirp != NULL) + { + /* Skip the slash. */ + ++rdirp; + } + + } while (dirp != NULL); + free (dir); + /* Now it better work. */ + if (chdir (dirname) < 0) + error (1, errno, "could not chdir to %s", dirname); + } + + if (strcmp (command_name, "export") != 0) + { + if (last_entries) + Entries_Close (last_entries); + last_entries = Entries_Open (0); + } + } + else + free (dirname); + free (reposdirname); + (*func) (data, last_entries, short_pathname, filename); + if (reposname != NULL) + { + free (short_pathname); + free (reposname); + } +} + +static void +copy_a_file (data, ent_list, short_pathname, filename) + char *data; + List *ent_list; + char *short_pathname; + char *filename; +{ + char *newname; + + read_line (&newname, 0); + copy_file (filename, newname); + free (newname); +} + +static void +handle_copy_file (args, len) + char *args; + int len; +{ + call_in_directory (args, copy_a_file, (char *)NULL); +} + +/* + * The Checksum response gives the checksum for the file transferred + * over by the next Updated, Merged or Patch response. We just store + * it here, and then check it in update_entries. + */ + +static int stored_checksum_valid; +static unsigned char stored_checksum[16]; + +static void +handle_checksum (args, len) + char *args; + int len; +{ + char *s; + char buf[3]; + int i; + + if (stored_checksum_valid) + error (1, 0, "Checksum received before last one was used"); + + s = args; + buf[2] = '\0'; + for (i = 0; i < 16; i++) + { + char *bufend; + + buf[0] = *s++; + buf[1] = *s++; + stored_checksum[i] = (char) strtol (buf, &bufend, 16); + if (bufend != buf + 2) + break; + } + + if (i < 16 || *s != '\0') + error (1, 0, "Invalid Checksum response: `%s'", args); + + stored_checksum_valid = 1; +} + +/* + * If we receive a patch, but the patch program fails to apply it, we + * want to request the original file. We keep a list of files whose + * patches have failed. + */ + +char **failed_patches; +int failed_patches_count; + +struct update_entries_data +{ + enum { + /* + * We are just getting an Entries line; the local file is + * correct. + */ + UPDATE_ENTRIES_CHECKIN, + /* We are getting the file contents as well. */ + UPDATE_ENTRIES_UPDATE, + /* + * We are getting a patch against the existing local file, not + * an entire new file. + */ + UPDATE_ENTRIES_PATCH + } contents; + + /* + * String to put in the timestamp field or NULL to use the timestamp + * of the file. + */ + char *timestamp; +}; + +/* Update the Entries line for this file. */ +static void +update_entries (data_arg, ent_list, short_pathname, filename) + char *data_arg; + List *ent_list; + char *short_pathname; + char *filename; +{ + char *entries_line; + struct update_entries_data *data = (struct update_entries_data *)data_arg; + + char *cp; + char *user; + char *vn; + /* Timestamp field. Always empty according to the protocol. */ + char *ts; + char *options; + char *tag; + char *date; + char *tag_or_date; + char *scratch_entries; + int bin = 0; + + read_line (&entries_line, 0); + + /* + * Parse the entries line. + */ + if (strcmp (command_name, "export") != 0) + { + scratch_entries = xstrdup (entries_line); + + if (scratch_entries[0] != '/') + error (1, 0, "bad entries line `%s' from server", entries_line); + user = scratch_entries + 1; + if ((cp = strchr (user, '/')) == NULL) + error (1, 0, "bad entries line `%s' from server", entries_line); + *cp++ = '\0'; + vn = cp; + if ((cp = strchr (vn, '/')) == NULL) + error (1, 0, "bad entries line `%s' from server", entries_line); + *cp++ = '\0'; + + ts = cp; + if ((cp = strchr (ts, '/')) == NULL) + error (1, 0, "bad entries line `%s' from server", entries_line); + *cp++ = '\0'; + options = cp; + if ((cp = strchr (options, '/')) == NULL) + error (1, 0, "bad entries line `%s' from server", entries_line); + *cp++ = '\0'; + tag_or_date = cp; + + /* If a slash ends the tag_or_date, ignore everything after it. */ + cp = strchr (tag_or_date, '/'); + if (cp != NULL) + *cp = '\0'; + tag = (char *) NULL; + date = (char *) NULL; + if (*tag_or_date == 'T') + tag = tag_or_date + 1; + else if (*tag_or_date == 'D') + date = tag_or_date + 1; + } + + if (data->contents == UPDATE_ENTRIES_UPDATE + || data->contents == UPDATE_ENTRIES_PATCH) + { + char *size_string; + char *mode_string; + int size; + int size_read; + int size_left; + int fd; + char *buf; + char *buf2; + char *temp_filename; + int use_gzip, gzip_status; + pid_t gzip_pid = 0; + + read_line (&mode_string, 0); + + read_line (&size_string, 0); + if (size_string[0] == 'z') + { + use_gzip = 1; + size = atoi (size_string+1); + } + else + { + use_gzip = 0; + size = atoi (size_string); + } + free (size_string); + + temp_filename = xmalloc (strlen (filename) + 80); +#ifdef _POSIX_NO_TRUNC + sprintf (temp_filename, ".new.%.9s", filename); +#else /* _POSIX_NO_TRUNC */ + sprintf (temp_filename, ".new.%s", filename); +#endif /* _POSIX_NO_TRUNC */ + buf = xmalloc (size); + + /* Some systems, like OS/2 and Windows NT, end lines with CRLF + instead of just LF. Format translation is done in the C + library I/O funtions. Here we tell them whether or not to + convert -- if this file is marked "binary" with the RCS -kb + flag, then we don't want to convert, else we do (because + CVS assumes text files by default). */ + + bin = !(strcmp (options, "-kb")); + fd = open (temp_filename, + O_WRONLY | O_CREAT | O_TRUNC | (bin ? OPEN_BINARY : 0), + 0777); + + if (fd < 0) + error (1, errno, "writing %s", short_pathname); + + if (use_gzip) + fd = filter_through_gunzip (fd, 0, &gzip_pid); + + if (size > 0) + { + buf2 = buf; + size_left = size; + while ((size_read = fread (buf2, 1, size_left, from_server)) != size_left) + { + if (feof (from_server)) + /* FIXME: Should delete temp_filename. */ + error (1, 0, "unexpected end of file from server"); + else if (ferror (from_server)) + /* FIXME: Should delete temp_filename. */ + error (1, errno, "reading from server"); + else + { + /* short reads are ok if we keep trying */ + buf2 += size_read; + size_left -= size_read; + } + } + if (write (fd, buf, size) != size) + error (1, errno, "writing %s", short_pathname); + } + if (close (fd) < 0) + error (1, errno, "writing %s", short_pathname); + if (gzip_pid > 0) + { + if (waitpid (gzip_pid, &gzip_status, 0) == -1) + error (1, errno, "waiting for gzip process %d", gzip_pid); + else if (gzip_status != 0) + error (1, 0, "gzip process exited %d", gzip_status); + } + + gzip_pid = -1; + + /* Since gunzip writes files without converting LF to CRLF + (a reasonable behavior), we now have a patch file in LF + format. Leave the file as is if we're just going to feed + it to patch; patch can handle it. However, if it's the + final source file, convert it. */ + + if (data->contents == UPDATE_ENTRIES_UPDATE) + { +#ifdef LINES_CRLF_TERMINATED + + /* `bin' is non-zero iff `options' contains "-kb", meaning + treat this file as binary. */ + + if (use_gzip && (! bin)) + { + convert_file (temp_filename, O_RDONLY | OPEN_BINARY, + filename, O_WRONLY | O_CREAT | O_TRUNC); + if (unlink (temp_filename) < 0) + error (0, errno, "warning: couldn't delete %s", + temp_filename); + } + else + rename_file (temp_filename, filename); + +#else /* ! LINES_CRLF_TERMINATED */ + rename_file (temp_filename, filename); +#endif /* LINES_CRLF_TERMINATED */ + } + else + { + int retcode; + char backup[PATH_MAX]; + struct stat s; + + (void) sprintf (backup, "%s~", filename); + (void) unlink_file (backup); + if (!isfile (filename)) + error (1, 0, "patch original file %s does not exist", + short_pathname); + if (stat (temp_filename, &s) < 0) + error (1, 1, "can't stat patch file %s", temp_filename); + if (s.st_size == 0) + retcode = 0; + else + { + run_setup ("%s -f -s -b ~ %s %s", PATCH_PROGRAM, + filename, temp_filename); + retcode = run_exec (DEVNULL, RUN_TTY, RUN_TTY, RUN_NORMAL); + } + /* FIXME: should we really be silently ignoring errors? */ + (void) unlink_file (temp_filename); + if (retcode == 0) + { + /* FIXME: should we really be silently ignoring errors? */ + (void) unlink_file (backup); + } + else + { + int old_errno = errno; + char *path_tmp; + + if (isfile (backup)) + rename_file (backup, filename); + + /* Get rid of the patch reject file. */ + path_tmp = xmalloc (strlen (filename + 10)); + strcpy (path_tmp, filename); + strcat (path_tmp, ".rej"); + /* FIXME: should we really be silently ignoring errors? */ + (void) unlink_file (path_tmp); + free (path_tmp); + + /* Save this file to retrieve later. */ + failed_patches = + (char **) xrealloc ((char *) failed_patches, + ((failed_patches_count + 1) + * sizeof (char *))); + failed_patches[failed_patches_count] = + xstrdup (short_pathname); + ++failed_patches_count; + + error (retcode == -1 ? 1 : 0, retcode == -1 ? old_errno : 0, + "could not patch %s%s", filename, + retcode == -1 ? "" : "; will refetch"); + + stored_checksum_valid = 0; + + return; + } + } + free (temp_filename); + + if (stored_checksum_valid) + { + FILE *e; + struct MD5Context context; + unsigned char buf[8192]; + unsigned len; + unsigned char checksum[16]; + + /* + * Compute the MD5 checksum. This will normally only be + * used when receiving a patch, so we always compute it + * here on the final file, rather than on the received + * data. + * + * Note that if the file is a text file, we should read it + * here using text mode, so its lines will be terminated the same + * way they were transmitted. + */ + e = fopen (filename, "r"); + if (e == NULL) + error (1, errno, "could not open %s", short_pathname); + + MD5Init (&context); + while ((len = fread (buf, 1, sizeof buf, e)) != 0) + MD5Update (&context, buf, len); + if (ferror (e)) + error (1, errno, "could not read %s", short_pathname); + MD5Final (checksum, &context); + + fclose (e); + + stored_checksum_valid = 0; + + if (memcmp (checksum, stored_checksum, 16) != 0) + { + if (data->contents != UPDATE_ENTRIES_PATCH) + error (1, 0, "checksum failure on %s", + short_pathname); + + error (0, 0, + "checksum failure after patch to %s; will refetch", + short_pathname); + + /* Save this file to retrieve later. */ + failed_patches = + (char **) xrealloc ((char *) failed_patches, + ((failed_patches_count + 1) + * sizeof (char *))); + failed_patches[failed_patches_count] = + xstrdup (short_pathname); + ++failed_patches_count; + + return; + } + } + + { + /* FIXME: we should be respecting the umask. */ + int status = change_mode (filename, mode_string); + if (status != 0) + error (0, status, "cannot change mode of %s", short_pathname); + } + + free (mode_string); + free (buf); + } + + /* + * Process the entries line. Do this after we've written the file, + * since we need the timestamp. + */ + if (strcmp (command_name, "export") != 0) + { + char *local_timestamp; + char *file_timestamp; + + local_timestamp = data->timestamp; + if (local_timestamp == NULL || ts[0] == '+') + file_timestamp = time_stamp (filename); + else + file_timestamp = NULL; + + /* + * These special version numbers signify that it is not up to + * date. Create a dummy timestamp which will never compare + * equal to the timestamp of the file. + */ + if (vn[0] == '\0' || vn[0] == '0' || vn[0] == '-') + local_timestamp = "dummy timestamp"; + else if (local_timestamp == NULL) + local_timestamp = file_timestamp; + + Register (ent_list, filename, vn, local_timestamp, + options, tag, date, ts[0] == '+' ? file_timestamp : NULL); + + if (file_timestamp) + free (file_timestamp); + + free (scratch_entries); + } + free (entries_line); +} + +static void +handle_checked_in (args, len) + char *args; + int len; +{ + struct update_entries_data dat; + dat.contents = UPDATE_ENTRIES_CHECKIN; + dat.timestamp = NULL; + call_in_directory (args, update_entries, (char *)&dat); +} + +static void +handle_new_entry (args, len) + char *args; + int len; +{ + struct update_entries_data dat; + dat.contents = UPDATE_ENTRIES_CHECKIN; + dat.timestamp = "dummy timestamp from new-entry"; + call_in_directory (args, update_entries, (char *)&dat); +} + +static void +handle_updated (args, len) + char *args; + int len; +{ + struct update_entries_data dat; + dat.contents = UPDATE_ENTRIES_UPDATE; + dat.timestamp = NULL; + call_in_directory (args, update_entries, (char *)&dat); +} + +static void +handle_merged (args, len) + char *args; + int len; +{ + struct update_entries_data dat; + dat.contents = UPDATE_ENTRIES_UPDATE; + dat.timestamp = "Result of merge"; + call_in_directory (args, update_entries, (char *)&dat); +} + +static void +handle_patched (args, len) + char *args; + int len; +{ + struct update_entries_data dat; + dat.contents = UPDATE_ENTRIES_PATCH; + dat.timestamp = NULL; + call_in_directory (args, update_entries, (char *)&dat); +} + +static void +remove_entry (data, ent_list, short_pathname, filename) + char *data; + List *ent_list; + char *short_pathname; + char *filename; +{ + Scratch_Entry (ent_list, filename); +} + +static void +handle_remove_entry (args, len) + char *args; + int len; +{ + call_in_directory (args, remove_entry, (char *)NULL); +} + +static void +remove_entry_and_file (data, ent_list, short_pathname, filename) + char *data; + List *ent_list; + char *short_pathname; + char *filename; +{ + Scratch_Entry (ent_list, filename); + if (unlink_file (filename) < 0) + error (0, errno, "unable to remove %s", short_pathname); +} + +static void +handle_removed (args, len) + char *args; + int len; +{ + call_in_directory (args, remove_entry_and_file, (char *)NULL); +} + +/* Is this the top level (directory containing CVSROOT)? */ +static int +is_cvsroot_level (pathname) + char *pathname; +{ + char *short_pathname; + + if (strcmp (toplevel_repos, server_cvsroot) != 0) + return 0; + + if (!use_directory) + { + if (strncmp (pathname, server_cvsroot, strlen (server_cvsroot)) != 0) + error (1, 0, + "server bug: pathname `%s' doesn't specify file in `%s'", + pathname, server_cvsroot); + short_pathname = pathname + strlen (server_cvsroot) + 1; + if (short_pathname[-1] != '/') + error (1, 0, + "server bug: pathname `%s' doesn't specify file in `%s'", + pathname, server_cvsroot); + return strchr (short_pathname, '/') == NULL; + } + else + { + return strchr (pathname, '/') == NULL; + } +} + +static void +set_static (data, ent_list, short_pathname, filename) + char *data; + List *ent_list; + char *short_pathname; + char *filename; +{ + FILE *fp; + fp = open_file (CVSADM_ENTSTAT, "w+"); + if (fclose (fp) == EOF) + error (1, errno, "cannot close %s", CVSADM_ENTSTAT); +} + +static void +handle_set_static_directory (args, len) + char *args; + int len; +{ + if (strcmp (command_name, "export") == 0) + { + /* Swallow the repository. */ + read_line (NULL, 0); + return; + } + call_in_directory (args, set_static, (char *)NULL); +} + +static void +clear_static (data, ent_list, short_pathname, filename) + char *data; + List *ent_list; + char *short_pathname; + char *filename; +{ + if (unlink_file (CVSADM_ENTSTAT) < 0 && ! existence_error (errno)) + error (1, errno, "cannot remove file %s", CVSADM_ENTSTAT); +} + +static void +handle_clear_static_directory (pathname, len) + char *pathname; + int len; +{ + if (strcmp (command_name, "export") == 0) + { + /* Swallow the repository. */ + read_line (NULL, 0); + return; + } + + if (is_cvsroot_level (pathname)) + { + /* + * Top level (directory containing CVSROOT). This seems to normally + * lack a CVS directory, so don't try to create files in it. + */ + return; + } + call_in_directory (pathname, clear_static, (char *)NULL); +} + +static void +set_sticky (data, ent_list, short_pathname, filename) + char *data; + List *ent_list; + char *short_pathname; + char *filename; +{ + char *tagspec; + FILE *f; + + read_line (&tagspec, 0); + f = open_file (CVSADM_TAG, "w+"); + if (fprintf (f, "%s\n", tagspec) < 0) + error (1, errno, "writing %s", CVSADM_TAG); + if (fclose (f) == EOF) + error (1, errno, "closing %s", CVSADM_TAG); + free (tagspec); +} + +static void +handle_set_sticky (pathname, len) + char *pathname; + int len; +{ + if (strcmp (command_name, "export") == 0) + { + /* Swallow the repository. */ + read_line (NULL, 0); + /* Swallow the tag line. */ + (void) read_line (NULL, 0); + return; + } + if (is_cvsroot_level (pathname)) + { + /* + * Top level (directory containing CVSROOT). This seems to normally + * lack a CVS directory, so don't try to create files in it. + */ + + /* Swallow the repository. */ + read_line (NULL, 0); + /* Swallow the tag line. */ + (void) read_line (NULL, 0); + return; + } + + call_in_directory (pathname, set_sticky, (char *)NULL); +} + +static void +clear_sticky (data, ent_list, short_pathname, filename) + char *data; + List *ent_list; + char *short_pathname; + char *filename; +{ + if (unlink_file (CVSADM_TAG) < 0 && ! existence_error (errno)) + error (1, errno, "cannot remove %s", CVSADM_TAG); +} + +static void +handle_clear_sticky (pathname, len) + char *pathname; + int len; +{ + if (strcmp (command_name, "export") == 0) + { + /* Swallow the repository. */ + read_line (NULL, 0); + return; + } + + if (is_cvsroot_level (pathname)) + { + /* + * Top level (directory containing CVSROOT). This seems to normally + * lack a CVS directory, so don't try to create files in it. + */ + return; + } + + call_in_directory (pathname, clear_sticky, (char *)NULL); +} + +struct save_prog { + char *name; + char *dir; + struct save_prog *next; +}; + +static struct save_prog *checkin_progs; +static struct save_prog *update_progs; + +/* + * Unlike some requests this doesn't include the repository. So we can't + * just call call_in_directory and have the right thing happen; we save up + * the requests and do them at the end. + */ +static void +handle_set_checkin_prog (args, len) + char *args; + int len; +{ + char *prog; + struct save_prog *p; + read_line (&prog, 0); + p = (struct save_prog *) xmalloc (sizeof (struct save_prog)); + p->next = checkin_progs; + p->dir = xstrdup (args); + p->name = prog; + checkin_progs = p; +} + +static void +handle_set_update_prog (args, len) + char *args; + int len; +{ + char *prog; + struct save_prog *p; + read_line (&prog, 0); + p = (struct save_prog *) xmalloc (sizeof (struct save_prog)); + p->next = update_progs; + p->dir = xstrdup (args); + p->name = prog; + update_progs = p; +} + +static void do_deferred_progs PROTO((void)); + +static void +do_deferred_progs () +{ + struct save_prog *p; + struct save_prog *q; + + char fname[PATH_MAX]; + FILE *f; + if (toplevel_wd[0] != '\0') + { + if (chdir (toplevel_wd) < 0) + error (1, errno, "could not chdir to %s", toplevel_wd); + } + for (p = checkin_progs; p != NULL; ) + { + sprintf (fname, "%s/%s", p->dir, CVSADM_CIPROG); + f = open_file (fname, "w"); + if (fprintf (f, "%s\n", p->name) < 0) + error (1, errno, "writing %s", fname); + if (fclose (f) == EOF) + error (1, errno, "closing %s", fname); + free (p->name); + free (p->dir); + q = p->next; + free (p); + p = q; + } + checkin_progs = NULL; + for (p = update_progs; p != NULL; p = p->next) + { + sprintf (fname, "%s/%s", p->dir, CVSADM_UPROG); + f = open_file (fname, "w"); + if (fprintf (f, "%s\n", p->name) < 0) + error (1, errno, "writing %s", fname); + if (fclose (f) == EOF) + error (1, errno, "closing %s", fname); + free (p->name); + free (p->dir); + free (p); + } + update_progs = NULL; +} + +static int client_isemptydir PROTO((char *)); + +/* + * Returns 1 if the argument directory exists and is completely empty, + * other than the existence of the CVS directory entry. Zero otherwise. + */ +static int +client_isemptydir (dir) + char *dir; +{ + DIR *dirp; + struct dirent *dp; + + if ((dirp = opendir (dir)) == NULL) + { + if (! existence_error (errno)) + error (0, errno, "cannot open directory %s for empty check", dir); + return (0); + } + errno = 0; + while ((dp = readdir (dirp)) != NULL) + { + if (strcmp (dp->d_name, ".") != 0 && strcmp (dp->d_name, "..") != 0 && + strcmp (dp->d_name, CVSADM) != 0) + { + (void) closedir (dirp); + return (0); + } + } + if (errno != 0) + { + error (0, errno, "cannot read directory %s", dir); + (void) closedir (dirp); + return (0); + } + (void) closedir (dirp); + return (1); +} + +struct save_dir { + char *dir; + struct save_dir *next; +}; + +struct save_dir *prune_candidates; + +static void +add_prune_candidate (dir) + char *dir; +{ + struct save_dir *p; + + if (dir[0] == '.' && dir[1] == '\0') + return; + p = (struct save_dir *) xmalloc (sizeof (struct save_dir)); + p->dir = xstrdup (dir); + p->next = prune_candidates; + prune_candidates = p; +} + +static void process_prune_candidates PROTO((void)); + +static void +process_prune_candidates () +{ + struct save_dir *p; + struct save_dir *q; + + if (toplevel_wd[0] != '\0') + { + if (chdir (toplevel_wd) < 0) + error (1, errno, "could not chdir to %s", toplevel_wd); + } + for (p = prune_candidates; p != NULL; ) + { + if (client_isemptydir (p->dir)) + { + unlink_file_dir (p->dir); + } + free (p->dir); + q = p->next; + free (p); + p = q; + } +} + +/* Send a Repository line. */ + +static char *last_repos; +static char *last_update_dir; + +static void send_repository PROTO((char *, char *, char *)); + +static void +send_repository (dir, repos, update_dir) + char *dir; + char *repos; + char *update_dir; +{ + char *adm_name; + + if (update_dir == NULL || update_dir[0] == '\0') + update_dir = "."; + + if (last_repos != NULL + && strcmp (repos, last_repos) == 0 + && last_update_dir != NULL + && strcmp (update_dir, last_update_dir) == 0) + /* We've already sent it. */ + return; + + if (client_prune_dirs) + add_prune_candidate (update_dir); + + /* 80 is large enough for any of CVSADM_*. */ + adm_name = xmalloc (strlen (dir) + 80); + + if (use_directory == -1) + use_directory = supported_request ("Directory"); + + if (use_directory) + { + if (fprintf (to_server, "Directory ") < 0) + error (1, errno, "writing to server"); + if (fprintf (to_server, "%s", update_dir) < 0) + error (1, errno, "writing to server"); + + if (fprintf (to_server, "\n%s\n", repos) + < 0) + error (1, errno, "writing to server"); + } + else + { + if (fprintf (to_server, "Repository %s\n", repos) < 0) + error (1, errno, "writing to server"); + } + if (supported_request ("Static-directory")) + { + adm_name[0] = '\0'; + if (dir[0] != '\0') + { + strcat (adm_name, dir); + strcat (adm_name, "/"); + } + strcat (adm_name, CVSADM_ENTSTAT); + if (isreadable (adm_name)) + { + if (fprintf (to_server, "Static-directory\n") < 0) + error (1, errno, "writing to server"); + } + } + if (supported_request ("Sticky")) + { + FILE *f; + if (dir[0] == '\0') + strcpy (adm_name, CVSADM_TAG); + else + sprintf (adm_name, "%s/%s", dir, CVSADM_TAG); + + f = fopen (adm_name, "r"); + if (f == NULL) + { + if (! existence_error (errno)) + error (1, errno, "reading %s", adm_name); + } + else + { + char line[80]; + char *nl; + if (fprintf (to_server, "Sticky ") < 0) + error (1, errno, "writing to server"); + while (fgets (line, sizeof (line), f) != NULL) + { + if (fprintf (to_server, "%s", line) < 0) + error (1, errno, "writing to server"); + nl = strchr (line, '\n'); + if (nl != NULL) + break; + } + if (nl == NULL) + if (fprintf (to_server, "\n") < 0) + error (1, errno, "writing to server"); + if (fclose (f) == EOF) + error (0, errno, "closing %s", adm_name); + } + } + if (supported_request ("Checkin-prog")) + { + FILE *f; + if (dir[0] == '\0') + strcpy (adm_name, CVSADM_CIPROG); + else + sprintf (adm_name, "%s/%s", dir, CVSADM_CIPROG); + + f = fopen (adm_name, "r"); + if (f == NULL) + { + if (! existence_error (errno)) + error (1, errno, "reading %s", adm_name); + } + else + { + char line[80]; + char *nl; + if (fprintf (to_server, "Checkin-prog ") < 0) + error (1, errno, "writing to server"); + while (fgets (line, sizeof (line), f) != NULL) + { + if (fprintf (to_server, "%s", line) < 0) + error (1, errno, "writing to server"); + nl = strchr (line, '\n'); + if (nl != NULL) + break; + } + if (nl == NULL) + if (fprintf (to_server, "\n") < 0) + error (1, errno, "writing to server"); + if (fclose (f) == EOF) + error (0, errno, "closing %s", adm_name); + } + } + if (supported_request ("Update-prog")) + { + FILE *f; + if (dir[0] == '\0') + strcpy (adm_name, CVSADM_UPROG); + else + sprintf (adm_name, "%s/%s", dir, CVSADM_UPROG); + + f = fopen (adm_name, "r"); + if (f == NULL) + { + if (! existence_error (errno)) + error (1, errno, "reading %s", adm_name); + } + else + { + char line[80]; + char *nl; + if (fprintf (to_server, "Update-prog ") < 0) + error (1, errno, "writing to server"); + while (fgets (line, sizeof (line), f) != NULL) + { + if (fprintf (to_server, "%s", line) < 0) + error (1, errno, "writing to server"); + nl = strchr (line, '\n'); + if (nl != NULL) + break; + } + if (nl == NULL) + if (fprintf (to_server, "\n") < 0) + error (1, errno, "writing to server"); + if (fclose (f) == EOF) + error (0, errno, "closing %s", adm_name); + } + } + if (last_repos != NULL) + free (last_repos); + if (last_update_dir != NULL) + free (last_update_dir); + last_repos = xstrdup (repos); + last_update_dir = xstrdup (update_dir); +} + +/* Send a Repository line and set toplevel_repos. */ +static void send_a_repository PROTO((char *, char *, char *)); + +static void +send_a_repository (dir, repository, update_dir) + char *dir; + char *repository; + char *update_dir; +{ + if (toplevel_repos == NULL && repository != NULL) + { + if (update_dir[0] == '\0' + || (update_dir[0] == '.' && update_dir[1] == '\0')) + toplevel_repos = xstrdup (repository); + else + { + /* + * Get the repository from a CVS/Repository file if update_dir + * is absolute. This is not correct in general, because + * the CVS/Repository file might not be the top-level one. + * This is for cases like "cvs update /foo/bar" (I'm not + * sure it matters what toplevel_repos we get, but it does + * matter that we don't hit the "internal error" code below). + */ + if (update_dir[0] == '/') + toplevel_repos = Name_Repository (update_dir, update_dir); + else + { + /* + * Guess the repository of that directory by looking at a + * subdirectory and removing as many pathname components + * as are in update_dir. I think that will always (or at + * least almost always) be 1. + * + * So this deals with directories which have been + * renamed, though it doesn't necessarily deal with + * directories which have been put inside other + * directories (and cvs invoked on the containing + * directory). I'm not sure the latter case needs to + * work. + */ + /* + * This gets toplevel_repos wrong for "cvs update ../foo" + * but I'm not sure toplevel_repos matters in that case. + */ + int slashes_in_update_dir; + int slashes_skipped; + char *p; + + slashes_in_update_dir = 0; + for (p = update_dir; *p != '\0'; ++p) + if (*p == '/') + ++slashes_in_update_dir; + + slashes_skipped = 0; + p = repository + strlen (repository); + while (1) + { + if (p == repository) + error (1, 0, + "internal error: not enough slashes in %s", + repository); + if (*p == '/') + ++slashes_skipped; + if (slashes_skipped < slashes_in_update_dir + 1) + --p; + else + break; + } + toplevel_repos = xmalloc (p - repository + 1); + /* Note that we don't copy the trailing '/'. */ + strncpy (toplevel_repos, repository, p - repository); + toplevel_repos[p - repository] = '\0'; + } + } + } + + send_repository (dir, repository, update_dir); +} + +static int modules_count; +static int modules_allocated; +static char **modules_vector; + +static void +handle_module_expansion (args, len) + char *args; + int len; +{ + if (modules_vector == NULL) + { + modules_allocated = 1; /* Small for testing */ + modules_vector = (char **) xmalloc + (modules_allocated * sizeof (modules_vector[0])); + } + else if (modules_count >= modules_allocated) + { + modules_allocated *= 2; + modules_vector = (char **) xrealloc + ((char *) modules_vector, + modules_allocated * sizeof (modules_vector[0])); + } + modules_vector[modules_count] = xmalloc (strlen (args) + 1); + strcpy (modules_vector[modules_count], args); + ++modules_count; +} + +void +client_expand_modules (argc, argv, local) + int argc; + char **argv; + int local; +{ + int errs; + int i; + + for (i = 0; i < argc; ++i) + send_arg (argv[i]); + send_a_repository ("", server_cvsroot, ""); + if (fprintf (to_server, "expand-modules\n") < 0) + error (1, errno, "writing to server"); + errs = get_server_responses (); + if (last_repos != NULL) + free (last_repos); + last_repos = NULL; + if (last_update_dir != NULL) + free (last_update_dir); + last_update_dir = NULL; + if (errs) + error (errs, 0, ""); +} + +void +client_send_expansions (local) + int local; +{ + int i; + char *argv[1]; + for (i = 0; i < modules_count; ++i) + { + argv[0] = modules_vector[i]; + if (isfile (argv[0])) + send_files (1, argv, local, 0); + else + send_file_names (1, argv); + } + send_a_repository ("", server_cvsroot, ""); +} + +void +client_nonexpanded_setup () +{ + send_a_repository ("", server_cvsroot, ""); +} + +static void +handle_m (args, len) + char *args; + int len; +{ + fwrite (args, len, sizeof (*args), stdout); + putc ('\n', stdout); +} + +static void +handle_e (args, len) + char *args; + int len; +{ + fwrite (args, len, sizeof (*args), stderr); + putc ('\n', stderr); +} + +#endif /* CLIENT_SUPPORT */ +#if defined(CLIENT_SUPPORT) || defined(SERVER_SUPPORT) + +/* This table must be writeable if the server code is included. */ +struct response responses[] = +{ +#ifdef CLIENT_SUPPORT +#define RSP_LINE(n, f, t, s) {n, f, t, s} +#else /* ! CLIENT_SUPPORT */ +#define RSP_LINE(n, f, t, s) {n, s} +#endif /* CLIENT_SUPPORT */ + + RSP_LINE("ok", handle_ok, response_type_ok, rs_essential), + RSP_LINE("error", handle_error, response_type_error, rs_essential), + RSP_LINE("Valid-requests", handle_valid_requests, response_type_normal, + rs_essential), + RSP_LINE("Checked-in", handle_checked_in, response_type_normal, + rs_essential), + RSP_LINE("New-entry", handle_new_entry, response_type_normal, rs_optional), + RSP_LINE("Checksum", handle_checksum, response_type_normal, rs_optional), + RSP_LINE("Copy-file", handle_copy_file, response_type_normal, rs_optional), + RSP_LINE("Updated", handle_updated, response_type_normal, rs_essential), + RSP_LINE("Merged", handle_merged, response_type_normal, rs_essential), + RSP_LINE("Patched", handle_patched, response_type_normal, rs_optional), + RSP_LINE("Removed", handle_removed, response_type_normal, rs_essential), + RSP_LINE("Remove-entry", handle_remove_entry, response_type_normal, + rs_optional), + RSP_LINE("Set-static-directory", handle_set_static_directory, + response_type_normal, + rs_optional), + RSP_LINE("Clear-static-directory", handle_clear_static_directory, + response_type_normal, + rs_optional), + RSP_LINE("Set-sticky", handle_set_sticky, response_type_normal, + rs_optional), + RSP_LINE("Clear-sticky", handle_clear_sticky, response_type_normal, + rs_optional), + RSP_LINE("Set-checkin-prog", handle_set_checkin_prog, response_type_normal, + rs_optional), + RSP_LINE("Set-update-prog", handle_set_update_prog, response_type_normal, + rs_optional), + RSP_LINE("Module-expansion", handle_module_expansion, response_type_normal, + rs_optional), + RSP_LINE("M", handle_m, response_type_normal, rs_essential), + RSP_LINE("E", handle_e, response_type_normal, rs_essential), + /* Possibly should be response_type_error. */ + RSP_LINE(NULL, NULL, response_type_normal, rs_essential) + +#undef RSP_LINE +}; + +#endif /* CLIENT_SUPPORT or SERVER_SUPPORT */ +#ifdef CLIENT_SUPPORT + +/* + * Get some server responses and process them. Returns nonzero for + * error, 0 for success. + */ +int +get_server_responses () +{ + struct response *rs; + do + { + char *cmd; + int len; + + len = read_line (&cmd, 0); + for (rs = responses; rs->name != NULL; ++rs) + if (strncmp (cmd, rs->name, strlen (rs->name)) == 0) + { + int cmdlen = strlen (rs->name); + if (cmd[cmdlen] == '\0') + ; + else if (cmd[cmdlen] == ' ') + ++cmdlen; + else + /* + * The first len characters match, but it's a different + * response. e.g. the response is "oklahoma" but we + * matched "ok". + */ + continue; + (*rs->func) (cmd + cmdlen, len - cmdlen); + break; + } + if (rs->name == NULL) + /* It's OK to print just to the first '\0'. */ + error (0, 0, + "warning: unrecognized response `%s' from cvs server", + cmd); + free (cmd); + } while (rs->type == response_type_normal); + return rs->type == response_type_error ? 1 : 0; +} + +/* Get the responses and then close the connection. */ +int server_fd = -1; + +int +get_responses_and_close () +{ + int errs = get_server_responses (); + + do_deferred_progs (); + + if (client_prune_dirs) + process_prune_candidates (); + +#if defined(HAVE_KERBEROS) || defined(AUTH_CLIENT_SUPPORT) + if (server_fd != -1) + { + if (shutdown (server_fd, 1) < 0) + error (1, errno, "shutting down connection to %s", server_host); + /* + * In this case, both sides of the net connection will use the + * same fd. + */ + if (fileno (from_server) != fileno (to_server)) + { + if (fclose (to_server) != 0) + error (1, errno, "closing down connection to %s", server_host); + } + } + else +#endif /* HAVE_KERBEROS || AUTH_CLIENT_SUPPORT */ + +#ifdef SHUTDOWN_SERVER + SHUTDOWN_SERVER (fileno (to_server)); +#else /* ! SHUTDOWN_SERVER */ + { + +#ifdef START_RSH_WITH_POPEN_RW + if (pclose (to_server) == EOF) +#else /* ! START_RSH_WITH_POPEN_RW */ + if (fclose (to_server) == EOF) +#endif /* START_RSH_WITH_POPEN_RW */ + { + error (1, errno, "closing connection to %s", server_host); + } + } + + if (getc (from_server) != EOF) + error (0, 0, "dying gasps from %s unexpected", server_host); + else if (ferror (from_server)) + error (0, errno, "reading from %s", server_host); + + fclose (from_server); +#endif /* SHUTDOWN_SERVER */ + +#if ! RSH_NOT_TRANSPARENT + if (rsh_pid != -1 + && waitpid (rsh_pid, (int *) 0, 0) == -1) + if (errno != ECHILD) + error (1, errno, "waiting for process %d", rsh_pid); +#endif /* ! RSH_NOT_TRANSPARENT */ + + return errs; +} + +#ifndef RSH_NOT_TRANSPARENT +static void start_rsh_server PROTO((int *, int *)); +#endif /* RSH_NOT_TRANSPARENT */ + +int +supported_request (name) + char *name; +{ + struct request *rq; + + for (rq = requests; rq->name; rq++) + if (!strcmp (rq->name, name)) + return rq->status == rq_supported; + error (1, 0, "internal error: testing support for unknown option?"); +} + + +#ifdef AUTH_CLIENT_SUPPORT +void +init_sockaddr (name, hostname, port) + struct sockaddr_in *name; + const char *hostname; + unsigned short int port; +{ + struct hostent *hostinfo; + + name->sin_family = AF_INET; + name->sin_port = htons (port); + hostinfo = gethostbyname (hostname); + if (hostinfo == NULL) + { + fprintf (stderr, "Unknown host %s.\n", hostname); + exit (EXIT_FAILURE); + } + name->sin_addr = *(struct in_addr *) hostinfo->h_addr; +} + +void +connect_to_pserver (tofdp, fromfdp, log) + int *tofdp, *fromfdp; + char *log; +{ + int sock; + int tofd, fromfd; + struct hostent *host; + struct sockaddr_in client_sai; + + sock = socket (AF_INET, SOCK_STREAM, 0); + if (sock == -1) + { + fprintf (stderr, "socket() failed\n"); + exit (1); + } + init_sockaddr (&client_sai, server_host, CVS_AUTH_PORT); + connect (sock, (struct sockaddr *) &client_sai, sizeof (client_sai)); + + /* Run the authorization mini-protocol before anything else. */ + { + int i; + char ch, read_buf[PATH_MAX]; + char *begin = "BEGIN AUTH REQUEST\n"; + char *repository = server_cvsroot; + char *username = server_user; + char *password = NULL; + char *end = "END AUTH REQUEST\n"; + + /* Get the password, probably from ~/.cvspass. */ + password = get_cvs_password (server_user, server_host, server_cvsroot); + + /* Announce that we're starting the authorization protocol. */ + write (sock, begin, strlen (begin)); + + /* Send the data the server needs. */ + write (sock, repository, strlen (repository)); + write (sock, "\n", 1); + write (sock, username, strlen (username)); + write (sock, "\n", 1); + write (sock, password, strlen (password)); + write (sock, "\n", 1); + + /* Announce that we're ending the authorization protocol. */ + write (sock, end, strlen (end)); + + /* Paranoia. */ + memset (password, 0, strlen (password)); + + /* Get ACK or NACK from the server. + * + * We could avoid this careful read-char loop by having the ACK + * and NACK cookies be of the same length, so we'd simply read + * that length and see what we got. But then there'd be Yet + * Another Protocol Requirement floating around, and someday + * someone would make a change that breaks it and spend a hellish + * day tracking it down. Therefore, we use "\n" to mark off the + * end of both ACK and NACK, and we read until "\n". + */ + ch = 0; + memset (read_buf, 0, PATH_MAX); + for (i = 0; (i < (PATH_MAX - 1)) && (ch != '\n'); i++) + { + read (sock, &ch, 1); + read_buf[i] = ch; + } + + if (strcmp (read_buf, "I HATE YOU\n") == 0) + { + /* Authorization not granted. */ + if (shutdown (sock, 2) < 0) + error (1, errno, "shutdown() failed (server %s)", server_host); + error (1, 0, + "authorization failed: server %s rejected access", + server_host); + } + else if (strcmp (read_buf, "I LOVE YOU\n") != 0) + { + /* Unrecognized response from server. */ + if (shutdown (sock, 2) < 0) + error (1, errno, "shutdown() failed (server %s)", server_host); + error (1, 0, + "unrecognized auth response from %s: %s", + server_host, read_buf); + } + /* Else authorization granted, so we can go on... */ + } + + /* This was stolen straight from start_kerberos_server(). */ + { + server_fd = sock; + close_on_exec (server_fd); + /* + * If we do any filtering, TOFD and FROMFD will be + * closed. So make sure they're copies of SERVER_FD, + * and not the same fd number. + */ + if (log) + { + tofd = dup (sock); + fromfd = dup (sock); + } + else + tofd = fromfd = sock; + } + + /* Hand them back to the caller. */ + *tofdp = tofd; + *fromfdp = fromfd; +} +#endif /* AUTH_CLIENT_SUPPORT */ + + +#if HAVE_KERBEROS +void +start_kerberos_server (tofdp, fromfdp, log) + int *tofdp, *fromfdp; + char *log; +{ + int tofd, fromfd; + + struct hostent *hp; + char *hname; + const char *realm; + const char *portenv; + int port; + struct sockaddr_in sin; + int s; + KTEXT_ST ticket; + int status; + + /* + * We look up the host to give a better error message if it + * does not exist. However, we then pass server_host to + * krb_sendauth, rather than the canonical name, because + * krb_sendauth is going to do its own canonicalization anyhow + * and that lets us not worry about the static storage used by + * gethostbyname. + */ + hp = gethostbyname (server_host); + if (hp == NULL) + error (1, 0, "%s: unknown host", server_host); + hname = xmalloc (strlen (hp->h_name) + 1); + strcpy (hname, hp->h_name); + + realm = krb_realmofhost (hname); + + portenv = getenv ("CVS_CLIENT_PORT"); + if (portenv != NULL) + { + port = atoi (portenv); + if (port <= 0) + goto try_rsh_no_message; + port = htons (port); + } + else + { + struct servent *sp; + + sp = getservbyname ("cvs", "tcp"); + if (sp == NULL) + port = htons (CVS_PORT); + else + port = sp->s_port; + } + + s = socket (AF_INET, SOCK_STREAM, 0); + if (s < 0) + error (1, errno, "socket"); + + memset (&sin, 0, sizeof sin); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = INADDR_ANY; + sin.sin_port = 0; + + if (bind (s, (struct sockaddr *) &sin, sizeof sin) < 0) + error (1, errno, "bind"); + + memcpy (&sin.sin_addr, hp->h_addr, hp->h_length); + sin.sin_port = port; + + tofd = -1; + if (connect (s, (struct sockaddr *) &sin, sizeof sin) < 0) + { + error (0, errno, "connect"); + close (s); + } + else + { + struct sockaddr_in laddr; + int laddrlen; + MSG_DAT msg_data; + CREDENTIALS cred; + Key_schedule sched; + + laddrlen = sizeof (laddr); + if (getsockname (s, (struct sockaddr *) &laddr, &laddrlen) < 0) + error (1, errno, "getsockname"); + + /* We don't care about the checksum, and pass it as zero. */ + status = krb_sendauth (KOPT_DO_MUTUAL, s, &ticket, "rcmd", + hname, realm, (unsigned long) 0, &msg_data, + &cred, sched, &laddr, &sin, "KCVSV1.0"); + if (status != KSUCCESS) + { + error (0, 0, "kerberos: %s", krb_get_err_text(status)); + close (s); + } + else + { + server_fd = s; + close_on_exec (server_fd); + /* + * If we do any filtering, TOFD and FROMFD will be + * closed. So make sure they're copies of SERVER_FD, + * and not the same fd number. + */ + if (log) + { + tofd = dup (s); + fromfd = dup (s); + } + else + tofd = fromfd = s; + } + } + + if (tofd == -1) + { + error (0, 0, "trying to start server using rsh"); + try_rsh_no_message: + server_fd = -1; +#if ! RSH_NOT_TRANSPARENT + start_rsh_server (&tofd, &fromfd); +#else /* RSH_NOT_TRANSPARENT */ +#if defined (START_SERVER) + START_SERVER (&tofd, &fromfd, getcaller (), + server_user, server_host, server_cvsroot); +#endif /* defined (START_SERVER) */ +#endif /* ! RSH_NOT_TRANSPARENT */ + } + free (hname); + + /* Give caller the values it wants. */ + *tofdp = tofd; + *fromfdp = fromfd; +} + +#endif /* HAVE_KERBEROS */ + +/* Contact the server. */ +void +start_server () +{ + int tofd, fromfd; + char *log = getenv ("CVS_CLIENT_LOG"); + +#if HAVE_KERBEROS + start_kerberos_server (&tofd, &fromfd, log); + +#else /* ! HAVE_KERBEROS */ + +#ifdef AUTH_CLIENT_SUPPORT + if (use_authenticating_server) + { + connect_to_pserver (&tofd, &fromfd, log); + } + else +#endif /* AUTH_CLIENT_SUPPORT */ + { +#if ! RSH_NOT_TRANSPARENT + start_rsh_server (&tofd, &fromfd); +#else /* RSH_NOT_TRANSPARENT */ + +#if defined(START_SERVER) + START_SERVER (&tofd, &fromfd, getcaller (), + server_user, server_host, server_cvsroot); +#endif /* defined(START_SERVER) */ +#endif /* ! RSH_NOT_TRANSPARENT */ + } +#endif /* HAVE_KERBEROS */ + + /* todo: some OS's don't need these calls... */ + close_on_exec (tofd); + close_on_exec (fromfd); + + if (log) + { + int len = strlen (log); + char *buf = xmalloc (5 + len); + char *p; + static char *teeprog[3] = { "tee" }; + + teeprog[1] = buf; + strcpy (buf, log); + p = buf + len; + + strcpy (p, ".in"); + tofd = filter_stream_through_program (tofd, 0, teeprog, 0); + + strcpy (p, ".out"); + fromfd = filter_stream_through_program (fromfd, 1, teeprog, 0); + + free (buf); + } + + /* These will use binary mode on systems which have it. */ + to_server = fdopen (tofd, FOPEN_BINARY_WRITE); + if (to_server == NULL) + error (1, errno, "cannot fdopen %d for write", tofd); + from_server = fdopen (fromfd, FOPEN_BINARY_READ); + if (from_server == NULL) + error (1, errno, "cannot fdopen %d for read", fromfd); + + /* Clear static variables. */ + if (toplevel_repos != NULL) + free (toplevel_repos); + toplevel_repos = NULL; + if (last_dirname != NULL) + free (last_dirname); + last_dirname = NULL; + if (last_repos != NULL) + free (last_repos); + last_repos = NULL; + if (last_update_dir != NULL) + free (last_update_dir); + last_update_dir = NULL; + stored_checksum_valid = 0; + + if (fprintf (to_server, "Root %s\n", server_cvsroot) < 0) + error (1, errno, "writing to server"); + { + struct response *rs; + if (fprintf (to_server, "Valid-responses") < 0) + error (1, errno, "writing to server"); + for (rs = responses; rs->name != NULL; ++rs) + { + if (fprintf (to_server, " %s", rs->name) < 0) + error (1, errno, "writing to server"); + } + if (fprintf (to_server, "\n") < 0) + error (1, errno, "writing to server"); + } + if (fprintf (to_server, "valid-requests\n") < 0) + error (1, errno, "writing to server"); + if (get_server_responses ()) + exit (1); + + /* + * Now handle global options. + * + * -H, -f, -d, -e should be handled OK locally. + * + * -b we ignore (treating it as a server installation issue). + * FIXME: should be an error message. + * + * -v we print local version info; FIXME: Add a protocol request to get + * the version from the server so we can print that too. + * + * -l -t -r -w -q -n and -Q need to go to the server. + */ + + { + int have_global = supported_request ("Global_option"); + + if (noexec) + { + if (have_global) + { + if (fprintf (to_server, "Global_option -n\n") < 0) + error (1, errno, "writing to server"); + } + else + error (1, 0, + "This server does not support the global -n option."); + } + if (quiet) + { + if (have_global) + { + if (fprintf (to_server, "Global_option -q\n") < 0) + error (1, errno, "writing to server"); + } + else + error (1, 0, + "This server does not support the global -q option."); + } + if (really_quiet) + { + if (have_global) + { + if (fprintf (to_server, "Global_option -Q\n") < 0) + error (1, errno, "writing to server"); + } + else + error (1, 0, + "This server does not support the global -Q option."); + } + if (!cvswrite) + { + if (have_global) + { + if (fprintf (to_server, "Global_option -r\n") < 0) + error (1, errno, "writing to server"); + } + else + error (1, 0, + "This server does not support the global -r option."); + } + if (trace) + { + if (have_global) + { + if (fprintf (to_server, "Global_option -t\n") < 0) + error (1, errno, "writing to server"); + } + else + error (1, 0, + "This server does not support the global -t option."); + } + if (logoff) + { + if (have_global) + { + if (fprintf (to_server, "Global_option -l\n") < 0) + error (1, errno, "writing to server"); + } + else + error (1, 0, + "This server does not support the global -l option."); + } + } + if (gzip_level) + { + if (supported_request ("gzip-file-contents")) + { + if (fprintf (to_server, "gzip-file-contents %d\n", gzip_level) < 0) + error (1, 0, "writing to server"); + } + else + { + fprintf (stderr, "server doesn't support gzip-file-contents\n"); + gzip_level = 0; + } + } +} + +#ifndef RSH_NOT_TRANSPARENT +/* Contact the server by starting it with rsh. */ + +/* Right now, we have two different definitions for this function, + depending on whether we start the rsh server using popenRW or not. + This isn't ideal, and the best thing would probably be to change + the OS/2 port to be more like the regular Unix client (i.e., by + implementing piped_child)... but I'm doing something else at the + moment, and wish to make only one change at a time. -Karl */ + +#ifdef START_RSH_WITH_POPEN_RW + +static void +start_rsh_server (tofdp, fromfdp) + int *tofdp, *fromfdp; +{ + int pipes[2]; + + /* If you're working through firewalls, you can set the + CVS_RSH environment variable to a script which uses rsh to + invoke another rsh on a proxy machine. */ + char *cvs_rsh = getenv ("CVS_RSH"); + char *cvs_server = getenv ("CVS_SERVER"); + char command[PATH_MAX]; + int i = 0; + /* This needs to fit "rsh", "-b", "-l", "USER", "host", + "cmd (w/ args)", and NULL. We leave some room to grow. */ + char *rsh_argv[10]; + + if (!cvs_rsh) + cvs_rsh = "rsh"; + if (!cvs_server) + cvs_server = "cvs"; + + /* If you are running a very old (Nov 3, 1994, before 1.5) + * version of the server, you need to make sure that your .bashrc + * on the server machine does not set CVSROOT to something + * containing a colon (or better yet, upgrade the server). */ + + /* The command line starts out with rsh. */ + rsh_argv[i++] = cvs_rsh; + + /* "-b" for binary, under OS/2. */ + rsh_argv[i++] = "-b"; + + /* Then we strcat more things on the end one by one. */ + if (server_user != NULL) + { + rsh_argv[i++] = "-l"; + rsh_argv[i++] = server_user; + } + + rsh_argv[i++] = server_host; + rsh_argv[i++] = cvs_server; + rsh_argv[i++] = "server"; + + /* Mark the end of the arg list. */ + rsh_argv[i] = (char *) NULL; + + if (trace) + { + fprintf (stderr, " -> Starting server: "); + fprintf (stderr, "%s", command); + putc ('\n', stderr); + } + + /* Do the deed. */ + rsh_pid = popenRW (rsh_argv, pipes); + if (rsh_pid < 0) + error (1, errno, "cannot start server via rsh"); + + /* Give caller the file descriptors. */ + *tofdp = pipes[0]; + *fromfdp = pipes[1]; +} + +#else /* ! START_RSH_WITH_POPEN_RW */ + +static void +start_rsh_server (tofdp, fromfdp) + int *tofdp; + int *fromfdp; +{ + /* If you're working through firewalls, you can set the + CVS_RSH environment variable to a script which uses rsh to + invoke another rsh on a proxy machine. */ + char *cvs_rsh = getenv ("CVS_RSH"); + char *cvs_server = getenv ("CVS_SERVER"); + char *command; + + if (!cvs_rsh) + cvs_rsh = "rsh"; + if (!cvs_server) + cvs_server = "cvs"; + + /* Pass the command to rsh as a single string. This shouldn't + affect most rsh servers at all, and will pacify some buggy + versions of rsh that grab switches out of the middle of the + command (they're calling the GNU getopt routines incorrectly). */ + command = xmalloc (strlen (cvs_server) + + strlen (server_cvsroot) + + 50); + + /* If you are running a very old (Nov 3, 1994, before 1.5) + * version of the server, you need to make sure that your .bashrc + * on the server machine does not set CVSROOT to something + * containing a colon (or better yet, upgrade the server). */ + sprintf (command, "%s server", cvs_server); + + { + char *argv[10]; + char **p = argv; + + *p++ = cvs_rsh; + *p++ = server_host; + + /* If the login names differ between client and server + * pass it on to rsh. + */ + if (server_user != NULL) + { + *p++ = "-l"; + *p++ = server_user; + } + + *p++ = command; + *p++ = NULL; + + if (trace) + { + int i; + + fprintf (stderr, " -> Starting server: "); + for (i = 0; argv[i]; i++) + fprintf (stderr, "%s ", argv[i]); + putc ('\n', stderr); + } + rsh_pid = piped_child (argv, tofdp, fromfdp); + + if (rsh_pid < 0) + error (1, errno, "cannot start server via rsh"); + } +} + +#endif /* START_RSH_WITH_POPEN_RW */ +#endif /* ! RSH_NOT_TRANSPARENT */ + + + +/* Send an argument STRING. */ +void +send_arg (string) + char *string; +{ + char *p = string; + if (fprintf (to_server, "Argument ") < 0) + error (1, errno, "writing to server"); + while (*p) + { + if (*p == '\n') + { + if (fprintf (to_server, "\nArgumentx ") < 0) + error (1, errno, "writing to server"); + } + else if (putc (*p, to_server) == EOF) + error (1, errno, "writing to server"); + ++p; + } + if (putc ('\n', to_server) == EOF) + error (1, errno, "writing to server"); +} + +static void send_modified PROTO ((char *, char *, Vers_TS *)); + +static void +send_modified (file, short_pathname, vers) + char *file; + char *short_pathname; + Vers_TS *vers; +{ + /* File was modified, send it. */ + struct stat sb; + int fd; + char *buf; + char *mode_string; + int bufsize; + int bin = 0; + + /* Don't think we can assume fstat exists. */ + if (stat (file, &sb) < 0) + error (1, errno, "reading %s", short_pathname); + + mode_string = mode_to_string (sb.st_mode); + + /* Beware: on systems using CRLF line termination conventions, + the read and write functions will convert CRLF to LF, so the + number of characters read is not the same as sb.st_size. Text + files should always be transmitted using the LF convention, so + we don't want to disable this conversion. */ + bufsize = sb.st_size; + buf = xmalloc (bufsize); + + /* Is the file marked as containing binary data by the "-kb" flag? + If so, make sure to open it in binary mode: */ + + bin = !(strcmp (vers->options, "-kb")); + fd = open (file, O_RDONLY | (bin ? OPEN_BINARY : 0)); + + if (fd < 0) + error (1, errno, "reading %s", short_pathname); + + if (gzip_level && sb.st_size > 100) + { + int nread, newsize = 0, gzip_status; + pid_t gzip_pid; + char *bufp = buf; + int readsize = 8192; +#ifdef LINES_CRLF_TERMINATED + char tempfile[L_tmpnam]; + int converting; +#endif /* LINES_CRLF_TERMINATED */ + +#ifdef LINES_CRLF_TERMINATED + /* Assume everything in a "cvs import" is text. */ + if (vers == NULL) + converting = 1; + else + /* Otherwise, we convert things unless they're binary. */ + converting = (! bin); + + if (converting) + { + /* gzip reads and writes files without munging CRLF + sequences, as it should, but files should be + transmitted in LF form. Convert CRLF to LF before + gzipping, on systems where this is necessary. + + If Windows NT supported fork, we could do this by + pushing another filter on in front of gzip. But it + doesn't. I'd have to write a trivial little program to + do the conversion and have CVS spawn it off. But + little executables like that always get lost. + + Alternatively, this cruft could go away if we switched + to a gzip library instead of a subprocess; then we + could tell gzip to open the file with CRLF translation + enabled. */ + if (close (fd) < 0) + error (0, errno, "warning: can't close %s", short_pathname); + + tmpnam (tempfile); + convert_file (file, O_RDONLY, + tempfile, + O_WRONLY | O_CREAT | O_TRUNC | OPEN_BINARY); + + /* This OPEN_BINARY doesn't make any difference, I think, because + gzip will deal with the inherited handle as it pleases. But I + do remember something obscure in the manuals about propagating + the translation mode to created processes via environment + variables, ick. */ + fd = open (tempfile, O_RDONLY | OPEN_BINARY); + if (fd < 0) + error (1, errno, "reading %s", short_pathname); + } +#endif /* LINES_CRLF_TERMINATED */ + + fd = filter_through_gzip (fd, 1, gzip_level, &gzip_pid); + while (1) + { + if ((bufp - buf) + readsize >= bufsize) + { + /* + * We need to expand the buffer if gzip ends up expanding + * the file. + */ + newsize = bufp - buf; + while (newsize + readsize >= bufsize) + bufsize *= 2; + buf = xrealloc (buf, bufsize); + bufp = buf + newsize; + } + nread = read (fd, bufp, readsize); + if (nread < 0) + error (1, errno, "reading from gzip pipe"); + else if (nread == 0) + /* eof */ + break; + bufp += nread; + } + newsize = bufp - buf; + if (close (fd) < 0) + error (0, errno, "warning: can't close %s", short_pathname); + + if (waitpid (gzip_pid, &gzip_status, 0) != gzip_pid) + error (1, errno, "waiting for gzip proc %d", gzip_pid); + else if (gzip_status != 0) + error (1, errno, "gzip exited %d", gzip_status); + +#if LINES_CRLF_TERMINATED + if (converting) + { + if (unlink (tempfile) < 0) + error (0, errno, + "warning: can't remove temp file %s", tempfile); + } +#endif /* LINES_CRLF_TERMINATED */ + + fprintf (to_server, "Modified %s\n%s\nz%lu\n", file, mode_string, + (unsigned long) newsize); + fwrite (buf, newsize, 1, to_server); + if (feof (to_server) || ferror (to_server)) + error (1, errno, "writing to server"); + } + else + { + int newsize; + + { + char *bufp = buf; + int len; + + while ((len = read (fd, bufp, (buf + sb.st_size) - bufp)) > 0) + bufp += len; + + if (len < 0) + error (1, errno, "reading %s", short_pathname); + + newsize = bufp - buf; + } + if (close (fd) < 0) + error (0, errno, "warning: can't close %s", short_pathname); + + if (fprintf (to_server, "Modified %s\n%s\n%lu\n", file, + mode_string, (unsigned long) newsize) < 0) + error (1, errno, "writing to server"); + + /* + * Note that this only ends with a newline if the file ended with + * one. + */ + if (newsize > 0) + if (fwrite (buf, newsize, 1, to_server) != 1) + error (1, errno, "writing to server"); + } + free (buf); + free (mode_string); +} + +/* Deal with one file. */ +static int +send_fileproc (file, update_dir, repository, entries, srcfiles) + char *file; + char *update_dir; + char *repository; + List *entries; + List *srcfiles; +{ + Vers_TS *vers; + int update_dir_len = strlen (update_dir); + char *short_pathname = xmalloc (update_dir_len + strlen (file) + 40); + strcpy (short_pathname, update_dir); + if (update_dir[0] != '\0') + strcat (short_pathname, "/"); + strcat (short_pathname, file); + + send_a_repository ("", repository, update_dir); + + vers = Version_TS ((char *)NULL, (char *)NULL, (char *)NULL, + (char *)NULL, + file, 0, 0, entries, (List *)NULL); + + if (vers->vn_user != NULL) + { + /* The Entries request. */ + /* Not sure about whether this deals with -k and stuff right. */ + if (fprintf (to_server, "Entry /%s/%s/%s%s/%s/", file, vers->vn_user, + vers->ts_conflict == NULL ? "" : "+", + (vers->ts_conflict == NULL ? "" + : (vers->ts_user != NULL && + strcmp (vers->ts_conflict, vers->ts_user) == 0 + ? "=" + : "modified")), + vers->options) < 0) + error (1, errno, "writing to server"); + if (vers->entdata != NULL && vers->entdata->tag) + { + if (fprintf (to_server, "T%s", vers->entdata->tag) < 0) + error (1, errno, "writing to server"); + } + else if (vers->entdata != NULL && vers->entdata->date) + if (fprintf (to_server, "D%s", vers->entdata->date) < 0) + error (1, errno, "writing to server"); + if (fprintf (to_server, "\n") < 0) + error (1, errno, "writing to server"); + } + + if (vers->ts_user == NULL) + { + /* + * Do we want to print "file was lost" like normal CVS? + * Would it always be appropriate? + */ + /* File no longer exists. */ + if (!use_unchanged) + { + /* if the server is old, use the old request... */ + if (fprintf (to_server, "Lost %s\n", file) < 0) + error (1, errno, "writing to server"); + /* + * Otherwise, don't do anything for missing files, + * they just happen. + */ + } + } + else if (vers->ts_rcs == NULL + || strcmp (vers->ts_user, vers->ts_rcs) != 0) + { + send_modified (file, short_pathname, vers); + } + else + { + /* Only use this request if the server supports it... */ + if (use_unchanged) + if (fprintf (to_server, "Unchanged %s\n", file) < 0) + error (1, errno, "writing to server"); + } + + /* if this directory has an ignore list, add this file to it */ + if (ignlist) + { + Node *p; + + p = getnode (); + p->type = FILES; + p->key = xstrdup (file); + (void) addnode (ignlist, p); + } + + freevers_ts (&vers); + free (short_pathname); + return 0; +} + +/* + * send_dirent_proc () is called back by the recursion processor before a + * sub-directory is processed for update. + * A return code of 0 indicates the directory should be + * processed by the recursion code. A return of non-zero indicates the + * recursion code should skip this directory. + * + */ +static Dtype +send_dirent_proc (dir, repository, update_dir) + char *dir; + char *repository; + char *update_dir; +{ + int dir_exists; + char *cvsadm_repos_name; + + /* + * If the directory does not exist yet (e.g. "cvs update -d + * foo"), no need to send any files from it. + */ + dir_exists = isdir (dir); + + if (ignore_directory (update_dir)) + { + /* print the warm fuzzy message */ + if (!quiet) + error (0, 0, "Ignoring %s", update_dir); + return (R_SKIP_ALL); + } + + /* initialize the ignore list for this directory */ + ignlist = getlist (); + + /* + * If there is an empty directory (e.g. we are doing `cvs add' on a + * newly-created directory), the server still needs to know about it. + */ + + cvsadm_repos_name = xmalloc (strlen (dir) + sizeof (CVSADM_REP) + 80); + sprintf (cvsadm_repos_name, "%s/%s", dir, CVSADM_REP); + if (dir_exists && isreadable (cvsadm_repos_name)) + { + /* + * Get the repository from a CVS/Repository file whenever possible. + * The repository variable is wrong if the names in the local + * directory don't match the names in the repository. + */ + char *repos = Name_Repository (dir, update_dir); + send_a_repository (dir, repos, update_dir); + free (repos); + } + else + send_a_repository (dir, repository, update_dir); + free (cvsadm_repos_name); + + return (dir_exists ? R_PROCESS : R_SKIP_ALL); +} + +/* + * Send each option in a string to the server, one by one. + * This assumes that the options are single characters. For + * more complex parsing, do it yourself. + */ + +void +send_option_string (string) + char *string; +{ + char *p; + char it[3]; + + for (p = string; p[0]; p++) { + if (p[0] == ' ') + continue; + if (p[0] == '-') + continue; + it[0] = '-'; + it[1] = p[0]; + it[2] = '\0'; + send_arg (it); + } +} + + +/* Send the names of all the argument files to the server. */ + +void +send_file_names (argc, argv) + int argc; + char **argv; +{ + int i; + char *p; + char *q; + int level; + int max_level; + + /* Send Max-dotdot if needed. */ + max_level = 0; + for (i = 0; i < argc; ++i) + { + p = argv[i]; + level = 0; + do + { + q = strchr (p, '/'); + if (q != NULL) + ++q; + if (p[0] == '.' && p[1] == '.' && (p[2] == '\0' || p[2] == '/')) + { + --level; + if (-level > max_level) + max_level = -level; + } + else if (p[0] == '.' && (p[1] == '\0' || p[1] == '/')) + ; + else + ++level; + p = q; + } while (p != NULL); + } + if (max_level > 0) + { + if (supported_request ("Max-dotdot")) + { + if (fprintf (to_server, "Max-dotdot %d\n", max_level) < 0) + error (1, errno, "writing to server"); + } + else + /* + * "leading .." is not strictly correct, as this also includes + * cases like "foo/../..". But trying to explain that in the + * error message would probably just confuse users. + */ + error (1, 0, + "leading .. not supported by old (pre-Max-dotdot) servers"); + } + + for (i = 0; i < argc; ++i) + send_arg (argv[i]); +} + + +/* + * Send Repository, Modified and Entry. argc and argv contain only + * the files to operate on (or empty for everything), not options. + * local is nonzero if we should not recurse (-l option). Also sends + * Argument lines for argc and argv, so should be called after options + * are sent. + */ +void +send_files (argc, argv, local, aflag) + int argc; + char **argv; + int local; + int aflag; +{ + int err; + + send_file_names (argc, argv); + + /* + * aflag controls whether the tag/date is copied into the vers_ts. + * But we don't actually use it, so I don't think it matters what we pass + * for aflag here. + */ + err = start_recursion + (send_fileproc, update_filesdone_proc, + send_dirent_proc, (DIRLEAVEPROC)NULL, + argc, argv, local, W_LOCAL, aflag, 0, (char *)NULL, 0, 0); + if (err) + exit (1); + if (toplevel_repos == NULL) + /* + * This happens if we are not processing any files, + * or for checkouts in directories without any existing stuff + * checked out. The following assignment is correct for the + * latter case; I don't think toplevel_repos matters for the + * former. + */ + toplevel_repos = xstrdup (server_cvsroot); + send_repository ("", toplevel_repos, "."); +} + +void +client_import_setup (repository) + char *repository; +{ + if (toplevel_repos == NULL) /* should always be true */ + send_a_repository ("", repository, ""); +} + +/* + * Process the argument import file. + */ +int +client_process_import_file (message, vfile, vtag, targc, targv, repository) + char *message; + char *vfile; + char *vtag; + int targc; + char *targv[]; + char *repository; +{ + char *short_pathname; + int first_time; + + /* FIXME: I think this is always false now that we call + client_import_setup at the start. */ + + first_time = toplevel_repos == NULL; + + if (first_time) + send_a_repository ("", repository, ""); + + if (strncmp (repository, toplevel_repos, strlen (toplevel_repos)) != 0) + error (1, 0, + "internal error: pathname `%s' doesn't specify file in `%s'", + repository, toplevel_repos); + short_pathname = repository + strlen (toplevel_repos) + 1; + + if (!first_time) + { + send_a_repository ("", repository, short_pathname); + } + send_modified (vfile, short_pathname, NULL); + return 0; +} + +void +client_import_done () +{ + if (toplevel_repos == NULL) + /* + * This happens if we are not processing any files, + * or for checkouts in directories without any existing stuff + * checked out. The following assignment is correct for the + * latter case; I don't think toplevel_repos matters for the + * former. + */ + /* FIXME: "can't happen" now that we call client_import_setup + at the beginning. */ + toplevel_repos = xstrdup (server_cvsroot); + send_repository ("", toplevel_repos, "."); +} + +/* + * Send an option with an argument, dealing correctly with newlines in + * the argument. If ARG is NULL, forget the whole thing. + */ +void +option_with_arg (option, arg) + char *option; + char *arg; +{ + if (arg == NULL) + return; + if (fprintf (to_server, "Argument %s\n", option) < 0) + error (1, errno, "writing to server"); + send_arg (arg); +} + +/* + * Send a date to the server. This will passed a string which is the + * result of Make_Date, and looks like YY.MM.DD.HH.MM.SS, where all + * the letters are single digits. The time will be GMT. getdate on + * the server can't parse that, so we turn it back into something + * which it can parse. + */ + +void +client_senddate (date) + const char *date; +{ + int year, month, day, hour, minute, second; + char buf[100]; + + if (sscanf (date, DATEFORM, &year, &month, &day, &hour, &minute, &second) + != 6) + { + error (1, 0, "diff_client_senddate: sscanf failed on date"); + } + +#ifndef HAVE_RCS5 + /* We need to fix the timezone in this case; see Make_Date. */ + abort (); +#endif /* HAVE_RCS5 */ + + sprintf (buf, "%d/%d/%d %d:%d:%d GMT", month, day, year, + hour, minute, second); + option_with_arg ("-D", buf); +} + +int +client_commit (argc, argv) + int argc; + char **argv; +{ + parse_cvsroot (); + + return commit (argc, argv); +} + +int +client_update (argc, argv) + int argc; + char **argv; +{ + parse_cvsroot (); + + return update (argc, argv); +} + +int +client_checkout (argc, argv) + int argc; + char **argv; +{ + parse_cvsroot (); + + return checkout (argc, argv); +} + +int +client_diff (argc, argv) + int argc; + char **argv; +{ + parse_cvsroot (); + + return diff (argc, argv); /* Call real code */ +} + +int +client_status (argc, argv) + int argc; + char **argv; +{ + parse_cvsroot (); + return status (argc, argv); +} + +int +client_log (argc, argv) + int argc; + char **argv; +{ + + parse_cvsroot (); + + return cvslog (argc, argv); /* Call real code */ +} + +int +client_add (argc, argv) + int argc; + char **argv; +{ + + parse_cvsroot (); + + return add (argc, argv); /* Call real code */ +} + +int +client_remove (argc, argv) + int argc; + char **argv; +{ + + parse_cvsroot (); + + return cvsremove (argc, argv); /* Call real code */ +} + +int +client_rdiff (argc, argv) + int argc; + char **argv; +{ + + parse_cvsroot (); + + return patch (argc, argv); /* Call real code */ +} + +int +client_tag (argc, argv) + int argc; + char **argv; +{ + + parse_cvsroot (); + + return tag (argc, argv); /* Call real code */ +} + +int +client_rtag (argc, argv) + int argc; + char **argv; +{ + + parse_cvsroot (); + + return rtag (argc, argv); /* Call real code */ +} + +int +client_import (argc, argv) + int argc; + char **argv; +{ + + parse_cvsroot (); + + return import (argc, argv); /* Call real code */ +} + +int +client_admin (argc, argv) + int argc; + char **argv; +{ + + parse_cvsroot (); + + return admin (argc, argv); /* Call real code */ +} + +int +client_export (argc, argv) + int argc; + char **argv; +{ + + parse_cvsroot (); + + return checkout (argc, argv); /* Call real code */ +} + +int +client_history (argc, argv) + int argc; + char **argv; +{ + + parse_cvsroot (); + + return history (argc, argv); /* Call real code */ +} + +int +client_release (argc, argv) + int argc; + char **argv; +{ + + parse_cvsroot (); + + return release (argc, argv); /* Call real code */ +} + +#endif /* CLIENT_SUPPORT */ diff --git a/gnu/usr.bin/cvs/cvs/client.h b/gnu/usr.bin/cvs/cvs/client.h new file mode 100644 index 0000000..0602900 --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/client.h @@ -0,0 +1,163 @@ +/* Interface between the client and the rest of CVS. */ + +/* Stuff shared with the server. */ +extern char *mode_to_string PROTO((mode_t)); +extern int change_mode PROTO((char *, char *)); + +extern int gzip_level; +extern int filter_through_gzip PROTO((int, int, int, pid_t *)); +extern int filter_through_gunzip PROTO((int, int, pid_t *)); + +#ifdef CLIENT_SUPPORT +/* + * Functions to perform CVS commands via the protocol. argc and argv + * are the arguments and the return value is the exit status (zero success + * nonzero failure). + */ +extern int client_commit PROTO((int argc, char **argv)); +extern int client_update PROTO((int argc, char **argv)); +extern int client_checkout PROTO((int argc, char **argv)); +extern int client_diff PROTO((int argc, char **argv)); +extern int client_log PROTO((int argc, char **argv)); +extern int client_add PROTO((int argc, char **argv)); +extern int client_remove PROTO((int argc, char **argv)); +extern int client_status PROTO((int argc, char **argv)); +extern int client_rdiff PROTO((int argc, char **argv)); +extern int client_tag PROTO((int argc, char **argv)); +extern int client_rtag PROTO((int argc, char **argv)); +extern int client_import PROTO((int argc, char **argv)); +extern int client_admin PROTO((int argc, char **argv)); +extern int client_export PROTO((int argc, char **argv)); +extern int client_history PROTO((int argc, char **argv)); +extern int client_release PROTO((int argc, char **argv)); + +/* + * Flag variable for seeing whether common code is running as a client + * or to do a local operation. + */ +extern int client_active; + +/* Is the -P option to checkout or update specified? */ +extern int client_prune_dirs; + +/* Stream to write to the server. */ +extern FILE *to_server; +/* Stream to read from the server. */ +extern FILE *from_server; + +/* Internal functions that handle client communication to server, etc. */ +int supported_request PROTO ((char *)); +void option_with_arg PROTO((char *option, char *arg)); + +/* Get the responses and then close the connection. */ +extern int get_responses_and_close PROTO((void)); + +extern int get_server_responses PROTO((void)); + +/* Start up the connection to the server on the other end. */ +void +start_server PROTO((void)); + +/* Send the names of all the argument files to the server. */ +void +send_file_names PROTO((int argc, char **argv)); + +/* + * Send Repository, Modified and Entry. argc and argv contain only + * the files to operate on (or empty for everything), not options. + * local is nonzero if we should not recurse (-l option). Also sends + * Argument lines for argc and argv, so should be called after options + * are sent. + */ +void +send_files PROTO((int argc, char **argv, int local, int aflag)); + +/* + * Like send_files but never send "Unchanged"--just send the contents of the + * file in that case. This is used to fix it if you import a directory which + * happens to have CVS directories (yes it is obscure but the testsuite tests + * it). + */ +void +send_files_contents PROTO((int argc, char **argv, int local, int aflag)); + +/* Send an argument to the remote server. */ +void +send_arg PROTO((char *string)); + +/* Send a string of single-char options to the remote server, one by one. */ +void +send_option_string PROTO((char *string)); + +#endif /* CLIENT_SUPPORT */ + +/* + * This structure is used to catalog the responses the client is + * prepared to see from the server. + */ + +struct response +{ + /* Name of the response. */ + char *name; + +#ifdef CLIENT_SUPPORT + /* + * Function to carry out the response. ARGS is the text of the + * command after name and, if present, a single space, have been + * stripped off. The function can scribble into ARGS if it wants. + */ + void (*func) PROTO((char *args, int len)); + + /* + * ok and error are special; they indicate we are at the end of the + * responses, and error indicates we should exit with nonzero + * exitstatus. + */ + enum {response_type_normal, response_type_ok, response_type_error} type; +#endif + + /* Used by the server to indicate whether response is supported by + the client, as set by the Valid-responses request. */ + enum { + /* + * Failure to implement this response can imply a fatal + * error. This should be set only for responses which were in the + * original version of the protocol; it should not be set for new + * responses. + */ + rs_essential, + + /* Some clients might not understand this response. */ + rs_optional, + + /* + * Set by the server to one of the following based on what this + * client actually supports. + */ + rs_supported, + rs_not_supported + } status; +}; + +/* Table of responses ending in an entry with a NULL name. */ + +extern struct response responses[]; + +#ifdef CLIENT_SUPPORT + +extern void client_senddate PROTO((const char *date)); +extern void client_expand_modules PROTO((int argc, char **argv, int local)); +extern void client_send_expansions PROTO((int local)); +extern void client_nonexpanded_setup PROTO((void)); + +extern char **failed_patches; +extern int failed_patches_count; +extern char toplevel_wd[]; +extern void client_import_setup PROTO((char *repository)); +extern int client_process_import_file + PROTO((char *message, char *vfile, char *vtag, + int targc, char *targv[], char *repository)); +extern void client_import_done PROTO((void)); + +#endif /* CLIENT_SUPPORT */ diff --git a/gnu/usr.bin/cvs/cvs/expand_path.c b/gnu/usr.bin/cvs/cvs/expand_path.c new file mode 100644 index 0000000..7cc939f --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/expand_path.c @@ -0,0 +1,139 @@ +/* expand_path.c -- expand environmental variables in passed in string + The main routine is expand_pathname, it is the routine + that handles the '~' character in four forms: + ~name + ~name/ + ~/ + ~ + and handles environment variables contained within the pathname + which are defined by: + c is some character + ${var_name} var_name is the name of the environ variable + $var_name var_name ends with a non ascii character + char *expand_pathname(char *name) + This routine will expand the pathname to account for ~ + and $ characters as described above.If an error occurs, NULL + is returned. + Will only expand Built in CVS variables all others are ignored. + */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include "cvs.h" +#include <stdio.h> +#include <ctype.h> +#include <sys/types.h> +#if HAVE_STRING_H +#include <string.h> +#else +#include <strings.h> +#endif +static char *expand_variable PROTO((char *env)); +extern char *xmalloc (); +extern void free (); +char * +expand_path (name) + char *name; +{ + char *s; + char *d; + char mybuf[PATH_MAX]; + char buf[PATH_MAX]; + char *result; + s = name; + d = mybuf; + while ((*d++ = *s)) + if (*s++ == '$') + { + char *p = d; + char *e; + int flag = (*s == '{'); + + for (; (*d++ = *s); s++) + if (flag ? *s =='}' : + isalnum (*s) == 0 && *s!='_' ) + break; + *--d = 0; + e = expand_variable (&p[flag]); + + if (e) + { + for (d = &p[-1]; (*d++ = *e++);) + ; + --d; + if (flag && *s) + s++; + } + else + return NULL; /* no env variable */ + } + *d = 0; + s = mybuf; + d = buf; + /* If you don't want ~username ~/ to be expanded simply remove + * This entire if statement including the else portion + */ + if (*s++ == '~') + { + char *t; + char *p=s; + if (*s=='/' || *s==0) + t = getenv ("HOME"); + else + { + struct passwd *ps; + for (; *p!='/' && *p; p++) + ; + *p = 0; + ps = getpwnam (s); + if (ps == 0) + return NULL; /* no such user */ + t = ps->pw_dir; + } + while ((*d++ = *t++)) + ; + --d; + if (*p == 0) + *p = '/'; /* always add / */ + s=p; + } + else + --s; + /* Kill up to here */ + while ((*d++ = *s++)) + ; + *d=0; + result = xmalloc (sizeof(char) * strlen(buf)+1); + strcpy (result, buf); + return result; +} +static char * +expand_variable (name) + char *name; +{ + /* There is nothing expanding this function to allow it + * to read a file in the $CVSROOT/CVSROOT directory that + * says which environmental variables could be expanded + * or just say everything is fair game to be expanded + */ + if ( strcmp (name, CVSROOT_ENV) == 0 ) + return CVSroot; + else + if ( strcmp (name, RCSBIN_ENV) == 0 ) + return Rcsbin; + else + if ( strcmp (name, EDITOR1_ENV) == 0 ) + return Editor; + else + if ( strcmp (name, EDITOR2_ENV) == 0 ) + return Editor; + else + if ( strcmp (name, EDITOR3_ENV) == 0 ) + return Editor; + else + return NULL; + /* The code here could also just + * return whatever getenv would + * return. + */ +} diff --git a/gnu/usr.bin/cvs/cvs/login.c b/gnu/usr.bin/cvs/cvs/login.c new file mode 100644 index 0000000..ccb757c --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/login.c @@ -0,0 +1,287 @@ +/* + * Copyright (c) 1995, Cyclic Software, Bloomington, IN, USA + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with CVS. + * + * Allow user to log in for an authenticating server. + */ + +#include "cvs.h" + +#ifdef AUTH_CLIENT_SUPPORT /* This covers the rest of the file. */ + +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)login.c 1.1 95/10/01 $"; +USE(rcsid); +#endif + +#ifndef CVS_PASSWORD_FILE +#define CVS_PASSWORD_FILE ".cvspass" +#endif + + +/* The return value will need to be freed. */ +char * +construct_cvspass_filename () +{ + char *homedir; + char *passfile; + + /* Construct absolute pathname to user's password file. */ + /* todo: does this work under Win-NT and OS/2 ? */ + homedir = getenv ("HOME"); + if (! homedir) + { + error (1, errno, "could not find out home directory"); + return (char *) NULL; + } + + passfile = + (char *) xmalloc (strlen (homedir) + strlen (CVS_PASSWORD_FILE) + 3); + strcpy (passfile, homedir); + strcat (passfile, "/"); + strcat (passfile, CVS_PASSWORD_FILE); + + /* Safety first and last, Scouts. */ + if (isfile (passfile)) + /* xchmod() is too polite. */ + chmod (passfile, 0600); + + return passfile; +} + + +/* Prompt for a password, and store it in the file "CVS/.cvspass". + * + * Because the user might be accessing multiple repositories, with + * different passwords for each one, the format of ~/.cvspass is: + * + * user@host:/path cleartext_password + * user@host:/path cleartext_password + * ... + * + * Of course, the "user@" might be left off -- it's just based on the + * value of CVSroot. + * + * Like .netrc, the file's permissions are the only thing preventing + * it from being read by others. Unlike .netrc, we will not be + * fascist about it, at most issuing a warning, and never refusing to + * work. + */ +int +login (argc, argv) + int argc; + char **argv; +{ + char *username; + int i; + char *passfile; + FILE *fp; + char *typed_password, *found_password; + char linebuf[MAXLINELEN]; + int root_len, already_entered = 0; + + /* Make this a "fully-qualified" CVSroot if necessary. */ + if (! strchr (CVSroot, '@')) + { + /* We need to prepend "user@host:". */ + char *tmp; + + printf ("Repository \"%s\" not fully-qualified.\n", CVSroot); + printf ("Please enter \"user@host:/path\": "); + fflush (stdout); + fgets (linebuf, MAXLINELEN, stdin); + + tmp = xmalloc (strlen (linebuf) + 1); + + strcpy (tmp, linebuf); + tmp[strlen (linebuf) - 1] = '\0'; + CVSroot = tmp; + } + + /* Check to make sure it's fully-qualified before going on. */ + if (! CVSroot) + { + error (1, 0, "CVSroot is NULL"); + } + else if ((! strchr (CVSroot, '@')) && (! strchr (CVSroot, ':'))) + { + error (1, 0, "CVSroot not fully-qualified: %s", CVSroot); + } + + + passfile = construct_cvspass_filename (); + typed_password = getpass ("Enter CVS password: "); + + /* IF we have a password for this "[user@]host:/path" already + * THEN + * IF it's the same as the password we read from the prompt + * THEN + * do nothing + * ELSE + * replace the old password with the new one + * ELSE + * append new entry to the end of the file. + */ + + root_len = strlen (CVSroot); + + /* Yes, the method below reads the user's password file twice. It's + inefficient, but we're not talking about a gig of data here. */ + + fp = fopen (passfile, "r"); + if (fp == NULL) + { + error (1, errno, "unable to open %s", passfile); + return 1; + } + + /* Check each line to see if we have this entry already. */ + while (fgets (linebuf, MAXLINELEN, fp) != NULL) + { + if (! strncmp (CVSroot, linebuf, root_len)) + { + already_entered = 1; + break; + } + } + fclose (fp); + + + if (already_entered) + { + /* This user/host has a password in the file already. */ + + /* todo: what about these charsets??? */ + strtok (linebuf, " \n"); + found_password = strtok (NULL, " \n"); + if (strcmp (found_password, typed_password)) + { + /* typed_password and found_password don't match, so we'll + * have to update passfile. We replace the old password + * with the new one by writing a tmp file whose contents are + * exactly the same as passfile except that this one entry + * gets typed_password instead of found_password. Then we + * rename the tmp file on top of passfile. + */ + char *tmp_name; + FILE *tmp_fp; + + tmp_name = tmpnam (NULL); + if ((tmp_fp = fopen (tmp_name, "w")) == NULL) + { + error (1, errno, "unable to open temp file %s", tmp_name); + return 1; + } + chmod (tmp_name, 0600); + + fp = fopen (passfile, "r"); + if (fp == NULL) + { + error (1, errno, "unable to open %s", passfile); + return 1; + } + /* I'm not paranoid, they really ARE out to get me: */ + chmod (passfile, 0600); + + while (fgets (linebuf, MAXLINELEN, fp) != NULL) + { + if (strncmp (CVSroot, linebuf, root_len)) + fprintf (tmp_fp, "%s", linebuf); + else + fprintf (tmp_fp, "%s %s\n", CVSroot, typed_password); + } + fclose (tmp_fp); + fclose (fp); + rename_file (tmp_name, passfile); + chmod (passfile, 0600); + } + } + else + { + if ((fp = fopen (passfile, "a")) == NULL) + { + error (1, errno, "could not open %s", passfile); + free (passfile); + return 1; + } + + /* It's safer this way, and blank lines in the file are OK. */ + fprintf (fp, "\n%s %s\n", CVSroot, typed_password); + fclose (fp); + } + + /* Utter, total, raving paranoia, I know. */ + chmod (passfile, 0600); + memset (typed_password, 0, strlen (typed_password)); + + free (passfile); + return 0; +} + +/* todo: "cvs logout" could erase an entry from the file. + * But to what purpose? + */ + + +char * +get_cvs_password (user, host, cvsroot) +{ + int root_len; + int found_it = 0; + char *password; + char linebuf[MAXLINELEN]; + FILE *fp; + char *passfile; + + passfile = construct_cvspass_filename (); + fp = fopen (passfile, "r"); + if (fp == NULL) + { + error (0, errno, "could not open %s", passfile); + free (passfile); + goto prompt_for_it; + } + + root_len = strlen (CVSroot); + + /* Check each line to see if we have this entry already. */ + while (fgets (linebuf, MAXLINELEN, fp) != NULL) + { + if (strncmp (CVSroot, linebuf, root_len) == 0) + { + /* This is it! So break out and deal with linebuf. */ + found_it = 1; + break; + } + } + + if (found_it) + { + /* linebuf now contains the line with the password. */ + char *tmp; + + strtok (linebuf, " "); + password = strtok (NULL, "\n"); + + /* Give it permanent storage. */ + tmp = xmalloc (strlen (password) + 1); + strcpy (tmp, password); + tmp[strlen (password)] = '\0'; + memset (password, 0, strlen (password)); + return tmp; + } + else + { + prompt_for_it: + return getpass ("CVS password: "); + } +} + +#endif /* AUTH_CLIENT_SUPPORT from beginning of file. */ + diff --git a/gnu/usr.bin/cvs/cvs/rcscmds.c b/gnu/usr.bin/cvs/cvs/rcscmds.c new file mode 100644 index 0000000..af32cea --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/rcscmds.c @@ -0,0 +1,102 @@ +/* + * Copyright (c) 1992, Brian Berliner and Jeff Polk + * Copyright (c) 1989-1992, Brian Berliner + * + * You may distribute under the terms of the GNU General Public License as + * specified in the README file that comes with the CVS 1.4 kit. + * + * The functions in this file provide an interface for performing + * operations directly on RCS files. + */ + +#include "cvs.h" + +int +RCS_settag(path, tag, rev) + const char *path; + const char *tag; + const char *rev; +{ + run_setup ("%s%s -q -N%s:%s", Rcsbin, RCS, tag, rev); + run_arg (path); + return run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); +} + +/* NOERR is 1 to suppress errors--FIXME it would + be better to avoid the errors or some cleaner solution. */ +int +RCS_deltag(path, tag, noerr) + const char *path; + const char *tag; + int noerr; +{ + run_setup ("%s%s -q -N%s", Rcsbin, RCS, tag); + run_arg (path); + return run_exec (RUN_TTY, RUN_TTY, noerr ? DEVNULL : RUN_TTY, RUN_NORMAL); +} + +/* set RCS branch to REV */ +int +RCS_setbranch(path, rev) + const char *path; + const char *rev; +{ + run_setup ("%s%s -q -b%s", Rcsbin, RCS, rev ? rev : ""); + run_arg (path); + return run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); +} + +/* Lock revision REV. NOERR is 1 to suppress errors--FIXME it would + be better to avoid the errors or some cleaner solution. */ +int +RCS_lock(path, rev, noerr) + const char *path; + const char *rev; + int noerr; +{ + run_setup ("%s%s -q -l%s", Rcsbin, RCS, rev ? rev : ""); + run_arg (path); + return run_exec (RUN_TTY, RUN_TTY, noerr ? DEVNULL : RUN_TTY, RUN_NORMAL); +} + +/* Unlock revision REV. NOERR is 1 to suppress errors--FIXME it would + be better to avoid the errors or some cleaner solution. */ +int +RCS_unlock(path, rev, noerr) + const char *path; + const char *rev; + int noerr; +{ + run_setup ("%s%s -q -u%s", Rcsbin, RCS, rev ? rev : ""); + run_arg (path); + return run_exec (RUN_TTY, RUN_TTY, noerr ? DEVNULL : RUN_TTY, RUN_NORMAL); +} + +/* Merge revisions REV1 and REV2. */ +int +RCS_merge(path, options, rev1, rev2) + const char *path; + const char *options; + const char *rev1; + const char *rev2; +{ + int status; + + /* XXX - Do merge by hand instead of using rcsmerge, due to -k handling */ + + run_setup ("%s%s %s -r%s -r%s %s", Rcsbin, RCS_RCSMERGE, + options, rev1, rev2, path); + status = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); +#ifndef HAVE_RCS5 + if (status == 0) + { + /* Run GREP to see if there appear to be conflicts in the file */ + run_setup ("%s", GREP); + run_arg (RCS_MERGE_PAT); + run_arg (path); + status = (run_exec (RUN_TTY, DEVNULL, RUN_TTY, RUN_NORMAL) == 0); + + } +#endif + return status; +} diff --git a/gnu/usr.bin/cvs/cvs/server.c b/gnu/usr.bin/cvs/cvs/server.c new file mode 100644 index 0000000..fbfca86 --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/server.c @@ -0,0 +1,3992 @@ +#include "cvs.h" + +#ifdef SERVER_SUPPORT + +/* for select */ +#include <sys/types.h> +#ifdef HAVE_SYS_BSDTYPES_H +#include <sys/bsdtypes.h> +#endif +#include <sys/time.h> + +#if HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif + +#if HAVE_FCNTL_H +#include <fcntl.h> +#endif + +#ifndef O_NONBLOCK +#define O_NONBLOCK O_NDELAY +#endif + + +/* Functions which the server calls. */ +int add PROTO((int argc, char **argv)); +int admin PROTO((int argc, char **argv)); +int checkout PROTO((int argc, char **argv)); +int commit PROTO((int argc, char **argv)); +int diff PROTO((int argc, char **argv)); +int history PROTO((int argc, char **argv)); +int import PROTO((int argc, char **argv)); +int cvslog PROTO((int argc, char **argv)); +int patch PROTO((int argc, char **argv)); +int release PROTO((int argc, char **argv)); +int cvsremove PROTO((int argc, char **argv)); +int rtag PROTO((int argc, char **argv)); +int status PROTO((int argc, char **argv)); +int tag PROTO((int argc, char **argv)); +int update PROTO((int argc, char **argv)); + + +/* + * This is where we stash stuff we are going to use. Format string + * which expects a single directory within it, starting with a slash. + */ +static char *server_temp_dir; + +/* Nonzero if we should keep the temp directory around after we exit. */ +static int dont_delete_temp; + +static char no_mem_error; +#define NO_MEM_ERROR (&no_mem_error) + +static void server_write_entries PROTO((void)); + +/* + * Read a line from the stream "instream" without command line editing. + * + * Action is compatible with "readline", e.g. space for the result is + * malloc'd and should be freed by the caller. + * + * A NULL return means end of file. A return of NO_MEM_ERROR means + * that we are out of memory. + */ +static char *read_line PROTO((FILE *)); + +static char * +read_line (stream) + FILE *stream; +{ + int c; + char *result; + int input_index = 0; + int result_size = 80; + + fflush (stdout); + result = (char *) malloc (result_size); + if (result == NULL) + return NO_MEM_ERROR; + + while (1) + { + c = fgetc (stream); + + if (c == EOF) + { + free (result); + return NULL; + } + + if (c == '\n') + break; + + result[input_index++] = c; + while (input_index >= result_size) + { + result_size *= 2; + result = (char *) realloc (result, result_size); + if (result == NULL) + return NO_MEM_ERROR; + } + } + + result[input_index++] = '\0'; + return result; +} + +/* + * Make directory DIR, including all intermediate directories if necessary. + * Returns 0 for success or errno code. + */ +static int mkdir_p PROTO((char *)); + +static int +mkdir_p (dir) + char *dir; +{ + char *p; + char *q = malloc (strlen (dir) + 1); + int retval; + + if (q == NULL) + return ENOMEM; + + /* + * Skip over leading slash if present. We won't bother to try to + * make '/'. + */ + p = dir + 1; + while (1) + { + while (*p != '/' && *p != '\0') + ++p; + if (*p == '/') + { + strncpy (q, dir, p - dir); + q[p - dir] = '\0'; + if (CVS_MKDIR (q, 0777) < 0) + { + if (errno != EEXIST + && (errno != EACCES || !isdir(q))) + { + retval = errno; + goto done; + } + } + ++p; + } + else + { + if (CVS_MKDIR (dir, 0777) < 0) + retval = errno; + else + retval = 0; + goto done; + } + } + done: + free (q); + return retval; +} + +/* + * Print the error response for error code STATUS. The caller is + * reponsible for making sure we get back to the command loop without + * any further output occuring. + */ +static void +print_error (status) + int status; +{ + char *msg; + printf ("error "); + msg = strerror (status); + if (msg) + printf ("%s", msg); + printf ("\n"); +} + +static int pending_error; +/* + * Malloc'd text for pending error. Each line must start with "E ". The + * last line should not end with a newline. + */ +static char *pending_error_text; + +/* If an error is pending, print it and return 1. If not, return 0. */ +static int +print_pending_error () +{ + if (pending_error_text) + { + printf ("%s\n", pending_error_text); + if (pending_error) + print_error (pending_error); + else + printf ("error \n"); + pending_error = 0; + free (pending_error_text); + pending_error_text = NULL; + return 1; + } + else if (pending_error) + { + print_error (pending_error); + pending_error = 0; + return 1; + } + else + return 0; +} + +/* Is an error pending? */ +#define error_pending() (pending_error || pending_error_text) + +int +supported_response (name) + char *name; +{ + struct response *rs; + + for (rs = responses; rs->name != NULL; ++rs) + if (strcmp (rs->name, name) == 0) + return rs->status == rs_supported; + error (1, 0, "internal error: testing support for unknown response?"); +} + +static void +serve_valid_responses (arg) + char *arg; +{ + char *p = arg; + char *q; + struct response *rs; + do + { + q = strchr (p, ' '); + if (q != NULL) + *q++ = '\0'; + for (rs = responses; rs->name != NULL; ++rs) + { + if (strcmp (rs->name, p) == 0) + break; + } + if (rs->name == NULL) + /* + * It is a response we have never heard of (and thus never + * will want to use). So don't worry about it. + */ + ; + else + rs->status = rs_supported; + p = q; + } while (q != NULL); + for (rs = responses; rs->name != NULL; ++rs) + { + if (rs->status == rs_essential) + { + printf ("E response `%s' not supported by client\nerror \n", + rs->name); + exit (1); + } + else if (rs->status == rs_optional) + rs->status = rs_not_supported; + } +} + +static int use_dir_and_repos = 0; + +static void +serve_root (arg) + char *arg; +{ + char *env; + extern char *CVSroot; + char path[PATH_MAX]; + int save_errno; + + if (error_pending()) return; + + (void) sprintf (path, "%s/%s", arg, CVSROOTADM); + if (!isaccessible (path, R_OK | X_OK)) + { + save_errno = errno; + pending_error_text = malloc (80 + strlen (path)); + if (pending_error_text != NULL) + sprintf (pending_error_text, "E Cannot access %s", path); + pending_error = save_errno; + } + (void) strcat (path, "/"); + (void) strcat (path, CVSROOTADM_HISTORY); + if (isfile (path) && !isaccessible (path, R_OK | W_OK)) + { + save_errno = errno; + pending_error_text = malloc (80 + strlen (path)); + if (pending_error_text != NULL) + sprintf (pending_error_text, "E \ +Sorry, you don't have read/write access to the history file %s", path); + pending_error = save_errno; + } + + CVSroot = malloc (strlen (arg) + 1); + if (CVSroot == NULL) + { + pending_error = ENOMEM; + return; + } + strcpy (CVSroot, arg); +#ifdef HAVE_PUTENV + env = malloc (strlen (CVSROOT_ENV) + strlen (CVSroot) + 1 + 1); + if (env == NULL) + { + pending_error = ENOMEM; + return; + } + (void) sprintf (env, "%s=%s", CVSROOT_ENV, arg); + (void) putenv (env); + /* do not free env, as putenv has control of it */ +#endif +} + +/* + * Add as many directories to the temp directory as the client tells us it + * will use "..", so we never try to access something outside the temp + * directory via "..". + */ +static void +serve_max_dotdot (arg) + char *arg; +{ + int lim = atoi (arg); + int i; + char *p; + + if (lim < 0) + return; + p = malloc (strlen (server_temp_dir) + 2 * lim + 10); + if (p == NULL) + { + pending_error = ENOMEM; + return; + } + strcpy (p, server_temp_dir); + for (i = 0; i < lim; ++i) + strcat (p, "/d"); + free (server_temp_dir); + server_temp_dir = p; +} + +static void +dirswitch (dir, repos) + char *dir; + char *repos; +{ + char *dirname; + int status; + FILE *f; + + server_write_entries (); + + if (error_pending()) return; + + dirname = malloc (strlen (server_temp_dir) + strlen (dir) + 40); + if (dirname == NULL) + { + pending_error = ENOMEM; + return; + } + + strcpy (dirname, server_temp_dir); + strcat (dirname, "/"); + strcat (dirname, dir); + + status = mkdir_p (dirname); + if (status != 0 + && status != EEXIST) + { + pending_error = status; + pending_error_text = malloc (80 + strlen(dirname)); + sprintf(pending_error_text, "E cannot mkdir %s", dirname); + return; + } + if (chdir (dirname) < 0) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(dirname)); + sprintf(pending_error_text, "E cannot change to %s", dirname); + return; + } + /* + * This is pretty much like calling Create_Admin, but Create_Admin doesn't + * report errors in the right way for us. + */ + if (CVS_MKDIR (CVSADM, 0777) < 0) + { + if (errno == EEXIST) + /* Don't create the files again. */ + return; + pending_error = errno; + return; + } + f = fopen (CVSADM_REP, "w"); + if (f == NULL) + { + pending_error = errno; + return; + } + if (fprintf (f, "%s\n", repos) < 0) + { + pending_error = errno; + fclose (f); + return; + } + if (fclose (f) == EOF) + { + pending_error = errno; + return; + } + f = fopen (CVSADM_ENT, "w+"); + if (f == NULL) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_ENT)); + sprintf(pending_error_text, "E cannot open %s", CVSADM_ENT); + return; + } + if (fclose (f) == EOF) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_ENT)); + sprintf(pending_error_text, "E cannot close %s", CVSADM_ENT); + return; + } + free (dirname); +} + +static void +serve_repository (arg) + char *arg; +{ + dirswitch (arg + 1, arg); +} + +static void +serve_directory (arg) + char *arg; +{ + char *repos; + use_dir_and_repos = 1; + repos = read_line (stdin); + if (repos == NULL) + { + pending_error_text = malloc (80 + strlen (arg)); + if (pending_error_text) + { + if (feof (stdin)) + sprintf (pending_error_text, + "E end of file reading mode for %s", arg); + else + { + sprintf (pending_error_text, + "E error reading mode for %s", arg); + pending_error = errno; + } + } + else + pending_error = ENOMEM; + } + else if (repos == NO_MEM_ERROR) + { + pending_error = ENOMEM; + } + else + { + dirswitch (arg, repos); + free (repos); + } +} + +static void +serve_static_directory (arg) + char *arg; +{ + FILE *f; + f = fopen (CVSADM_ENTSTAT, "w+"); + if (f == NULL) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_ENTSTAT)); + sprintf(pending_error_text, "E cannot open %s", CVSADM_ENTSTAT); + return; + } + if (fclose (f) == EOF) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_ENTSTAT)); + sprintf(pending_error_text, "E cannot close %s", CVSADM_ENTSTAT); + return; + } +} + +static void +serve_sticky (arg) + char *arg; +{ + FILE *f; + f = fopen (CVSADM_TAG, "w+"); + if (f == NULL) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_TAG)); + sprintf(pending_error_text, "E cannot open %s", CVSADM_TAG); + return; + } + if (fprintf (f, "%s\n", arg) < 0) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_TAG)); + sprintf(pending_error_text, "E cannot write to %s", CVSADM_TAG); + return; + } + if (fclose (f) == EOF) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_TAG)); + sprintf(pending_error_text, "E cannot close %s", CVSADM_TAG); + return; + } +} + +/* + * Read SIZE bytes from stdin, write them to FILE. + * + * Currently this isn't really used for receiving parts of a file -- + * the file is still sent over in one chunk. But if/when we get + * spiffy in-process gzip support working, perhaps the compressed + * pieces could be sent over as they're ready, if the network is fast + * enough. Or something. + */ +static void +receive_partial_file (size, file) + int size; + int file; +{ + char buf[16*1024], *bufp; + int toread, nread, nwrote; + while (size > 0) + { + toread = sizeof (buf); + if (toread > size) + toread = size; + + nread = fread (buf, 1, toread, stdin); + if (nread <= 0) + { + if (feof (stdin)) + { + pending_error_text = malloc (80); + if (pending_error_text) + { + sprintf (pending_error_text, + "E premature end of file from client"); + pending_error = 0; + } + else + pending_error = ENOMEM; + } + else if (ferror (stdin)) + { + pending_error_text = malloc (40); + if (pending_error_text) + sprintf (pending_error_text, + "E error reading from client"); + pending_error = errno; + } + else + { + pending_error_text = malloc (40); + if (pending_error_text) + sprintf (pending_error_text, + "E short read from client"); + pending_error = 0; + } + return; + } + size -= nread; + bufp = buf; + while (nread) + { + nwrote = write (file, bufp, nread); + if (nwrote < 0) + { + pending_error_text = malloc (40); + if (pending_error_text) + sprintf (pending_error_text, "E unable to write"); + pending_error = errno; + return; + } + nread -= nwrote; + bufp += nwrote; + } + } +} + +/* Receive SIZE bytes, write to filename FILE. */ +static void +receive_file (size, file, gzipped) + int size; + char *file; + int gzipped; +{ + int fd; + char *arg = file; + pid_t gzip_pid = 0; + int gzip_status; + + /* Write the file. */ + fd = open (arg, O_WRONLY | O_CREAT | O_TRUNC, 0600); + if (fd < 0) + { + pending_error_text = malloc (40 + strlen (arg)); + if (pending_error_text) + sprintf (pending_error_text, "E cannot open %s", arg); + pending_error = errno; + return; + } + + /* + * FIXME: This doesn't do anything reasonable with gunzip's stderr, which + * means that if gunzip writes to stderr, it will cause all manner of + * protocol violations. + */ + if (gzipped) + fd = filter_through_gunzip (fd, 0, &gzip_pid); + + receive_partial_file (size, fd); + + if (pending_error_text) + { + char *p = realloc (pending_error_text, + strlen (pending_error_text) + strlen (arg) + 30); + if (p) + { + pending_error_text = p; + sprintf (p + strlen (p), ", file %s", arg); + } + /* else original string is supposed to be unchanged */ + } + + if (close (fd) < 0 && !error_pending ()) + { + pending_error_text = malloc (40 + strlen (arg)); + if (pending_error_text) + sprintf (pending_error_text, "E cannot close %s", arg); + pending_error = errno; + if (gzip_pid) + waitpid (gzip_pid, (int *) 0, 0); + return; + } + + if (gzip_pid) + { + if (waitpid (gzip_pid, &gzip_status, 0) != gzip_pid) + error (1, errno, "waiting for gunzip process %d", gzip_pid); + else if (gzip_status != 0) + error (1, 0, "gunzip exited %d", gzip_status); + } +} + +static void +serve_modified (arg) + char *arg; +{ + int size; + char *size_text; + char *mode_text; + + int gzipped = 0; + + if (error_pending ()) return; + + mode_text = read_line (stdin); + if (mode_text == NULL) + { + pending_error_text = malloc (80 + strlen (arg)); + if (pending_error_text) + { + if (feof (stdin)) + sprintf (pending_error_text, + "E end of file reading mode for %s", arg); + else + { + sprintf (pending_error_text, + "E error reading mode for %s", arg); + pending_error = errno; + } + } + else + pending_error = ENOMEM; + return; + } + else if (mode_text == NO_MEM_ERROR) + { + pending_error = ENOMEM; + return; + } + size_text = read_line (stdin); + if (size_text == NULL) + { + pending_error_text = malloc (80 + strlen (arg)); + if (pending_error_text) + { + if (feof (stdin)) + sprintf (pending_error_text, + "E end of file reading size for %s", arg); + else + { + sprintf (pending_error_text, + "E error reading size for %s", arg); + pending_error = errno; + } + } + else + pending_error = ENOMEM; + return; + } + else if (size_text == NO_MEM_ERROR) + { + pending_error = ENOMEM; + return; + } + if (size_text[0] == 'z') + { + gzipped = 1; + size = atoi (size_text + 1); + } + else + size = atoi (size_text); + free (size_text); + + if (size >= 0) + { + receive_file (size, arg, gzipped); + if (error_pending ()) return; + } + + { + int status = change_mode (arg, mode_text); + free (mode_text); + if (status) + { + pending_error_text = malloc (40 + strlen (arg)); + if (pending_error_text) + sprintf (pending_error_text, + "E cannot change mode for %s", arg); + pending_error = status; + return; + } + } +} + +#endif /* SERVER_SUPPORT */ + +#if defined(SERVER_SUPPORT) || defined(CLIENT_SUPPORT) + +int use_unchanged = 0; + +#endif +#ifdef SERVER_SUPPORT + +static void +serve_enable_unchanged (arg) + char *arg; +{ + use_unchanged = 1; +} + +static void +serve_lost (arg) + char *arg; +{ + if (use_unchanged) + { + /* A missing file already indicates it is nonexistent. */ + return; + } + else + { + struct utimbuf ut; + int fd = open (arg, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (fd < 0 || close (fd) < 0) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(arg)); + sprintf(pending_error_text, "E cannot open %s", arg); + return; + } + /* + * Set the times to the beginning of the epoch to tell time_stamp() + * that the file was lost. + */ + ut.actime = 0; + ut.modtime = 0; + if (utime (arg, &ut) < 0) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(arg)); + sprintf(pending_error_text, "E cannot utime %s", arg); + return; + } + } +} + +struct an_entry { + struct an_entry *next; + char *entry; +}; + +static struct an_entry *entries; + +static void +serve_unchanged (arg) + char *arg; +{ + if (error_pending ()) + return; + if (!use_unchanged) + { + /* A missing file already indicates it is unchanged. */ + return; + } + else + { + struct an_entry *p; + char *name; + char *cp; + char *timefield; + + /* Rewrite entries file to have `=' in timestamp field. */ + for (p = entries; p != NULL; p = p->next) + { + name = p->entry + 1; + cp = strchr (name, '/'); + if (cp != NULL + && strlen (arg) == cp - name + && strncmp (arg, name, cp - name) == 0) + { + timefield = strchr (cp + 1, '/') + 1; + if (*timefield != '=') + { + cp = timefield + strlen (timefield); + cp[1] = '\0'; + while (cp > timefield) + { + *cp = cp[-1]; + --cp; + } + *timefield = '='; + } + break; + } + } + } +} + +static void +serve_entry (arg) + char *arg; +{ + struct an_entry *p; + char *cp; + if (error_pending()) return; + p = (struct an_entry *) malloc (sizeof (struct an_entry)); + if (p == NULL) + { + pending_error = ENOMEM; + return; + } + /* Leave space for serve_unchanged to write '=' if it wants. */ + cp = malloc (strlen (arg) + 2); + if (cp == NULL) + { + pending_error = ENOMEM; + return; + } + strcpy (cp, arg); + p->next = entries; + p->entry = cp; + entries = p; +} + +static void +server_write_entries () +{ + FILE *f; + struct an_entry *p; + struct an_entry *q; + + if (entries == NULL) + return; + + f = NULL; + /* Note that we free all the entries regardless of errors. */ + if (!error_pending ()) + { + f = fopen (CVSADM_ENT, "w"); + if (f == NULL) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_ENT)); + sprintf(pending_error_text, "E cannot open %s", CVSADM_ENT); + } + } + for (p = entries; p != NULL;) + { + if (!error_pending ()) + { + if (fprintf (f, "%s\n", p->entry) < 0) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_ENT)); + sprintf(pending_error_text, "E cannot write to %s", CVSADM_ENT); + } + } + free (p->entry); + q = p->next; + free (p); + p = q; + } + entries = NULL; + if (f != NULL && fclose (f) == EOF && !error_pending ()) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_ENT)); + sprintf(pending_error_text, "E cannot close %s", CVSADM_ENT); + } +} + +static int argument_count; +static char **argument_vector; +static int argument_vector_size; + +static void +serve_argument (arg) + char *arg; +{ + char *p; + + if (error_pending()) return; + + if (argument_vector_size <= argument_count) + { + argument_vector_size *= 2; + argument_vector = + (char **) realloc ((char *)argument_vector, + argument_vector_size * sizeof (char *)); + if (argument_vector == NULL) + { + pending_error = ENOMEM; + return; + } + } + p = malloc (strlen (arg) + 1); + if (p == NULL) + { + pending_error = ENOMEM; + return; + } + strcpy (p, arg); + argument_vector[argument_count++] = p; +} + +static void +serve_argumentx (arg) + char *arg; +{ + char *p; + + if (error_pending()) return; + + p = argument_vector[argument_count - 1]; + p = realloc (p, strlen (p) + 1 + strlen (arg) + 1); + if (p == NULL) + { + pending_error = ENOMEM; + return; + } + strcat (p, "\n"); + strcat (p, arg); + argument_vector[argument_count - 1] = p; +} + +static void +serve_global_option (arg) + char *arg; +{ + if (arg[0] != '-' || arg[1] == '\0' || arg[2] != '\0') + { + error_return: + pending_error_text = malloc (strlen (arg) + 80); + sprintf (pending_error_text, "E Protocol error: bad global option %s", + arg); + return; + } + switch (arg[1]) + { + case 'n': + noexec = 1; + break; + case 'q': + quiet = 1; + break; + case 'r': + cvswrite = 0; + break; + case 'Q': + really_quiet = 1; + break; + case 'l': + logoff = 1; + break; + case 't': + trace = 1; + break; + default: + goto error_return; + } +} + +/* + * We must read data from a child process and send it across the + * network. We do not want to block on writing to the network, so we + * store the data from the child process in memory. A BUFFER + * structure holds the status of one communication, and uses a linked + * list of buffer_data structures to hold data. + */ + +struct buffer +{ + /* Data. */ + struct buffer_data *data; + + /* Last buffer on data chain. */ + struct buffer_data *last; + + /* File descriptor to write to or read from. */ + int fd; + + /* Nonzero if this is an output buffer (sanity check). */ + int output; + + /* Nonzero if the file descriptor is in nonblocking mode. */ + int nonblocking; + + /* Function to call if we can't allocate memory. */ + void (*memory_error) PROTO((struct buffer *)); +}; + +/* Data is stored in lists of these structures. */ + +struct buffer_data +{ + /* Next buffer in linked list. */ + struct buffer_data *next; + + /* + * A pointer into the data area pointed to by the text field. This + * is where to find data that has not yet been written out. + */ + char *bufp; + + /* The number of data bytes found at BUFP. */ + int size; + + /* + * Actual buffer. This never changes after the structure is + * allocated. The buffer is BUFFER_DATA_SIZE bytes. + */ + char *text; +}; + +/* The size we allocate for each buffer_data structure. */ +#define BUFFER_DATA_SIZE (4096) + +#ifdef SERVER_FLOWCONTROL +/* The maximum we'll queue to the remote client before blocking. */ +# ifndef SERVER_HI_WATER +# define SERVER_HI_WATER (2 * 1024 * 1024) +# endif /* SERVER_HI_WATER */ +/* When the buffer drops to this, we restart the child */ +# ifndef SERVER_LO_WATER +# define SERVER_LO_WATER (1 * 1024 * 1024) +# endif /* SERVER_LO_WATER */ +#endif /* SERVER_FLOWCONTROL */ + +/* Linked list of available buffer_data structures. */ +static struct buffer_data *free_buffer_data; + +static void allocate_buffer_datas PROTO((void)); +static inline struct buffer_data *get_buffer_data PROTO((void)); +static int buf_empty_p PROTO((struct buffer *)); +static void buf_output PROTO((struct buffer *, const char *, int)); +static void buf_output0 PROTO((struct buffer *, const char *)); +static inline void buf_append_char PROTO((struct buffer *, int)); +static int buf_send_output PROTO((struct buffer *)); +static int set_nonblock PROTO((struct buffer *)); +static int set_block PROTO((struct buffer *)); +static int buf_send_counted PROTO((struct buffer *)); +static inline void buf_append_data PROTO((struct buffer *, + struct buffer_data *, + struct buffer_data *)); +static int buf_read_file PROTO((FILE *, long, struct buffer_data **, + struct buffer_data **)); +static int buf_input_data PROTO((struct buffer *, int *)); +static void buf_copy_lines PROTO((struct buffer *, struct buffer *, int)); +static int buf_copy_counted PROTO((struct buffer *, struct buffer *)); + +#ifdef SERVER_FLOWCONTROL +static int buf_count_mem PROTO((struct buffer *)); +static int set_nonblock_fd PROTO((int)); +#endif /* SERVER_FLOWCONTROL */ + +/* Allocate more buffer_data structures. */ + +static void +allocate_buffer_datas () +{ + struct buffer_data *alc; + char *space; + int i; + + /* Allocate buffer_data structures in blocks of 16. */ +#define ALLOC_COUNT (16) + + alc = ((struct buffer_data *) + malloc (ALLOC_COUNT * sizeof (struct buffer_data))); + space = (char *) valloc (ALLOC_COUNT * BUFFER_DATA_SIZE); + if (alc == NULL || space == NULL) + return; + for (i = 0; i < ALLOC_COUNT; i++, alc++, space += BUFFER_DATA_SIZE) + { + alc->next = free_buffer_data; + free_buffer_data = alc; + alc->text = space; + } +} + +/* Get a new buffer_data structure. */ + +static inline struct buffer_data * +get_buffer_data () +{ + struct buffer_data *ret; + + if (free_buffer_data == NULL) + { + allocate_buffer_datas (); + if (free_buffer_data == NULL) + return NULL; + } + + ret = free_buffer_data; + free_buffer_data = ret->next; + return ret; +} + +/* See whether a buffer is empty. */ + +static int +buf_empty_p (buf) + struct buffer *buf; +{ + struct buffer_data *data; + + for (data = buf->data; data != NULL; data = data->next) + if (data->size > 0) + return 0; + return 1; +} + +#ifdef SERVER_FLOWCONTROL +/* + * Count how much data is stored in the buffer.. + * Note that each buffer is a malloc'ed chunk BUFFER_DATA_SIZE. + */ + +static int +buf_count_mem (buf) + struct buffer *buf; +{ + struct buffer_data *data; + int mem = 0; + + for (data = buf->data; data != NULL; data = data->next) + mem += BUFFER_DATA_SIZE; + + return mem; +} +#endif /* SERVER_FLOWCONTROL */ + +/* Add data DATA of length LEN to BUF. */ + +static void +buf_output (buf, data, len) + struct buffer *buf; + const char *data; + int len; +{ + if (! buf->output) + abort (); + + if (buf->data != NULL + && (((buf->last->text + BUFFER_DATA_SIZE) + - (buf->last->bufp + buf->last->size)) + >= len)) + { + memcpy (buf->last->bufp + buf->last->size, data, len); + buf->last->size += len; + return; + } + + while (1) + { + struct buffer_data *newdata; + + newdata = get_buffer_data (); + if (newdata == NULL) + { + (*buf->memory_error) (buf); + return; + } + + if (buf->data == NULL) + buf->data = newdata; + else + buf->last->next = newdata; + newdata->next = NULL; + buf->last = newdata; + + newdata->bufp = newdata->text; + + if (len <= BUFFER_DATA_SIZE) + { + newdata->size = len; + memcpy (newdata->text, data, len); + return; + } + + newdata->size = BUFFER_DATA_SIZE; + memcpy (newdata->text, data, BUFFER_DATA_SIZE); + + data += BUFFER_DATA_SIZE; + len -= BUFFER_DATA_SIZE; + } + + /*NOTREACHED*/ +} + +/* Add a '\0' terminated string to BUF. */ + +static void +buf_output0 (buf, string) + struct buffer *buf; + const char *string; +{ + buf_output (buf, string, strlen (string)); +} + +/* Add a single character to BUF. */ + +static inline void +buf_append_char (buf, ch) + struct buffer *buf; + int ch; +{ + if (buf->data != NULL + && (buf->last->text + BUFFER_DATA_SIZE + != buf->last->bufp + buf->last->size)) + { + *(buf->last->bufp + buf->last->size) = ch; + ++buf->last->size; + } + else + { + char b; + + b = ch; + buf_output (buf, &b, 1); + } +} + +/* + * Send all the output we've been saving up. Returns 0 for success or + * errno code. If the buffer has been set to be nonblocking, this + * will just write until the write would block. + */ + +static int +buf_send_output (buf) + struct buffer *buf; +{ + if (! buf->output) + abort (); + + while (buf->data != NULL) + { + struct buffer_data *data; + + data = buf->data; + while (data->size > 0) + { + int nbytes; + + nbytes = write (buf->fd, data->bufp, data->size); + if (nbytes <= 0) + { + int status; + + if (buf->nonblocking + && (nbytes == 0 +#ifdef EWOULDBLOCK + || errno == EWOULDBLOCK +#endif + || errno == EAGAIN)) + { + /* + * A nonblocking write failed to write any data. + * Just return. + */ + return 0; + } + + /* + * An error, or EOF. Throw away all the data and + * return. + */ + if (nbytes == 0) + status = EIO; + else + status = errno; + + buf->last->next = free_buffer_data; + free_buffer_data = buf->data; + buf->data = NULL; + buf->last = NULL; + + return status; + } + + data->size -= nbytes; + data->bufp += nbytes; + } + + buf->data = data->next; + data->next = free_buffer_data; + free_buffer_data = data; + } + + buf->last = NULL; + + return 0; +} + +#ifdef SERVER_FLOWCONTROL +/* + * Set buffer BUF to non-blocking I/O. Returns 0 for success or errno + * code. + */ + +static int +set_nonblock_fd (fd) + int fd; +{ + int flags; + + flags = fcntl (fd, F_GETFL, 0); + if (flags < 0) + return errno; + if (fcntl (fd, F_SETFL, flags | O_NONBLOCK) < 0) + return errno; + return 0; +} +#endif /* SERVER_FLOWCONTROL */ + +static int +set_nonblock (buf) + struct buffer *buf; +{ + int flags; + + if (buf->nonblocking) + return 0; + flags = fcntl (buf->fd, F_GETFL, 0); + if (flags < 0) + return errno; + if (fcntl (buf->fd, F_SETFL, flags | O_NONBLOCK) < 0) + return errno; + buf->nonblocking = 1; + return 0; +} + +/* + * Set buffer BUF to blocking I/O. Returns 0 for success or errno + * code. + */ + +static int +set_block (buf) + struct buffer *buf; +{ + int flags; + + if (! buf->nonblocking) + return 0; + flags = fcntl (buf->fd, F_GETFL, 0); + if (flags < 0) + return errno; + if (fcntl (buf->fd, F_SETFL, flags & ~O_NONBLOCK) < 0) + return errno; + buf->nonblocking = 0; + return 0; +} + +/* + * Send a character count and some output. Returns errno code or 0 for + * success. + * + * Sending the count in binary is OK since this is only used on a pipe + * within the same system. + */ + +static int +buf_send_counted (buf) + struct buffer *buf; +{ + int size; + struct buffer_data *data; + + if (! buf->output) + abort (); + + size = 0; + for (data = buf->data; data != NULL; data = data->next) + size += data->size; + + data = get_buffer_data (); + if (data == NULL) + { + (*buf->memory_error) (buf); + return ENOMEM; + } + + data->next = buf->data; + buf->data = data; + if (buf->last == NULL) + buf->last = data; + + data->bufp = data->text; + data->size = sizeof (int); + + *((int *) data->text) = size; + + return buf_send_output (buf); +} + +/* Append a list of buffer_data structures to an buffer. */ + +static inline void +buf_append_data (buf, data, last) + struct buffer *buf; + struct buffer_data *data; + struct buffer_data *last; +{ + if (data != NULL) + { + if (buf->data == NULL) + buf->data = data; + else + buf->last->next = data; + buf->last = last; + } +} + +/* + * Copy the contents of file F into buffer_data structures. We can't + * copy directly into an buffer, because we want to handle failure and + * succeess differently. Returns 0 on success, or -2 if out of + * memory, or a status code on error. Since the caller happens to + * know the size of the file, it is passed in as SIZE. On success, + * this function sets *RETP and *LASTP, which may be passed to + * buf_append_data. + */ + +static int +buf_read_file (f, size, retp, lastp) + FILE *f; + long size; + struct buffer_data **retp; + struct buffer_data **lastp; +{ + int status; + + *retp = NULL; + *lastp = NULL; + + while (size > 0) + { + struct buffer_data *data; + int get; + + data = get_buffer_data (); + if (data == NULL) + { + status = -2; + goto error_return; + } + + if (*retp == NULL) + *retp = data; + else + (*lastp)->next = data; + data->next = NULL; + *lastp = data; + + data->bufp = data->text; + data->size = 0; + + if (size > BUFFER_DATA_SIZE) + get = BUFFER_DATA_SIZE; + else + get = size; + + errno = EIO; + if (fread (data->text, get, 1, f) != 1) + { + status = errno; + goto error_return; + } + + data->size += get; + size -= get; + } + + return 0; + + error_return: + if (*retp != NULL) + { + (*lastp)->next = free_buffer_data; + free_buffer_data = *retp; + } + return status; +} + +static int +buf_read_file_to_eof (f, retp, lastp) + FILE *f; + struct buffer_data **retp; + struct buffer_data **lastp; +{ + int status; + + *retp = NULL; + *lastp = NULL; + + while (!feof (f)) + { + struct buffer_data *data; + int get, nread; + + data = get_buffer_data (); + if (data == NULL) + { + status = -2; + goto error_return; + } + + if (*retp == NULL) + *retp = data; + else + (*lastp)->next = data; + data->next = NULL; + *lastp = data; + + data->bufp = data->text; + data->size = 0; + + get = BUFFER_DATA_SIZE; + + errno = EIO; + nread = fread (data->text, 1, get, f); + if (nread == 0 && !feof (f)) + { + status = errno; + goto error_return; + } + + data->size = nread; + } + + return 0; + + error_return: + if (*retp != NULL) + { + (*lastp)->next = free_buffer_data; + free_buffer_data = *retp; + } + return status; +} + +static int +buf_chain_length (buf) + struct buffer_data *buf; +{ + int size = 0; + while (buf) + { + size += buf->size; + buf = buf->next; + } + return size; +} + +/* + * Read an arbitrary amount of data from a file descriptor into an + * input buffer. The file descriptor will be in nonblocking mode, and + * we just grab what we can. Return 0 on success, or -1 on end of + * file, or -2 if out of memory, or an error code. If COUNTP is not + * NULL, *COUNTP is set to the number of bytes read. + */ + +static int +buf_input_data (buf, countp) + struct buffer *buf; + int *countp; +{ + if (buf->output) + abort (); + + if (countp != NULL) + *countp = 0; + + while (1) + { + int get; + int nbytes; + + if (buf->data == NULL + || (buf->last->bufp + buf->last->size + == buf->last->text + BUFFER_DATA_SIZE)) + { + struct buffer_data *data; + + data = get_buffer_data (); + if (data == NULL) + { + (*buf->memory_error) (buf); + return -2; + } + + if (buf->data == NULL) + buf->data = data; + else + buf->last->next = data; + data->next = NULL; + buf->last = data; + + data->bufp = data->text; + data->size = 0; + } + + get = ((buf->last->text + BUFFER_DATA_SIZE) + - (buf->last->bufp + buf->last->size)); + nbytes = read (buf->fd, buf->last->bufp + buf->last->size, get); + if (nbytes <= 0) + { + if (nbytes == 0) + { + /* + * This assumes that we are using POSIX or BSD style + * nonblocking I/O. On System V we will get a zero + * return if there is no data, even when not at EOF. + */ + return -1; + } + + if (errno == EAGAIN +#ifdef EWOULDBLOCK + || errno == EWOULDBLOCK +#endif + ) + return 0; + + return errno; + } + + buf->last->size += nbytes; + if (countp != NULL) + *countp += nbytes; + } + + /*NOTREACHED*/ +} + +/* + * Copy lines from an input buffer to an output buffer. This copies + * all complete lines (characters up to a newline) from INBUF to + * OUTBUF. Each line in OUTBUF is preceded by the character COMMAND + * and a space. + */ + +static void +buf_copy_lines (outbuf, inbuf, command) + struct buffer *outbuf; + struct buffer *inbuf; + int command; +{ + if (! outbuf->output || inbuf->output) + abort (); + + while (1) + { + struct buffer_data *data; + struct buffer_data *nldata; + char *nl; + int len; + + /* See if there is a newline in INBUF. */ + nldata = NULL; + nl = NULL; + for (data = inbuf->data; data != NULL; data = data->next) + { + nl = memchr (data->bufp, '\n', data->size); + if (nl != NULL) + { + nldata = data; + break; + } + } + + if (nldata == NULL) + { + /* There are no more lines in INBUF. */ + return; + } + + /* Put in the command. */ + buf_append_char (outbuf, command); + buf_append_char (outbuf, ' '); + + if (inbuf->data != nldata) + { + /* + * Simply move over all the buffers up to the one containing + * the newline. + */ + for (data = inbuf->data; data->next != nldata; data = data->next) + ; + data->next = NULL; + buf_append_data (outbuf, inbuf->data, data); + inbuf->data = nldata; + } + + /* + * If the newline is at the very end of the buffer, just move + * the buffer onto OUTBUF. Otherwise we must copy the data. + */ + len = nl + 1 - nldata->bufp; + if (len == nldata->size) + { + inbuf->data = nldata->next; + if (inbuf->data == NULL) + inbuf->last = NULL; + + nldata->next = NULL; + buf_append_data (outbuf, nldata, nldata); + } + else + { + buf_output (outbuf, nldata->bufp, len); + nldata->bufp += len; + nldata->size -= len; + } + } +} + +/* + * Copy counted data from one buffer to another. The count is an + * integer, host size, host byte order (it is only used across a + * pipe). If there is enough data, it should be moved over. If there + * is not enough data, it should remain on the original buffer. This + * returns the number of bytes it needs to see in order to actually + * copy something over. + */ + +static int +buf_copy_counted (outbuf, inbuf) + struct buffer *outbuf; + struct buffer *inbuf; +{ + if (! outbuf->output || inbuf->output) + abort (); + + while (1) + { + struct buffer_data *data; + int need; + union + { + char intbuf[sizeof (int)]; + int i; + } u; + char *intp; + int count; + struct buffer_data *start; + int startoff; + struct buffer_data *stop; + int stopwant; + + /* See if we have enough bytes to figure out the count. */ + need = sizeof (int); + intp = u.intbuf; + for (data = inbuf->data; data != NULL; data = data->next) + { + if (data->size >= need) + { + memcpy (intp, data->bufp, need); + break; + } + memcpy (intp, data->bufp, data->size); + intp += data->size; + need -= data->size; + } + if (data == NULL) + { + /* We don't have enough bytes to form an integer. */ + return need; + } + + count = u.i; + start = data; + startoff = need; + + /* + * We have an integer in COUNT. We have gotten all the data + * from INBUF in all buffers before START, and we have gotten + * STARTOFF bytes from START. See if we have enough bytes + * remaining in INBUF. + */ + need = count - (start->size - startoff); + if (need <= 0) + { + stop = start; + stopwant = count; + } + else + { + for (data = start->next; data != NULL; data = data->next) + { + if (need <= data->size) + break; + need -= data->size; + } + if (data == NULL) + { + /* We don't have enough bytes. */ + return need; + } + stop = data; + stopwant = need; + } + + /* + * We have enough bytes. Free any buffers in INBUF before + * START, and remove STARTOFF bytes from START, so that we can + * forget about STARTOFF. + */ + start->bufp += startoff; + start->size -= startoff; + + if (start->size == 0) + start = start->next; + + if (stop->size == stopwant) + { + stop = stop->next; + stopwant = 0; + } + + while (inbuf->data != start) + { + data = inbuf->data; + inbuf->data = data->next; + data->next = free_buffer_data; + free_buffer_data = data; + } + + /* + * We want to copy over the bytes from START through STOP. We + * only want STOPWANT bytes from STOP. + */ + + if (start != stop) + { + /* Attach the buffers from START through STOP to OUTBUF. */ + for (data = start; data->next != stop; data = data->next) + ; + inbuf->data = stop; + data->next = NULL; + buf_append_data (outbuf, start, data); + } + + if (stopwant > 0) + { + buf_output (outbuf, stop->bufp, stopwant); + stop->bufp += stopwant; + stop->size -= stopwant; + } + } + + /*NOTREACHED*/ +} + +static struct buffer protocol; + +static void +protocol_memory_error (buf) + struct buffer *buf; +{ + error (1, ENOMEM, "Virtual memory exhausted"); +} + +/* + * Process IDs of the subprocess, or negative if that subprocess + * does not exist. + */ +static pid_t command_pid; + +static void +outbuf_memory_error (buf) + struct buffer *buf; +{ + static const char msg[] = "E Fatal server error\n\ +error ENOMEM Virtual memory exhausted.\n"; + if (command_pid > 0) + kill (command_pid, SIGTERM); + + /* + * We have arranged things so that printing this now either will + * be legal, or the "E fatal error" line will get glommed onto the + * end of an existing "E" or "M" response. + */ + + /* If this gives an error, not much we could do. syslog() it? */ + write (STDOUT_FILENO, msg, sizeof (msg) - 1); + server_cleanup (0); + exit (1); +} + +static void +input_memory_error (buf) + struct buffer *buf; +{ + outbuf_memory_error (buf); +} + +/* Execute COMMAND in a subprocess with the approriate funky things done. */ + +static struct fd_set_wrapper { fd_set fds; } command_fds_to_drain; +static int max_command_fd; + +#ifdef SERVER_FLOWCONTROL +static int flowcontrol_pipe[2]; +#endif /* SERVER_FLOWCONTROL */ + +static void +do_cvs_command (command) + int (*command) PROTO((int argc, char **argv)); +{ + /* + * The following file descriptors are set to -1 if that file is not + * currently open. + */ + + /* Data on these pipes is a series of '\n'-terminated lines. */ + int stdout_pipe[2]; + int stderr_pipe[2]; + + /* + * Data on this pipe is a series of counted (see buf_send_counted) + * packets. Each packet must be processed atomically (i.e. not + * interleaved with data from stdout_pipe or stderr_pipe). + */ + int protocol_pipe[2]; + + int dev_null_fd = -1; + + int errs; + + command_pid = -1; + stdout_pipe[0] = -1; + stdout_pipe[1] = -1; + stderr_pipe[0] = -1; + stderr_pipe[1] = -1; + protocol_pipe[0] = -1; + protocol_pipe[1] = -1; + + server_write_entries (); + + if (print_pending_error ()) + goto free_args_and_return; + + /* + * We use a child process which actually does the operation. This + * is so we can intercept its standard output. Even if all of CVS + * were written to go to some special routine instead of writing + * to stdout or stderr, we would still need to do the same thing + * for the RCS commands. + */ + + if (pipe (stdout_pipe) < 0) + { + print_error (errno); + goto error_exit; + } + if (pipe (stderr_pipe) < 0) + { + print_error (errno); + goto error_exit; + } + if (pipe (protocol_pipe) < 0) + { + print_error (errno); + goto error_exit; + } +#ifdef SERVER_FLOWCONTROL + if (pipe (flowcontrol_pipe) < 0) + { + print_error (errno); + goto error_exit; + } + set_nonblock_fd (flowcontrol_pipe[0]); + set_nonblock_fd (flowcontrol_pipe[1]); +#endif /* SERVER_FLOWCONTROL */ + + dev_null_fd = open ("/dev/null", O_RDONLY); + if (dev_null_fd < 0) + { + print_error (errno); + goto error_exit; + } + + /* Don't use vfork; we're not going to exec(). */ + command_pid = fork (); + if (command_pid < 0) + { + print_error (errno); + goto error_exit; + } + if (command_pid == 0) + { + int exitstatus; + + /* Since we're in the child, and the parent is going to take + care of packaging up our error messages, we can clear this + flag. */ + error_use_protocol = 0; + + protocol.data = protocol.last = NULL; + protocol.fd = protocol_pipe[1]; + protocol.output = 1; + protocol.nonblocking = 0; + protocol.memory_error = protocol_memory_error; + + if (dup2 (dev_null_fd, STDIN_FILENO) < 0) + error (1, errno, "can't set up pipes"); + if (dup2 (stdout_pipe[1], STDOUT_FILENO) < 0) + error (1, errno, "can't set up pipes"); + if (dup2 (stderr_pipe[1], STDERR_FILENO) < 0) + error (1, errno, "can't set up pipes"); + close (stdout_pipe[0]); + close (stderr_pipe[0]); + close (protocol_pipe[0]); +#ifdef SERVER_FLOWCONTROL + close (flowcontrol_pipe[1]); +#endif /* SERVER_FLOWCONTROL */ + + /* + * Set this in .bashrc if you want to give yourself time to attach + * to the subprocess with a debugger. + */ + if (getenv ("CVS_SERVER_SLEEP")) + { + int secs = atoi (getenv ("CVS_SERVER_SLEEP")); + sleep (secs); + } + + exitstatus = (*command) (argument_count, argument_vector); + + /* + * When we exit, that will close the pipes, giving an EOF to + * the parent. + */ + exit (exitstatus); + } + + /* OK, sit around getting all the input from the child. */ + { + struct buffer outbuf; + struct buffer stdoutbuf; + struct buffer stderrbuf; + struct buffer protocol_inbuf; + /* Number of file descriptors to check in select (). */ + int num_to_check; + int count_needed = 0; +#ifdef SERVER_FLOWCONTROL + int have_flowcontrolled = 0; +#endif /* SERVER_FLOWCONTROL */ + + FD_ZERO (&command_fds_to_drain.fds); + num_to_check = stdout_pipe[0]; + FD_SET (stdout_pipe[0], &command_fds_to_drain.fds); + if (stderr_pipe[0] > num_to_check) + num_to_check = stderr_pipe[0]; + FD_SET (stderr_pipe[0], &command_fds_to_drain.fds); + if (protocol_pipe[0] > num_to_check) + num_to_check = protocol_pipe[0]; + FD_SET (protocol_pipe[0], &command_fds_to_drain.fds); + if (STDOUT_FILENO > num_to_check) + num_to_check = STDOUT_FILENO; + max_command_fd = num_to_check; + /* + * File descriptors are numbered from 0, so num_to_check needs to + * be one larger than the largest descriptor. + */ + ++num_to_check; + if (num_to_check > FD_SETSIZE) + { + printf ("E internal error: FD_SETSIZE not big enough.\nerror \n"); + goto error_exit; + } + + outbuf.data = outbuf.last = NULL; + outbuf.fd = STDOUT_FILENO; + outbuf.output = 1; + outbuf.nonblocking = 0; + outbuf.memory_error = outbuf_memory_error; + + stdoutbuf.data = stdoutbuf.last = NULL; + stdoutbuf.fd = stdout_pipe[0]; + stdoutbuf.output = 0; + stdoutbuf.nonblocking = 0; + stdoutbuf.memory_error = input_memory_error; + + stderrbuf.data = stderrbuf.last = NULL; + stderrbuf.fd = stderr_pipe[0]; + stderrbuf.output = 0; + stderrbuf.nonblocking = 0; + stderrbuf.memory_error = input_memory_error; + + protocol_inbuf.data = protocol_inbuf.last = NULL; + protocol_inbuf.fd = protocol_pipe[0]; + protocol_inbuf.output = 0; + protocol_inbuf.nonblocking = 0; + protocol_inbuf.memory_error = input_memory_error; + + set_nonblock (&outbuf); + set_nonblock (&stdoutbuf); + set_nonblock (&stderrbuf); + set_nonblock (&protocol_inbuf); + + if (close (stdout_pipe[1]) < 0) + { + print_error (errno); + goto error_exit; + } + stdout_pipe[1] = -1; + + if (close (stderr_pipe[1]) < 0) + { + print_error (errno); + goto error_exit; + } + stderr_pipe[1] = -1; + + if (close (protocol_pipe[1]) < 0) + { + print_error (errno); + goto error_exit; + } + protocol_pipe[1] = -1; + +#ifdef SERVER_FLOWCONTROL + if (close (flowcontrol_pipe[0]) < 0) + { + print_error (errno); + goto error_exit; + } + flowcontrol_pipe[0] = -1; +#endif /* SERVER_FLOWCONTROL */ + + if (close (dev_null_fd) < 0) + { + print_error (errno); + goto error_exit; + } + dev_null_fd = -1; + + while (stdout_pipe[0] >= 0 + || stderr_pipe[0] >= 0 + || protocol_pipe[0] >= 0) + { + fd_set readfds; + fd_set writefds; + int numfds; +#ifdef SERVER_FLOWCONTROL + int bufmemsize; + + /* + * See if we are swamping the remote client and filling our VM. + * Tell child to hold off if we do. + */ + bufmemsize = buf_count_mem (&outbuf); + if (!have_flowcontrolled && (bufmemsize > SERVER_HI_WATER)) + { + if (write(flowcontrol_pipe[1], "S", 1) == 1) + have_flowcontrolled = 1; + } + else if (have_flowcontrolled && (bufmemsize < SERVER_LO_WATER)) + { + if (write(flowcontrol_pipe[1], "G", 1) == 1) + have_flowcontrolled = 0; + } +#endif /* SERVER_FLOWCONTROL */ + + FD_ZERO (&readfds); + FD_ZERO (&writefds); + if (! buf_empty_p (&outbuf)) + FD_SET (STDOUT_FILENO, &writefds); + + if (stdout_pipe[0] >= 0) + { + FD_SET (stdout_pipe[0], &readfds); + } + if (stderr_pipe[0] >= 0) + { + FD_SET (stderr_pipe[0], &readfds); + } + if (protocol_pipe[0] >= 0) + { + FD_SET (protocol_pipe[0], &readfds); + } + + do { + /* This used to select on exceptions too, but as far + as I know there was never any reason to do that and + SCO doesn't let you select on exceptions on pipes. */ + numfds = select (num_to_check, &readfds, &writefds, + (fd_set *)0, (struct timeval *)NULL); + if (numfds < 0 + && errno != EINTR) + { + print_error (errno); + goto error_exit; + } + } while (numfds < 0); + + if (FD_ISSET (STDOUT_FILENO, &writefds)) + { + /* What should we do with errors? syslog() them? */ + buf_send_output (&outbuf); + } + + if (stdout_pipe[0] >= 0 + && (FD_ISSET (stdout_pipe[0], &readfds))) + { + int status; + + status = buf_input_data (&stdoutbuf, (int *) NULL); + + buf_copy_lines (&outbuf, &stdoutbuf, 'M'); + + if (status == -1) + stdout_pipe[0] = -1; + else if (status > 0) + { + print_error (status); + goto error_exit; + } + + /* What should we do with errors? syslog() them? */ + buf_send_output (&outbuf); + } + + if (stderr_pipe[0] >= 0 + && (FD_ISSET (stderr_pipe[0], &readfds))) + { + int status; + + status = buf_input_data (&stderrbuf, (int *) NULL); + + buf_copy_lines (&outbuf, &stderrbuf, 'E'); + + if (status == -1) + stderr_pipe[0] = -1; + else if (status > 0) + { + print_error (status); + goto error_exit; + } + + /* What should we do with errors? syslog() them? */ + buf_send_output (&outbuf); + } + + if (protocol_pipe[0] >= 0 + && (FD_ISSET (protocol_pipe[0], &readfds))) + { + int status; + int count_read; + + status = buf_input_data (&protocol_inbuf, &count_read); + + /* + * We only call buf_copy_counted if we have read + * enough bytes to make it worthwhile. This saves us + * from continually recounting the amount of data we + * have. + */ + count_needed -= count_read; + if (count_needed <= 0) + count_needed = buf_copy_counted (&outbuf, &protocol_inbuf); + + if (status == -1) + protocol_pipe[0] = -1; + else if (status > 0) + { + print_error (status); + goto error_exit; + } + + /* What should we do with errors? syslog() them? */ + buf_send_output (&outbuf); + } + } + + /* + * OK, we've gotten EOF on all the pipes. If there is + * anything left on stdoutbuf or stderrbuf (this could only + * happen if there was no trailing newline), send it over. + */ + if (! buf_empty_p (&stdoutbuf)) + { + buf_append_char (&stdoutbuf, '\n'); + buf_copy_lines (&outbuf, &stdoutbuf, 'M'); + } + if (! buf_empty_p (&stderrbuf)) + { + buf_append_char (&stderrbuf, '\n'); + buf_copy_lines (&outbuf, &stderrbuf, 'E'); + } + if (! buf_empty_p (&protocol_inbuf)) + buf_output0 (&outbuf, + "E Protocol error: uncounted data discarded\n"); + + errs = 0; + + while (command_pid > 0) + { + int status; + pid_t waited_pid; + waited_pid = waitpid (command_pid, &status, 0); + if (waited_pid < 0) + { + /* + * Intentionally ignoring EINTR. Other errors + * "can't happen". + */ + continue; + } + + if (WIFEXITED (status)) + errs += WEXITSTATUS (status); + else + { + int sig = WTERMSIG (status); + /* + * This is really evil, because signals might be numbered + * differently on the two systems. We should be using + * signal names (either of the "Terminated" or the "SIGTERM" + * variety). But cvs doesn't currently use libiberty...we + * could roll our own.... FIXME. + */ + printf ("E Terminated with fatal signal %d\n", sig); + + /* Test for a core dump. Is this portable? */ + if (status & 0x80) + { + printf ("E Core dumped; preserving %s on server.\n\ +E CVS locks may need cleaning up.\n", + server_temp_dir); + dont_delete_temp = 1; + } + ++errs; + } + if (waited_pid == command_pid) + command_pid = -1; + } + + /* + * OK, we've waited for the child. By now all CVS locks are free + * and it's OK to block on the network. + */ + set_block (&outbuf); + buf_send_output (&outbuf); + } + + if (errs) + /* We will have printed an error message already. */ + printf ("error \n"); + else + printf ("ok\n"); + goto free_args_and_return; + + error_exit: + if (command_pid > 0) + kill (command_pid, SIGTERM); + + while (command_pid > 0) + { + pid_t waited_pid; + waited_pid = waitpid (command_pid, (int *) 0, 0); + if (waited_pid < 0 && errno == EINTR) + continue; + if (waited_pid == command_pid) + command_pid = -1; + } + + close (dev_null_fd); + close (protocol_pipe[0]); + close (protocol_pipe[1]); + close (stderr_pipe[0]); + close (stderr_pipe[1]); + close (stdout_pipe[0]); + close (stdout_pipe[1]); + + free_args_and_return: + /* Now free the arguments. */ + { + /* argument_vector[0] is a dummy argument, we don't mess with it. */ + char **cp; + for (cp = argument_vector + 1; + cp < argument_vector + argument_count; + ++cp) + free (*cp); + + argument_count = 1; + } + return; +} + +#ifdef SERVER_FLOWCONTROL +/* + * Called by the child at convenient points in the server's execution for + * the server child to block.. ie: when it has no locks active. + */ +void +server_pause_check() +{ + int paused = 0; + char buf[1]; + + while (read (flowcontrol_pipe[0], buf, 1) == 1) + { + if (*buf == 'S') /* Stop */ + paused = 1; + else if (*buf == 'G') /* Go */ + paused = 0; + else + return; /* ??? */ + } + while (paused) { + int numfds, numtocheck; + fd_set fds; + + FD_ZERO (&fds); + FD_SET (flowcontrol_pipe[0], &fds); + numtocheck = flowcontrol_pipe[0] + 1; + + do { + numfds = select (numtocheck, &fds, (fd_set *)0, + (fd_set *)0, (struct timeval *)NULL); + if (numfds < 0 + && errno != EINTR) + { + print_error (errno); + return; + } + } while (numfds < 0); + + if (FD_ISSET (flowcontrol_pipe[0], &fds)) + { + while (read (flowcontrol_pipe[0], buf, 1) == 1) + { + if (*buf == 'S') /* Stop */ + paused = 1; + else if (*buf == 'G') /* Go */ + paused = 0; + else + return; /* ??? */ + } + } + } +} +#endif /* SERVER_FLOWCONTROL */ + +static void output_dir PROTO((char *, char *)); + +static void +output_dir (update_dir, repository) + char *update_dir; + char *repository; +{ + if (use_dir_and_repos) + { + if (update_dir[0] == '\0') + buf_output0 (&protocol, "."); + else + buf_output0 (&protocol, update_dir); + buf_output0 (&protocol, "/\n"); + } + buf_output0 (&protocol, repository); + buf_output0 (&protocol, "/"); +} + +/* + * Entries line that we are squirreling away to send to the client when + * we are ready. + */ +static char *entries_line; + +/* + * File which has been Scratch_File'd, we are squirreling away that fact + * to inform the client when we are ready. + */ +static char *scratched_file; + +/* + * The scratched_file will need to be removed as well as having its entry + * removed. + */ +static int kill_scratched_file; + +void +server_register (name, version, timestamp, options, tag, date, conflict) + char *name; + char *version; + char *timestamp; + char *options; + char *tag; + char *date; + char *conflict; +{ + int len; + + if (trace) + { + (void) fprintf (stderr, + "%c-> server_register(%s, %s, %s, %s, %s, %s, %s)\n", + (server_active) ? 'S' : ' ', /* silly */ + name, version, timestamp, options, tag ? tag : "", + date ? date : "", conflict ? conflict : ""); + } + + if (options == NULL) + options = ""; + + if (entries_line != NULL) + { + /* + * If CVS decides to Register it more than once (which happens + * on "cvs update foo/foo.c" where foo and foo.c are already + * checked out), use the last of the entries lines Register'd. + */ + free (entries_line); + } + + /* + * I have reports of Scratch_Entry and Register both happening, in + * two different cases. Using the last one which happens is almost + * surely correct; I haven't tracked down why they both happen (or + * even verified that they are for the same file). + */ + if (scratched_file != NULL) + { + free (scratched_file); + scratched_file = NULL; + } + + len = (strlen (name) + strlen (version) + strlen (options) + 80); + if (tag) + len += strlen (tag); + if (date) + len += strlen (date); + + entries_line = xmalloc (len); + sprintf (entries_line, "/%s/%s/", name, version); + if (conflict != NULL) + { + strcat (entries_line, "+="); + } + strcat (entries_line, "/"); + strcat (entries_line, options); + strcat (entries_line, "/"); + if (tag != NULL) + { + strcat (entries_line, "T"); + strcat (entries_line, tag); + } + else if (date != NULL) + { + strcat (entries_line, "D"); + strcat (entries_line, date); + } +} + +void +server_scratch (fname) + char *fname; +{ + /* + * I have reports of Scratch_Entry and Register both happening, in + * two different cases. Using the last one which happens is almost + * surely correct; I haven't tracked down why they both happen (or + * even verified that they are for the same file). + */ + if (entries_line != NULL) + { + free (entries_line); + entries_line = NULL; + } + + if (scratched_file != NULL) + { + buf_output0 (&protocol, + "E CVS server internal error: duplicate Scratch_Entry\n"); + buf_send_counted (&protocol); + return; + } + scratched_file = xstrdup (fname); + kill_scratched_file = 1; +} + +void +server_scratch_entry_only () +{ + kill_scratched_file = 0; +} + +/* Print a new entries line, from a previous server_register. */ +static void +new_entries_line () +{ + if (entries_line) + { + buf_output0 (&protocol, entries_line); + buf_output (&protocol, "\n", 1); + } + else + /* Return the error message as the Entries line. */ + buf_output0 (&protocol, + "CVS server internal error: Register missing\n"); + free (entries_line); + entries_line = NULL; +} + +static void +serve_ci (arg) + char *arg; +{ + do_cvs_command (commit); +} + +void +server_checked_in (file, update_dir, repository) + char *file; + char *update_dir; + char *repository; +{ + if (noexec) + return; + if (scratched_file != NULL && entries_line == NULL) + { + /* + * This happens if we are now doing a "cvs remove" after a previous + * "cvs add" (without a "cvs ci" in between). + */ + buf_output0 (&protocol, "Remove-entry "); + output_dir (update_dir, repository); + buf_output0 (&protocol, file); + buf_output (&protocol, "\n", 1); + free (scratched_file); + scratched_file = NULL; + } + else + { + buf_output0 (&protocol, "Checked-in "); + output_dir (update_dir, repository); + buf_output0 (&protocol, file); + buf_output (&protocol, "\n", 1); + new_entries_line (); + } + buf_send_counted (&protocol); +} + +void +server_update_entries (file, update_dir, repository, updated) + char *file; + char *update_dir; + char *repository; + enum server_updated_arg4 updated; +{ + if (noexec) + return; + if (updated == SERVER_UPDATED) + buf_output0 (&protocol, "Checked-in "); + else + { + if (!supported_response ("New-entry")) + return; + buf_output0 (&protocol, "New-entry "); + } + + output_dir (update_dir, repository); + buf_output0 (&protocol, file); + buf_output (&protocol, "\n", 1); + new_entries_line (); + buf_send_counted (&protocol); +} + +static void +serve_update (arg) + char *arg; +{ + do_cvs_command (update); +} + +static void +serve_diff (arg) + char *arg; +{ + do_cvs_command (diff); +} + +static void +serve_log (arg) + char *arg; +{ + do_cvs_command (cvslog); +} + +static void +serve_add (arg) + char *arg; +{ + do_cvs_command (add); +} + +static void +serve_remove (arg) + char *arg; +{ + do_cvs_command (cvsremove); +} + +static void +serve_status (arg) + char *arg; +{ + do_cvs_command (status); +} + +static void +serve_rdiff (arg) + char *arg; +{ + do_cvs_command (patch); +} + +static void +serve_tag (arg) + char *arg; +{ + do_cvs_command (tag); +} + +static void +serve_rtag (arg) + char *arg; +{ + do_cvs_command (rtag); +} + +static void +serve_import (arg) + char *arg; +{ + do_cvs_command (import); +} + +static void +serve_admin (arg) + char *arg; +{ + do_cvs_command (admin); +} + +static void +serve_history (arg) + char *arg; +{ + do_cvs_command (history); +} + +static void +serve_release (arg) + char *arg; +{ + do_cvs_command (release); +} + +static void +serve_co (arg) + char *arg; +{ + char *tempdir; + int status; + + if (print_pending_error ()) + return; + + if (!isdir (CVSADM)) + { + /* + * The client has not sent a "Repository" line. Check out + * into a pristine directory. + */ + tempdir = malloc (strlen (server_temp_dir) + 80); + if (tempdir == NULL) + { + printf ("E Out of memory\n"); + return; + } + strcpy (tempdir, server_temp_dir); + strcat (tempdir, "/checkout-dir"); + status = mkdir_p (tempdir); + if (status != 0 && status != EEXIST) + { + printf ("E Cannot create %s\n", tempdir); + print_error (errno); + free (tempdir); + return; + } + + if (chdir (tempdir) < 0) + { + printf ("E Cannot change to directory %s\n", tempdir); + print_error (errno); + free (tempdir); + return; + } + free (tempdir); + } + do_cvs_command (checkout); +} + +static void +serve_export (arg) + char *arg; +{ + /* Tell checkout() to behave like export not checkout. */ + command_name = "export"; + serve_co (arg); +} + +void +server_copy_file (file, update_dir, repository, newfile) + char *file; + char *update_dir; + char *repository; + char *newfile; +{ + if (!supported_response ("Copy-file")) + return; + buf_output0 (&protocol, "Copy-file "); + output_dir (update_dir, repository); + buf_output0 (&protocol, file); + buf_output0 (&protocol, "\n"); + buf_output0 (&protocol, newfile); + buf_output0 (&protocol, "\n"); +} + +void +server_updated (file, update_dir, repository, updated, file_info, checksum) + char *file; + char *update_dir; + char *repository; + enum server_updated_arg4 updated; + struct stat *file_info; + unsigned char *checksum; +{ + char *short_pathname; + + if (noexec) + return; + + short_pathname = xmalloc (strlen (update_dir) + strlen (file) + 10); + if (update_dir[0] == '\0') + strcpy (short_pathname, file); + else + sprintf (short_pathname, "%s/%s", update_dir, file); + + if (entries_line != NULL && scratched_file == NULL) + { + FILE *f; + struct stat sb; + struct buffer_data *list, *last; + unsigned long size; + char size_text[80]; + + if (stat (file, &sb) < 0) + { + if (existence_error (errno)) + { + /* + * If we have a sticky tag for a branch on which the + * file is dead, and cvs update the directory, it gets + * a T_CHECKOUT but no file. So in this case just + * forget the whole thing. + */ + free (entries_line); + entries_line = NULL; + goto done; + } + error (1, errno, "reading %s", short_pathname); + } + + if (checksum != NULL) + { + static int checksum_supported = -1; + + if (checksum_supported == -1) + { + checksum_supported = supported_response ("Checksum"); + } + + if (checksum_supported) + { + int i; + char buf[3]; + + buf_output0 (&protocol, "Checksum "); + for (i = 0; i < 16; i++) + { + sprintf (buf, "%02x", (unsigned int) checksum[i]); + buf_output0 (&protocol, buf); + } + buf_append_char (&protocol, '\n'); + } + } + + if (updated == SERVER_UPDATED) + buf_output0 (&protocol, "Updated "); + else if (updated == SERVER_MERGED) + buf_output0 (&protocol, "Merged "); + else if (updated == SERVER_PATCHED) + buf_output0 (&protocol, "Patched "); + else + abort (); + output_dir (update_dir, repository); + buf_output0 (&protocol, file); + buf_output (&protocol, "\n", 1); + + new_entries_line (); + + { + char *mode_string; + + /* FIXME: When we check out files the umask of the server + (set in .bashrc if rsh is in use, or set in main.c in + the kerberos case, I think) affects what mode we send, + and it shouldn't. */ + if (file_info != NULL) + mode_string = mode_to_string (file_info->st_mode); + else + mode_string = mode_to_string (sb.st_mode); + buf_output0 (&protocol, mode_string); + buf_output0 (&protocol, "\n"); + free (mode_string); + } + + list = last = NULL; + size = 0; + if (sb.st_size > 0) + { + if (gzip_level + /* + * For really tiny files, the gzip process startup + * time will outweigh the compression savings. This + * might be computable somehow; using 100 here is just + * a first approximation. + */ + && sb.st_size > 100) + { + int status, fd, gzip_status; + pid_t gzip_pid; + + fd = open (file, O_RDONLY, 0); + if (fd < 0) + error (1, errno, "reading %s", short_pathname); + fd = filter_through_gzip (fd, 1, gzip_level, &gzip_pid); + f = fdopen (fd, "r"); + status = buf_read_file_to_eof (f, &list, &last); + size = buf_chain_length (list); + if (status == -2) + (*protocol.memory_error) (&protocol); + else if (status != 0) + error (1, ferror (f) ? errno : 0, "reading %s", + short_pathname); + if (fclose (f) == EOF) + error (1, errno, "reading %s", short_pathname); + if (waitpid (gzip_pid, &gzip_status, 0) == -1) + error (1, errno, "waiting for gzip process %d", gzip_pid); + else if (gzip_status != 0) + error (1, 0, "gzip exited %d", gzip_status); + /* Prepending length with "z" is flag for using gzip here. */ + buf_output0 (&protocol, "z"); + } + else + { + long status; + + size = sb.st_size; + f = fopen (file, "r"); + if (f == NULL) + error (1, errno, "reading %s", short_pathname); + status = buf_read_file (f, sb.st_size, &list, &last); + if (status == -2) + (*protocol.memory_error) (&protocol); + else if (status != 0) + error (1, ferror (f) ? errno : 0, "reading %s", + short_pathname); + if (fclose (f) == EOF) + error (1, errno, "reading %s", short_pathname); + } + } + + sprintf (size_text, "%lu\n", size); + buf_output0 (&protocol, size_text); + + buf_append_data (&protocol, list, last); + /* Note we only send a newline here if the file ended with one. */ + + /* + * Avoid using up too much disk space for temporary files. + * A file which does not exist indicates that the file is up-to-date, + * which is now the case. If this is SERVER_MERGED, the file is + * not up-to-date, and we indicate that by leaving the file there. + * I'm thinking of cases like "cvs update foo/foo.c foo". + */ + if ((updated == SERVER_UPDATED || updated == SERVER_PATCHED) + /* But if we are joining, we'll need the file when we call + join_file. */ + && !joining ()) + unlink (file); + } + else if (scratched_file != NULL && entries_line == NULL) + { + if (strcmp (scratched_file, file) != 0) + error (1, 0, + "CVS server internal error: `%s' vs. `%s' scratched", + scratched_file, + file); + free (scratched_file); + scratched_file = NULL; + + if (kill_scratched_file) + buf_output0 (&protocol, "Removed "); + else + buf_output0 (&protocol, "Remove-entry "); + output_dir (update_dir, repository); + buf_output0 (&protocol, file); + buf_output (&protocol, "\n", 1); + } + else if (scratched_file == NULL && entries_line == NULL) + { + /* + * This can happen with death support if we were processing + * a dead file in a checkout. + */ + } + else + error (1, 0, + "CVS server internal error: Register *and* Scratch_Entry.\n"); + buf_send_counted (&protocol); + done: + free (short_pathname); +} + +void +server_set_entstat (update_dir, repository) + char *update_dir; + char *repository; +{ + static int set_static_supported = -1; + if (set_static_supported == -1) + set_static_supported = supported_response ("Set-static-directory"); + if (!set_static_supported) return; + + buf_output0 (&protocol, "Set-static-directory "); + output_dir (update_dir, repository); + buf_output0 (&protocol, "\n"); + buf_send_counted (&protocol); +} + +void +server_clear_entstat (update_dir, repository) + char *update_dir; + char *repository; +{ + static int clear_static_supported = -1; + if (clear_static_supported == -1) + clear_static_supported = supported_response ("Clear-static-directory"); + if (!clear_static_supported) return; + + if (noexec) + return; + + buf_output0 (&protocol, "Clear-static-directory "); + output_dir (update_dir, repository); + buf_output0 (&protocol, "\n"); + buf_send_counted (&protocol); +} + +void +server_set_sticky (update_dir, repository, tag, date) + char *update_dir; + char *repository; + char *tag; + char *date; +{ + static int set_sticky_supported = -1; + if (set_sticky_supported == -1) + set_sticky_supported = supported_response ("Set-sticky"); + if (!set_sticky_supported) return; + + if (noexec) + return; + + if (tag == NULL && date == NULL) + { + buf_output0 (&protocol, "Clear-sticky "); + output_dir (update_dir, repository); + buf_output0 (&protocol, "\n"); + } + else + { + buf_output0 (&protocol, "Set-sticky "); + output_dir (update_dir, repository); + buf_output0 (&protocol, "\n"); + if (tag != NULL) + { + buf_output0 (&protocol, "T"); + buf_output0 (&protocol, tag); + } + else + { + buf_output0 (&protocol, "D"); + buf_output0 (&protocol, date); + } + buf_output0 (&protocol, "\n"); + } + buf_send_counted (&protocol); +} + +static void +serve_gzip_contents (arg) + char *arg; +{ + int level; + level = atoi (arg); + if (level == 0) + level = 6; + gzip_level = level; +} + +static void +serve_ignore (arg) + char *arg; +{ + /* + * Just ignore this command. This is used to support the + * update-patches command, which is not a real command, but a signal + * to the client that update will accept the -u argument. + */ +} + +static int +expand_proc (pargc, argv, where, mwhere, mfile, shorten, + local_specified, omodule, msg) + int *pargc; + char **argv; + char *where; + char *mwhere; + char *mfile; + int shorten; + int local_specified; + char *omodule; + char *msg; +{ + int i; + char *dir = argv[0]; + + /* If mwhere has been specified, the thing we're expanding is a + module -- just return its name so the client will ask for the + right thing later. If it is an alias or a real directory, + mwhere will not be set, so send out the appropriate + expansion. */ + + if (mwhere != NULL) + printf ("Module-expansion %s\n", mwhere); + else + { + /* We may not need to do this anymore -- check the definition + of aliases before removing */ + if (*pargc == 1) + printf ("Module-expansion %s\n", dir); + else + for (i = 1; i < *pargc; ++i) + printf ("Module-expansion %s/%s\n", dir, argv[i]); + } + return 0; +} + +static void +serve_expand_modules (arg) + char *arg; +{ + int i; + int err; + DBM *db; + err = 0; + + /* + * FIXME: error handling is bogus; do_module can write to stdout and/or + * stderr and we're not using do_cvs_command. + */ + + server_expanding = 1; + db = open_module (); + for (i = 1; i < argument_count; i++) + err += do_module (db, argument_vector[i], + CHECKOUT, "Updating", expand_proc, + NULL, 0, 0, 0, + (char *) NULL); + close_module (db); + server_expanding = 0; + { + /* argument_vector[0] is a dummy argument, we don't mess with it. */ + char **cp; + for (cp = argument_vector + 1; + cp < argument_vector + argument_count; + ++cp) + free (*cp); + + argument_count = 1; + } + if (err) + /* We will have printed an error message already. */ + printf ("error \n"); + else + printf ("ok\n"); +} + +void +server_prog (dir, name, which) + char *dir; + char *name; + enum progs which; +{ + if (!supported_response ("Set-checkin-prog")) + { + printf ("E \ +warning: this client does not support -i or -u flags in the modules file.\n"); + return; + } + switch (which) + { + case PROG_CHECKIN: + printf ("Set-checkin-prog "); + break; + case PROG_UPDATE: + printf ("Set-update-prog "); + break; + } + printf ("%s\n%s\n", dir, name); +} + +static void +serve_checkin_prog (arg) + char *arg; +{ + FILE *f; + f = fopen (CVSADM_CIPROG, "w+"); + if (f == NULL) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_CIPROG)); + sprintf(pending_error_text, "E cannot open %s", CVSADM_CIPROG); + return; + } + if (fprintf (f, "%s\n", arg) < 0) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_CIPROG)); + sprintf(pending_error_text, "E cannot write to %s", CVSADM_CIPROG); + return; + } + if (fclose (f) == EOF) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_CIPROG)); + sprintf(pending_error_text, "E cannot close %s", CVSADM_CIPROG); + return; + } +} + +static void +serve_update_prog (arg) + char *arg; +{ + FILE *f; + f = fopen (CVSADM_UPROG, "w+"); + if (f == NULL) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_UPROG)); + sprintf(pending_error_text, "E cannot open %s", CVSADM_UPROG); + return; + } + if (fprintf (f, "%s\n", arg) < 0) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_UPROG)); + sprintf(pending_error_text, "E cannot write to %s", CVSADM_UPROG); + return; + } + if (fclose (f) == EOF) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(CVSADM_UPROG)); + sprintf(pending_error_text, "E cannot close %s", CVSADM_UPROG); + return; + } +} + +static void serve_valid_requests PROTO((char *arg)); + +#endif /* SERVER_SUPPORT */ +#if defined(SERVER_SUPPORT) || defined(CLIENT_SUPPORT) + +/* + * Parts of this table are shared with the client code, + * but the client doesn't need to know about the handler + * functions. + */ + +struct request requests[] = +{ +#ifdef SERVER_SUPPORT +#define REQ_LINE(n, f, s) {n, f, s} +#else +#define REQ_LINE(n, f, s) {n, s} +#endif + + REQ_LINE("Root", serve_root, rq_essential), + REQ_LINE("Valid-responses", serve_valid_responses, rq_essential), + REQ_LINE("valid-requests", serve_valid_requests, rq_essential), + REQ_LINE("Repository", serve_repository, rq_essential), + REQ_LINE("Directory", serve_directory, rq_optional), + REQ_LINE("Max-dotdot", serve_max_dotdot, rq_optional), + REQ_LINE("Static-directory", serve_static_directory, rq_optional), + REQ_LINE("Sticky", serve_sticky, rq_optional), + REQ_LINE("Checkin-prog", serve_checkin_prog, rq_optional), + REQ_LINE("Update-prog", serve_update_prog, rq_optional), + REQ_LINE("Entry", serve_entry, rq_essential), + REQ_LINE("Modified", serve_modified, rq_essential), + REQ_LINE("Lost", serve_lost, rq_optional), + REQ_LINE("UseUnchanged", serve_enable_unchanged, rq_enableme), + REQ_LINE("Unchanged", serve_unchanged, rq_optional), + REQ_LINE("Argument", serve_argument, rq_essential), + REQ_LINE("Argumentx", serve_argumentx, rq_essential), + REQ_LINE("Global_option", serve_global_option, rq_optional), + REQ_LINE("expand-modules", serve_expand_modules, rq_optional), + REQ_LINE("ci", serve_ci, rq_essential), + REQ_LINE("co", serve_co, rq_essential), + REQ_LINE("update", serve_update, rq_essential), + REQ_LINE("diff", serve_diff, rq_optional), + REQ_LINE("log", serve_log, rq_optional), + REQ_LINE("add", serve_add, rq_optional), + REQ_LINE("remove", serve_remove, rq_optional), + REQ_LINE("update-patches", serve_ignore, rq_optional), + REQ_LINE("gzip-file-contents", serve_gzip_contents, rq_optional), + REQ_LINE("status", serve_status, rq_optional), + REQ_LINE("rdiff", serve_rdiff, rq_optional), + REQ_LINE("tag", serve_tag, rq_optional), + REQ_LINE("rtag", serve_rtag, rq_optional), + REQ_LINE("import", serve_import, rq_optional), + REQ_LINE("admin", serve_admin, rq_optional), + REQ_LINE("export", serve_export, rq_optional), + REQ_LINE("history", serve_history, rq_optional), + REQ_LINE("release", serve_release, rq_optional), + REQ_LINE(NULL, NULL, rq_optional) + +#undef REQ_LINE +}; + +#endif /* SERVER_SUPPORT or CLIENT_SUPPORT */ +#ifdef SERVER_SUPPORT + +static void +serve_valid_requests (arg) + char *arg; +{ + struct request *rq; + if (print_pending_error ()) + return; + printf ("Valid-requests"); + for (rq = requests; rq->name != NULL; rq++) + if (rq->func != NULL) + printf (" %s", rq->name); + printf ("\nok\n"); +} + +#ifdef sun +/* + * Delete temporary files. SIG is the signal making this happen, or + * 0 if not called as a result of a signal. + */ +static int command_pid_is_dead; +static void wait_sig (sig) + int sig; +{ + int status; + pid_t r = wait (&status); + if (r == command_pid) + command_pid_is_dead++; +} +#endif + +void +server_cleanup (sig) + int sig; +{ + /* Do "rm -rf" on the temp directory. */ + int len; + char *cmd; + char *temp_dir; + + if (dont_delete_temp) + return; + + /* What a bogus kludge. This disgusting code makes all kinds of + assumptions about SunOS, and is only for a bug in that system. + So only enable it on Suns. */ +#ifdef sun + if (command_pid > 0) { + /* To avoid crashes on SunOS due to bugs in SunOS tmpfs + triggered by the use of rename() in RCS, wait for the + subprocess to die. Unfortunately, this means draining output + while waiting for it to unblock the signal we sent it. Yuck! */ + int status; + pid_t r; + + signal (SIGCHLD, wait_sig); + if (sig) + /* Perhaps SIGTERM would be more correct. But the child + process will delay the SIGINT delivery until its own + children have exited. */ + kill (command_pid, SIGINT); + /* The caller may also have sent a signal to command_pid, so + always try waiting. First, though, check and see if it's still + there.... */ + do_waitpid: + r = waitpid (command_pid, &status, WNOHANG); + if (r == 0) + ; + else if (r == command_pid) + command_pid_is_dead++; + else if (r == -1) + switch (errno) { + case ECHILD: + command_pid_is_dead++; + break; + case EINTR: + goto do_waitpid; + } + else + /* waitpid should always return one of the above values */ + abort (); + while (!command_pid_is_dead) { + struct timeval timeout; + struct fd_set_wrapper readfds; + char buf[100]; + int i; + + /* Use a non-zero timeout to avoid eating up CPU cycles. */ + timeout.tv_sec = 2; + timeout.tv_usec = 0; + readfds = command_fds_to_drain; + switch (select (max_command_fd + 1, &readfds.fds, + (fd_set *)0, (fd_set *)0, + &timeout)) { + case -1: + if (errno != EINTR) + abort (); + case 0: + /* timeout */ + break; + case 1: + for (i = 0; i <= max_command_fd; i++) + { + if (!FD_ISSET (i, &readfds.fds)) + continue; + /* this fd is non-blocking */ + while (read (i, buf, sizeof (buf)) >= 1) + ; + } + break; + default: + abort (); + } + } + } +#endif + + /* This might be set by the user in ~/.bashrc, ~/.cshrc, etc. */ + temp_dir = getenv ("TMPDIR"); + if (temp_dir == NULL || temp_dir[0] == '\0') + temp_dir = "/tmp"; + chdir(temp_dir); + + len = strlen (server_temp_dir) + 80; + cmd = malloc (len); + if (cmd == NULL) + { + printf ("E Cannot delete %s on server; out of memory\n", + server_temp_dir); + return; + } + sprintf (cmd, "rm -rf %s", server_temp_dir); + system (cmd); + free (cmd); +} + +int server_active = 0; +int server_expanding = 0; + +int +server (argc, argv) + int argc; + char **argv; +{ + if (argc == -1) + { + static const char *const msg[] = + { + "Usage: %s %s\n", + " Normally invoked by a cvs client on a remote machine.\n", + NULL + }; + usage (msg); + } + /* Ignore argc and argv. They might be from .cvsrc. */ + + /* Since we're in the server parent process, error should use the + protocol to report error messages. */ + error_use_protocol = 1; + + /* + * Put Rcsbin at the start of PATH, so that rcs programs can find + * themselves. + */ +#ifdef HAVE_PUTENV + if (Rcsbin != NULL && *Rcsbin) + { + char *p; + char *env; + + p = getenv ("PATH"); + if (p != NULL) + { + env = malloc (strlen (Rcsbin) + strlen (p) + sizeof "PATH=:"); + if (env != NULL) + sprintf (env, "PATH=%s:%s", Rcsbin, p); + } + else + { + env = malloc (strlen (Rcsbin) + sizeof "PATH="); + if (env != NULL) + sprintf (env, "PATH=%s", Rcsbin); + } + if (env == NULL) + { + printf ("E Fatal server error, aborting.\n\ +error ENOMEM Virtual memory exhausted.\n"); + exit (1); + } + putenv (env); + } +#endif + + /* OK, now figure out where we stash our temporary files. */ + { + char *p; + + /* This might be set by the user in ~/.bashrc, ~/.cshrc, etc. */ + char *temp_dir = getenv ("TMPDIR"); + if (temp_dir == NULL || temp_dir[0] == '\0') + temp_dir = "/tmp"; + + server_temp_dir = malloc (strlen (temp_dir) + 80); + if (server_temp_dir == NULL) + { + /* + * Strictly speaking, we're not supposed to output anything + * now. But we're about to exit(), give it a try. + */ + printf ("E Fatal server error, aborting.\n\ +error ENOMEM Virtual memory exhausted.\n"); + exit (1); + } + strcpy (server_temp_dir, temp_dir); + + /* Remove a trailing slash from TMPDIR if present. */ + p = server_temp_dir + strlen (server_temp_dir) - 1; + if (*p == '/') + *p = '\0'; + + /* + * I wanted to use cvs-serv/PID, but then you have to worry about + * the permissions on the cvs-serv directory being right. So + * use cvs-servPID. + */ + strcat (server_temp_dir, "/cvs-serv"); + + p = server_temp_dir + strlen (server_temp_dir); + sprintf (p, "%d", getpid ()); + } + + (void) SIG_register (SIGHUP, server_cleanup); + (void) SIG_register (SIGINT, server_cleanup); + (void) SIG_register (SIGQUIT, server_cleanup); + (void) SIG_register (SIGPIPE, server_cleanup); + (void) SIG_register (SIGTERM, server_cleanup); + + /* Now initialize our argument vector (for arguments from the client). */ + + /* Small for testing. */ + argument_vector_size = 1; + argument_vector = + (char **) malloc (argument_vector_size * sizeof (char *)); + if (argument_vector == NULL) + { + /* + * Strictly speaking, we're not supposed to output anything + * now. But we're about to exit(), give it a try. + */ + printf ("E Fatal server error, aborting.\n\ +error ENOMEM Virtual memory exhausted.\n"); + exit (1); + } + + argument_count = 1; + argument_vector[0] = "Dummy argument 0"; + + server_active = 1; + while (1) + { + char *cmd, *orig_cmd; + struct request *rq; + + orig_cmd = cmd = read_line (stdin); + if (cmd == NULL) + break; + if (cmd == NO_MEM_ERROR) + { + printf ("E Fatal server error, aborting.\n\ +error ENOMEM Virtual memory exhausted.\n"); + break; + } + for (rq = requests; rq->name != NULL; ++rq) + if (strncmp (cmd, rq->name, strlen (rq->name)) == 0) + { + int len = strlen (rq->name); + if (cmd[len] == '\0') + cmd += len; + else if (cmd[len] == ' ') + cmd += len + 1; + else + /* + * The first len characters match, but it's a different + * command. e.g. the command is "cooperate" but we matched + * "co". + */ + continue; + (*rq->func) (cmd); + break; + } + if (rq->name == NULL) + { + if (!print_pending_error ()) + printf ("error unrecognized request `%s'\n", cmd); + } + free (orig_cmd); + } + server_cleanup (0); + return 0; +} + + +#ifdef AUTH_SERVER_SUPPORT + +/* This was test code, which we may need again. */ +#if 0 + /* If we were invoked this way, then stdin comes from the + client and stdout/stderr writes to it. */ + int c; + while ((c = getc (stdin)) != EOF && c != '*') + { + printf ("%c", toupper (c)); + fflush (stdout); + } + exit (0); +#endif /* 1/0 */ + + +/* + * 0 means no entry found for this user. + * 1 means entry found and password matches. + * 2 means entry found, but password does not match. + */ +int +check_repository_password (username, password, repository) + char *username, *password, *repository; +{ + int retval = 0; + FILE *fp; + char *filename; + char linebuf[MAXLINELEN]; + int found_it = 0, len; + + filename = xmalloc (strlen (repository) + + 1 + + strlen ("CVSROOT") + + 1 + + strlen ("passwd") + + 1); + + strcpy (filename, repository); + strcat (filename, "/CVSROOT"); + strcat (filename, "/passwd"); + + fp = fopen (filename, "r"); + if (fp == NULL) + { + /* This is ok -- the cvs passwd file might not exist. */ + fclose (fp); + return 0; + } + + /* Look for a relevant line -- one with this user's name. */ + len = strlen (username); + while (fgets (linebuf, MAXPATHLEN - 1, fp)) + { + if ((strncmp (linebuf, username, len) == 0) + && (linebuf[len] == ':')) + { + found_it = 1; + break; + } + } + fclose (fp); + + /* If found_it != 0, then linebuf contains the information we need. */ + if (found_it) + { + char *found_password; + + strtok (linebuf, ":"); + found_password = strtok (NULL, ": \n"); + + if (strcmp (found_password, crypt (password, found_password)) == 0) + retval = 1; + else + retval = 2; + } + else + retval = 0; + + free (filename); + + return retval; +} + + +/* Return 1 if password matches, else 0. */ +int +check_password (username, password, repository) + char *username, *password, *repository; +{ + int rc; + + /* First we see if this user has a password in the CVS-specific + password file. If so, that's enough to authenticate with. If + not, we'll check /etc/passwd. */ + + rc = check_repository_password (username, password, repository); + + if (rc == 1) + return 1; + else if (rc == 2) + return 0; + else if (rc == 0) + { + /* No cvs password found, so try /etc/passwd. */ + + struct passwd *pw; + char *found_passwd; + + pw = getpwnam (username); + if (pw == NULL) + { + printf ("E Fatal error, aborting.\n" + "error 0 %s: no such user\n", username); + exit (1); + } + found_passwd = pw->pw_passwd; + + if (found_passwd && *found_passwd) + return (! strcmp (found_passwd, crypt (password, found_passwd))); + else if (password && *password) + return 1; + else + return 0; + } + else + { + /* Something strange happened. We don't know what it was, but + we certainly won't grant authorization. */ + return 0; + } +} + + +/* Read username and password from client (i.e., stdin). + If correct, then switch to run as that user and send an ACK to the + client via stdout, else send NACK and die. */ +void +authenticate_connection () +{ + int len; + char tmp[PATH_MAX]; + char repository[PATH_MAX]; + char username[PATH_MAX]; + char password[PATH_MAX]; + char server_user[PATH_MAX]; + struct passwd *pw; + + /* The Authentication Protocol. Client sends: + * + * BEGIN AUTH REQUEST\n + * <REPOSITORY>\n + * <USERNAME>\n + * <PASSWORD>\n + * END AUTH REQUEST\n + * + * Server uses above information to authenticate, then sends + * + * I LOVE YOU\n + * + * if it grants access, else + * + * I HATE YOU\n + * + * if it denies access (and it exits if denying). + * + * Note that the actual client/server protocol has not started up + * yet, because we haven't authenticated! Therefore, there are + * certain things we can't take for granted. For example, don't use + * error() because `error_use_protocol' has not yet been set by + * server(). + * + * We need to know where the repository is too, to look up the + * password in the special CVS passwd file before we try + * /etc/passwd. However, the repository is normally transmitted in + * the regular client/server protocol, which has not yet started, + * blah blah blah. This is why the client transmits the repository + * as part of the "authentication protocol". Thus, the repository + * will be redundantly retransmitted later, but that's no big deal. + */ + + /* Make sure the protocol starts off on the right foot... */ + fgets (tmp, PATH_MAX, stdin); + if (strcmp (tmp, "BEGIN AUTH REQUEST\n")) + { + printf ("error: bad auth protocol start: %s", tmp); + fflush (stdout); + exit (1); + } + + /* Get the three important pieces of information in order. */ + fgets (repository, PATH_MAX, stdin); + fgets (username, PATH_MAX, stdin); + fgets (password, PATH_MAX, stdin); + + /* Make them pure. */ + strip_trailing_newlines (repository); + strip_trailing_newlines (username); + strip_trailing_newlines (password); + + /* ... and make sure the protocol ends on the right foot. */ + fgets (tmp, PATH_MAX, stdin); + if (strcmp (tmp, "END AUTH REQUEST\n")) + { + printf ("error: bad auth protocol end: %s", tmp); + fflush (stdout); + exit (1); + } + + if (check_password (username, password, repository)) + { + printf ("I LOVE YOU\n"); + fflush (stdout); + } + else + { + printf ("I HATE YOU\n"); + fflush (stdout); + exit (1); + } + + /* Do everything that kerberos did. */ + pw = getpwnam (username); + if (pw == NULL) + { + printf ("E Fatal error, aborting.\n" + "error 0 %s: no such user\n", username); + exit (1); + } + + initgroups (pw->pw_name, pw->pw_gid); + setgid (pw->pw_gid); + setuid (pw->pw_uid); + /* Inhibit access by randoms. Don't want people randomly + changing our temporary tree before we check things in. */ + umask (077); + +#if HAVE_PUTENV + /* Set LOGNAME and USER in the environment, in case they are + already set to something else. */ + { + char *env; + + env = xmalloc (sizeof "LOGNAME=" + strlen (username)); + (void) sprintf (env, "LOGNAME=%s", username); + (void) putenv (env); + + env = xmalloc (sizeof "USER=" + strlen (username)); + (void) sprintf (env, "USER=%s", username); + (void) putenv (env); + } +#endif /* HAVE_PUTENV */ +} + +#endif AUTH_SERVER_SUPPORT + + +#endif /* SERVER_SUPPORT */ + diff --git a/gnu/usr.bin/cvs/cvs/server.h b/gnu/usr.bin/cvs/cvs/server.h new file mode 100644 index 0000000..cb49267 --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/server.h @@ -0,0 +1,136 @@ +/* Interface between the server and the rest of CVS. */ + +/* Miscellaneous stuff which isn't actually particularly server-specific. */ +#ifndef STDIN_FILENO +#define STDIN_FILENO 0 +#define STDOUT_FILENO 1 +#define STDERR_FILENO 2 +#endif + +#ifdef SERVER_SUPPORT + +/* + * Nonzero if we are using the server. Used by various places to call + * server-specific functions. + */ +extern int server_active; +extern int server_expanding; + +/* Server functions exported to the rest of CVS. */ + +/* Run the server. */ +extern int server PROTO((int argc, char **argv)); + +/* We have a new Entries line for a file. TAG or DATE can be NULL. */ +extern void server_register + PROTO((char *name, char *version, char *timestamp, + char *options, char *tag, char *date, char *conflict)); + +/* + * We want to nuke the Entries line for a file, and (unless + * server_scratch_entry_only is subsequently called) the file itself. + */ +extern void server_scratch PROTO((char *name)); + +/* + * The file which just had server_scratch called on it needs to have only + * the Entries line removed, not the file itself. + */ +extern void server_scratch_entry_only PROTO((void)); + +/* + * We just successfully checked in FILE (which is just the bare + * filename, with no directory). REPOSITORY is the directory for the + * repository. + */ +extern void server_checked_in + PROTO((char *file, char *update_dir, char *repository)); + +extern void server_copy_file + PROTO((char *file, char *update_dir, char *repository, char *newfile)); + +/* + * We just successfully updated FILE (bare filename, no directory). + * REPOSITORY is the directory for the repository. This is called + * after server_register or server_scratch, in the latter case the + * file is to be removed. UPDATED indicates whether the file is now + * up to date (SERVER_UPDATED, yes, SERVER_MERGED, no, SERVER_PATCHED, + * yes, but file is a diff from user version to repository version). + */ +enum server_updated_arg4 {SERVER_UPDATED, SERVER_MERGED, SERVER_PATCHED}; +extern void server_updated + PROTO((char *file, char *update_dir, char *repository, + enum server_updated_arg4 updated, struct stat *, + unsigned char *checksum)); + +/* Set the Entries.Static flag. */ +extern void server_set_entstat PROTO((char *update_dir, char *repository)); +/* Clear it. */ +extern void server_clear_entstat PROTO((char *update_dir, char *repository)); + +/* Set or clear a per-directory sticky tag or date. */ +extern void server_set_sticky PROTO((char *update_dir, char *repository, + char *tag, + char *date)); + +extern void server_update_entries + PROTO((char *file, char *update_dir, char *repository, + enum server_updated_arg4 updated)); + +enum progs {PROG_CHECKIN, PROG_UPDATE}; +extern void server_prog PROTO((char *, char *, enum progs)); +extern void server_cleanup PROTO((int sig)); + +#ifdef SERVER_FLOWCONTROL +/* Pause if it's convenient to avoid memory blowout */ +extern void server_check_pause PROTO((void)); +#endif /* SERVER_FLOWCONTROL */ + +#endif /* SERVER_SUPPORT */ + +/* Stuff shared with the client. */ +struct request +{ + /* Name of the request. */ + char *name; + +#ifdef SERVER_SUPPORT + /* + * Function to carry out the request. ARGS is the text of the command + * after name and, if present, a single space, have been stripped off. + */ + void (*func) PROTO((char *args)); +#endif + + /* Stuff for use by the client. */ + enum { + /* + * Failure to implement this request can imply a fatal + * error. This should be set only for commands which were in the + * original version of the protocol; it should not be set for new + * commands. + */ + rq_essential, + + /* Some servers might lack this request. */ + rq_optional, + + /* + * Set by the client to one of the following based on what this + * server actually supports. + */ + rq_supported, + rq_not_supported, + + /* + * If the server supports this request, and we do too, tell the + * server by making the request. + */ + rq_enableme + } status; +}; + +/* Table of requests ending with an entry with a NULL name. */ +extern struct request requests[]; + +extern int use_unchanged; diff --git a/gnu/usr.bin/cvs/cvs/update.h b/gnu/usr.bin/cvs/cvs/update.h new file mode 100644 index 0000000..68c91d5 --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/update.h @@ -0,0 +1,9 @@ +/* Definitions of routines shared between local and client/server + "update" code. */ + +/* List of files that we have either processed or are willing to + ignore. Any file not on this list gets a question mark printed. */ +extern List *ignlist; + +extern int +update_filesdone_proc PROTO((int err, char *repository, char *update_dir)); diff --git a/gnu/usr.bin/cvs/cvs/wrapper.c b/gnu/usr.bin/cvs/cvs/wrapper.c new file mode 100644 index 0000000..ec5f43e --- /dev/null +++ b/gnu/usr.bin/cvs/cvs/wrapper.c @@ -0,0 +1,371 @@ +#include "cvs.h" + +/* + Original Author: athan@morgan.com <Andrew C. Athan> 2/1/94 + Modified By: vdemarco@bou.shl.com + + This package was written to support the NEXTSTEP concept of + "wrappers." These are essentially directories that are to be + treated as "files." This package allows such wrappers to be + "processed" on the way in and out of CVS. The intended use is to + wrap up a wrapper into a single tar, such that that tar can be + treated as a single binary file in CVS. To solve the problem + effectively, it was also necessary to be able to prevent rcsmerge + application at appropriate times. + + ------------------ + Format of wrapper file ($CVSROOT/CVSROOT/cvswrappers or .cvswrappers) + + wildcard [option value][option value]... + + where option is one of + -f from cvs filter value: path to filter + -t to cvs filter value: path to filter + -m update methodology value: MERGE or COPY + + and value is a single-quote delimited value. + + E.g: + *.nib -f 'gunzipuntar' -t 'targzip' -m 'COPY' +*/ + + +typedef struct { + char *wildCard; + char *tocvsFilter; + char *fromcvsFilter; + char *conflictHook; + WrapMergeMethod mergeMethod; +} WrapperEntry; + +static WrapperEntry **wrap_list=NULL; +static WrapperEntry **wrap_saved_list=NULL; + +static int wrap_size=0; +static int wrap_count=0; +static int wrap_tempcount=0; +static int wrap_saved_count=0; +static int wrap_saved_tempcount=0; + +#define WRAPPER_GROW 8 + +void wrap_add_entry PROTO((WrapperEntry *e,int temp)); +void wrap_kill PROTO((void)); +void wrap_kill_temp PROTO((void)); +void wrap_free_entry PROTO((WrapperEntry *e)); +void wrap_free_entry_internal PROTO((WrapperEntry *e)); +void wrap_restore_saved PROTO((void)); + +void wrap_setup() +{ + char file[PATH_MAX]; + struct passwd *pw; + + /* Then add entries found in repository, if it exists */ + (void) sprintf (file, "%s/%s/%s", CVSroot, CVSROOTADM, CVSROOTADM_WRAPPER); + if (isfile (file)){ + wrap_add_file(file,0); + } + + /* Then add entries found in home dir, (if user has one) and file exists */ + if ((pw = (struct passwd *) getpwuid (getuid ())) && pw->pw_dir){ + (void) sprintf (file, "%s/%s", pw->pw_dir, CVSDOTWRAPPER); + if (isfile (file)){ + wrap_add_file (file, 0); + } + } + + /* Then add entries found in CVSWRAPPERS environment variable. */ + wrap_add (getenv (WRAPPER_ENV), 0); +} + +/* + * Open a file and read lines, feeding each line to a line parser. Arrange + * for keeping a temporary list of wrappers at the end, if the "temp" + * argument is set. + */ +void +wrap_add_file (file, temp) + const char *file; + int temp; +{ + FILE *fp; + char line[1024]; + + wrap_restore_saved(); + wrap_kill_temp(); + + /* load the file */ + if (!(fp = fopen (file, "r"))) + return; + while (fgets (line, sizeof (line), fp)) + wrap_add (line, temp); + (void) fclose (fp); +} + +void +wrap_kill() +{ + wrap_kill_temp(); + while(wrap_count) + wrap_free_entry(wrap_list[--wrap_count]); +} + +void +wrap_kill_temp() +{ + WrapperEntry **temps=wrap_list+wrap_count; + + while(wrap_tempcount) + wrap_free_entry(temps[--wrap_tempcount]); +} + +void +wrap_free_entry(e) + WrapperEntry *e; +{ + wrap_free_entry_internal(e); + free(e); +} + +void +wrap_free_entry_internal(e) + WrapperEntry *e; +{ + free(e->wildCard); + if(e->tocvsFilter) + free(e->tocvsFilter); + if(e->fromcvsFilter) + free(e->fromcvsFilter); + if(e->conflictHook) + free(e->conflictHook); +} + +void +wrap_restore_saved() +{ + if(!wrap_saved_list) + return; + + wrap_kill(); + + free(wrap_list); + + wrap_list=wrap_saved_list; + wrap_count=wrap_saved_count; + wrap_tempcount=wrap_saved_tempcount; + + wrap_saved_list=NULL; + wrap_saved_count=0; + wrap_saved_tempcount=0; +} + +void +wrap_add (line, isTemp) + char *line; + int isTemp; +{ + char *temp; + char ctemp; + WrapperEntry e; + char opt; + + if (!line || line[0] == '#') + return; + + memset (&e, 0, sizeof(e)); + + /* Search for the wild card */ + while(*line && isspace(*line)) + ++line; + for(temp=line;*line && !isspace(*line);++line) + ; + if(temp==line) + return; + + ctemp=*line; + *line='\0'; + + e.wildCard=xstrdup(temp); + *line=ctemp; + + while(*line){ + /* Search for the option */ + while(*line && *line!='-') + ++line; + if(!*line) + break; + ++line; + if(!*line) + break; + opt=*line; + + /* Search for the filter commandline */ + for(++line;*line && *line!='\'';++line); + if(!*line) + break; + + for(temp=++line;*line && (*line!='\'' || line[-1]=='\\');++line) + ; + + if(line==temp+1) + break; + + ctemp=*line; + *line='\0'; + switch(opt){ + case 'f': + if(e.fromcvsFilter) + free(e.fromcvsFilter); + e.fromcvsFilter=expand_path (temp); + if (!e.fromcvsFilter) + error (1, 0, + "Invalid environmental variable string '%s'",temp); + break; + case 't': + if(e.tocvsFilter) + free(e.tocvsFilter); + e.tocvsFilter=expand_path (temp); + if (!e.tocvsFilter) + error (1, 0, + "Invalid environmental variable string '%s'",temp); + break; + case 'c': + if(e.conflictHook) + free(e.conflictHook); + e.conflictHook=expand_path (temp); + if (!e.conflictHook) + error (1, 0, + "Invalid environmental variable string '%s'",temp); + break; + case 'm': + if(*temp=='C' || *temp=='c') + e.mergeMethod=WRAP_COPY; + else + e.mergeMethod=WRAP_MERGE; + break; + default: + break; + } + *line=ctemp; + if(!*line)break; + ++line; + } + + wrap_add_entry(&e, isTemp); +} + +void +wrap_add_entry(e, temp) + WrapperEntry *e; + int temp; +{ + int x; + if(wrap_count+wrap_tempcount>=wrap_size){ + wrap_size += WRAPPER_GROW; + wrap_list = (WrapperEntry **) xrealloc ((char *) wrap_list, + wrap_size * + sizeof (WrapperEntry *)); + } + + if(!temp && wrap_tempcount){ + for(x=wrap_count+wrap_tempcount-1;x>=wrap_count;--x) + wrap_list[x+1]=wrap_list[x]; + } + + x=(temp ? wrap_count+(wrap_tempcount++):(wrap_count++)); + wrap_list[x]=(WrapperEntry *)xmalloc(sizeof(WrapperEntry)); + wrap_list[x]->wildCard=e->wildCard; + wrap_list[x]->fromcvsFilter=e->fromcvsFilter; + wrap_list[x]->tocvsFilter=e->tocvsFilter; + wrap_list[x]->conflictHook=e->conflictHook; + wrap_list[x]->mergeMethod=e->mergeMethod; +} + +/* Return 1 if the given filename is a wrapper filename */ +int +wrap_name_has (name,has) + const char *name; + WrapMergeHas has; +{ + int x,count=wrap_count+wrap_saved_count; + char *temp; + + for(x=0;x<count;++x) + if (fnmatch (wrap_list[x]->wildCard, name, 0) == 0){ + switch(has){ + case WRAP_TOCVS: + temp=wrap_list[x]->tocvsFilter; + break; + case WRAP_FROMCVS: + temp=wrap_list[x]->fromcvsFilter; + break; + case WRAP_CONFLICT: + temp=wrap_list[x]->conflictHook; + break; + default: + abort (); + } + if(temp==NULL) + return (0); + else + return (1); + } + return (0); +} + +WrapperEntry * +wrap_matching_entry (name) + const char *name; +{ + int x,count=wrap_count+wrap_saved_count; + + for(x=0;x<count;++x) + if (fnmatch (wrap_list[x]->wildCard, name, 0) == 0) + return wrap_list[x]; + return (WrapperEntry *)NULL; +} + +char * +wrap_tocvs_process_file(fileName) + const char *fileName; +{ + WrapperEntry *e=wrap_matching_entry(fileName); + static char buf[L_tmpnam+1]; + + if(e==NULL || e->tocvsFilter==NULL) + return NULL; + + tmpnam(buf); + + run_setup(e->tocvsFilter,fileName,buf); + run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL|RUN_REALLY ); + + return buf; +} + +int +wrap_merge_is_copy (fileName) + const char *fileName; +{ + WrapperEntry *e=wrap_matching_entry(fileName); + if(e==NULL || e->mergeMethod==WRAP_MERGE) + return 0; + + return 1; +} + +char * +wrap_fromcvs_process_file(fileName) + const char *fileName; +{ + WrapperEntry *e=wrap_matching_entry(fileName); + static char buf[PATH_MAX]; + + if(e==NULL || e->fromcvsFilter==NULL) + return NULL; + + run_setup(e->fromcvsFilter,fileName); + run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL ); + return buf; +} diff --git a/gnu/usr.bin/cvs/cvsbug/cvsbug.8 b/gnu/usr.bin/cvs/cvsbug/cvsbug.8 new file mode 100644 index 0000000..496ef14 --- /dev/null +++ b/gnu/usr.bin/cvs/cvsbug/cvsbug.8 @@ -0,0 +1,269 @@ +.\" -*- nroff -*- +.\" --------------------------------------------------------------------------- +.\" man page for send-pr (by Heinz G. Seidl, hgs@cygnus.com) +.\" updated Feb 1993 for GNATS 3.00 by Jeffrey Osier, jeffrey@cygnus.com +.\" +.\" This file is part of the Problem Report Management System (GNATS) +.\" Copyright 1992 Cygnus Support +.\" +.\" This program is free software; you can redistribute it and/or +.\" modify it under the terms of the GNU General Public +.\" License as published by the Free Software Foundation; either +.\" version 2 of the License, or (at your option) any later version. +.\" +.\" This program is distributed in the hope that it will be useful, +.\" but WITHOUT ANY WARRANTY; without even the implied warranty of +.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +.\" General Public License for more details. +.\" +.\" You should have received a copy of the GNU Library General Public +.\" License along with this program; if not, write to the Free +.\" Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA +.\" +.\" --------------------------------------------------------------------------- +.nh +.TH CVSBUG 1 xVERSIONx "February 1993" +.SH NAME +cvsbug \- send problem report (PR) about CVS to a central support site +.SH SYNOPSIS +.B cvsbug +[ +.I site +] +[ +.B \-f +.I problem-report +] +[ +.B \-t +.I mail-address +] +.br +.in +0.8i +[ +.B \-P +] +[ +.B \-L +] +[ +.B \-\-request-id +] +[ +.B \-v +] +.SH DESCRIPTION +.B cvsbug +is a tool used to submit +.I problem reports +.\" SITE ADMINISTRATORS - change this if you use a local default +(PRs) to a central support site. In most cases the correct +.I site +will be the default. This argument indicates the support site which +is responsible for the category of problem involved. Some sites may +use a local address as a default. +.I site +values are defined by using the +.BR aliases (5). +.LP +.B cvsbug +invokes an editor on a problem report template (after trying to fill +in some fields with reasonable default values). When you exit the +editor, +.B cvsbug +sends the completed form to the +.I Problem Report Management System +(\fBGNATS\fR) at a central support site. At the support site, the PR +is assigned a unique number and is stored in the \fBGNATS\fR database +according to its category and submitter-id. \fBGNATS\fR automatically +replies with an acknowledgement, citing the category and the PR +number. +.LP +To ensure that a PR is handled promptly, it should contain your (unique) +\fIsubmitter-id\fR and one of the available \fIcategories\fR to identify the +problem area. (Use +.B `cvsbug -L' +to see a list of categories.) +.LP +The +.B cvsbug +template at your site should already be customized with your +submitter-id (running `\|\fBinstall-sid\fP \fIsubmitter-id\fP\|' to +accomplish this is part of the installation procedures for +.BR cvsbug ). +If this hasn't been done, see your system administrator for your +submitter-id, or request one from your support site by invoking +.B `cvsbug \-\-request\-id'. +If your site does not distinguish between different user sites, or if +you are not affiliated with the support site, use +.B `net' +for this field. +.LP +The more precise your problem description and the more complete your +information, the faster your support team can solve your problems. +.SH OPTIONS +.TP +.BI \-f " problem-report" +specify a file (\fIproblem-report\fR) which already contains a +complete problem report. +.B cvsbug +sends the contents of the file without invoking the editor. If +the value for +.I problem-report +is +.BR `\|\-\|' , +then +.B cvsbug +reads from standard input. +.TP +.BI \-t " mail-address" +Change mail address at the support site for problem reports. The +default +.I mail-address +is the address used for the default +.IR site . +Use the +.I site +argument rather than this option in nearly all cases. +.TP +.B \-P +print the form specified by the environment variable +.B PR_FORM +on standard output. If +.B PR_FORM +is not set, print the standard blank PR template. No mail is sent. +.TP +.B -L +print the list of available categories. No mail is sent. +.TP +.B \-\-request\-id +sends mail to the default support site, or +.I site +if specified, with a request for your +.IR submitter-id . +If you are +not affiliated with +.IR site , +use a +.I submitter-id +of +.BR net \|'. +.TP +.B \-v +Display the +.B cvsbug +version number. +.LP +Note: use +.B cvsbug +to submit problem reports rather than mailing them directly. Using +both the template and +.B cvsbug +itself will help ensure all necessary information will reach the +support site. +.SH ENVIRONMENT +The environment variable +.B EDITOR +specifies the editor to invoke on the template. +.br +default: +.B vi +.sp +If the environment variable +.B PR_FORM +is set, then its value is used as the file name of the template for +your problem-report editing session. You can use this to start with a +partially completed form (for example, a form with the identification +fields already completed). +.SH "HOW TO FILL OUT A PROBLEM REPORT" +Problem reports have to be in a particular form so that a program can +easily manage them. Please remember the following guidelines: +.IP \(bu 3m +describe only +.B one problem +with each problem report. +.IP \(bu 3m +For follow-up mail, use the same subject line as the one in the automatic +acknowledgent. It consists of category, PR number and the original synopsis +line. This allows the support site to relate several mail messages to a +particular PR and to record them automatically. +.IP \(bu 3m +Please try to be as accurate as possible in the subject and/or synopsis line. +.IP \(bu 3m +The subject and the synopsis line are not confidential. This is +because open-bugs lists are compiled from them. Avoid confidential +information there. +.LP +See the GNU +.B Info +file +.B cvsbug.info +or the document \fIReporting Problems With cvsbug\fR\ for detailed +information on reporting problems +.SH "HOW TO SUBMIT TEST CASES, CODE, ETC." +Submit small code samples with the PR. Contact the support site for +instructions on submitting larger test cases and problematic source +code. +.SH FILES +.ta \w'/tmp/pbad$$ 'u +/tmp/p$$ copy of PR used in editing session +.br +/tmp/pf$$ copy of empty PR form, for testing purposes +.br +/tmp/pbad$$ file for rejected PRs +.SH EMACS USER INTERFACE +An Emacs user interface for +.B cvsbug +with completion of field values is part of the +.B cvsbug +distribution (invoked with +.BR "M-x cvsbug" ). +See the file +.B cvsbug.info +or the ASCII file +.B INSTALL +in the top level directory of the distribution for configuration and +installation information. The Emacs LISP template file is +.B cvsbug-el.in +and is installed as +.BR cvsbug.el . +.SH INSTALLATION AND CONFIGURATION +See +.B cvsbug.info +or +.B INSTALL +for installation instructions. +.SH SEE ALSO +.I Reporting Problems Using cvsbug +(also installed as the GNU Info file +.BR cvsbug.info ). +.LP +.BR gnats (l), +.BR query-pr (1), +.BR edit-pr (1), +.BR gnats (8), +.BR queue-pr (8), +.BR at-pr (8), +.BR mkcat (8), +.BR mkdist (8). +.SH AUTHORS +Jeffrey Osier, Brendan Kehoe, Jason Merrill, Heinz G. Seidl (Cygnus +Support) +.SH COPYING +Copyright (c) 1992, 1993 Free Software Foundation, Inc. +.PP +Permission is granted to make and distribute verbatim copies of +this manual provided the copyright notice and this permission notice +are preserved on all copies. +.PP +Permission is granted to copy and distribute modified versions of this +manual under the conditions for verbatim copying, provided that the +entire resulting derived work is distributed under the terms of a +permission notice identical to this one. +.PP +Permission is granted to copy and distribute translations of this +manual into another language, under the above conditions for modified +versions, except that this permission notice may be included in +translations approved by the Free Software Foundation instead of in +the original English. + diff --git a/gnu/usr.bin/cvs/cvsbug/cvsbug.sh b/gnu/usr.bin/cvs/cvsbug/cvsbug.sh new file mode 100644 index 0000000..ab26cfc --- /dev/null +++ b/gnu/usr.bin/cvs/cvsbug/cvsbug.sh @@ -0,0 +1,528 @@ +#! /bin/sh +# Submit a problem report to a GNATS site. +# Copyright (C) 1993 Free Software Foundation, Inc. +# Contributed by Brendan Kehoe (brendan@cygnus.com), based on a +# version written by Heinz G. Seidl (hgs@ide.com). +# +# This file is part of GNU GNATS. +# Modified by Berliner for CVS. +# +#ident "@(#)cvs/src:$Name: $:$Id: cvsbug.sh,v 1.10 1995/11/15 00:18:00 woods Exp $" +# +# GNU GNATS is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# GNU GNATS is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU GNATS; see the file COPYING. If not, write to +# the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + +# The version of this send-pr. +VERSION=3.2 + +# The submitter-id for your site. +SUBMITTER=net + +## # Where the GNATS directory lives, if at all. +## [ -z "$GNATS_ROOT" ] && +## GNATS_ROOT=/usr/local/lib/gnats/gnats-db + +# The default mail address for PR submissions. +GNATS_ADDR=bug-cvs@prep.ai.mit.edu + +## # Where the gnats category tree lives. +## DATADIR=/usr/local/lib + +## # If we've been moved around, try using GCC_EXEC_PREFIX. +## [ ! -d $DATADIR/gnats -a -d "$GCC_EXEC_PREFIX" ] && DATADIR=${GCC_EXEC_PREFIX}.. + +# The default release for this host. +DEFAULT_RELEASE="xVERSIONx" + +# The default organization. +DEFAULT_ORGANIZATION="net" + +## # The default site to look for. +## GNATS_SITE=unknown + +## # Newer config information? +## [ -f ${GNATS_ROOT}/gnats-adm/config ] && . ${GNATS_ROOT}/gnats-adm/config + +# What mailer to use. This must come after the config file, since it is +# host-dependent. +if [ -f /usr/sbin/sendmail ]; then + MAIL_AGENT="/usr/sbin/sendmail -oi -t" +else + MAIL_AGENT="/usr/lib/sendmail -oi -t" +fi +MAILER=`echo $MAIL_AGENT | sed -e 's, .*,,'` +if [ ! -f "$MAILER" ] ; then + echo "$COMMAND: Cannot file mail program \"$MAILER\"." + echo "$COMMAND: Please fix the MAIL_AGENT entry in the $COMMAND file." + exit 1 +fi + +if test "`echo -n foo`" = foo ; then + ECHON=bsd +elif test "`echo 'foo\c'`" = foo ; then + ECHON=sysv +else + ECHON=none +fi + +if [ $ECHON = bsd ] ; then + ECHON1="echo -n" + ECHON2= +elif [ $ECHON = sysv ] ; then + ECHON1=echo + ECHON2='\c' +else + ECHON1=echo + ECHON2= +fi + +# + +[ -z "$TMPDIR" ] && TMPDIR=/tmp + +TEMP=$TMPDIR/p$$ +BAD=$TMPDIR/pbad$$ +REF=$TMPDIR/pf$$ + +if [ -z "$LOGNAME" -a -n "$USER" ]; then + LOGNAME=$USER +fi + +FROM="$LOGNAME" +REPLY_TO="$LOGNAME" + +# Find out the name of the originator of this PR. +if [ -n "$NAME" ]; then + ORIGINATOR="$NAME" +elif [ -f $HOME/.fullname ]; then + ORIGINATOR="`sed -e '1q' $HOME/.fullname`" +elif [ -f /bin/domainname ]; then + if [ "`/bin/domainname`" != "" -a -f /usr/bin/ypcat ]; then + # Must use temp file due to incompatibilities in quoting behavior + # and to protect shell metacharacters in the expansion of $LOGNAME + /usr/bin/ypcat passwd 2>/dev/null | cat - /etc/passwd | grep "^$LOGNAME:" | + cut -f5 -d':' | sed -e 's/,.*//' > $TEMP + ORIGINATOR="`cat $TEMP`" + rm -f $TEMP + fi +fi + +if [ "$ORIGINATOR" = "" ]; then + grep "^$LOGNAME:" /etc/passwd | cut -f5 -d':' | sed -e 's/,.*//' > $TEMP + ORIGINATOR="`cat $TEMP`" + rm -f $TEMP +fi + +if [ -n "$ORGANIZATION" ]; then + if [ -f "$ORGANIZATION" ]; then + ORGANIZATION="`cat $ORGANIZATION`" + fi +else + if [ -n "$DEFAULT_ORGANIZATION" ]; then + ORGANIZATION="$DEFAULT_ORGANIZATION" + elif [ -f $HOME/.organization ]; then + ORGANIZATION="`cat $HOME/.organization`" + elif [ -f $HOME/.signature ]; then + ORGANIZATION="`cat $HOME/.signature`" + fi +fi + +# If they don't have a preferred editor set, then use +if [ -z "$VISUAL" ]; then + if [ -z "$EDITOR" ]; then + EDIT=vi + else + EDIT="$EDITOR" + fi +else + EDIT="$VISUAL" +fi + +# Find out some information. +SYSTEM=`( [ -f /bin/uname ] && /bin/uname -a ) || \ + ( [ -f /usr/bin/uname ] && /usr/bin/uname -a ) || echo ""` +ARCH=`[ -f /bin/arch ] && /bin/arch` +MACHINE=`[ -f /bin/machine ] && /bin/machine` + +COMMAND=`echo $0 | sed -e 's,.*/,,'` +## USAGE="Usage: $COMMAND [-PVL] [-t address] [-f filename] [--request-id] +USAGE="Usage: $COMMAND [-PVL] +[--version]" +REMOVE= +BATCH= + +while [ $# -gt 0 ]; do + case "$1" in + -r) ;; # Ignore for backward compat. +## -t | --to) if [ $# -eq 1 ]; then echo "$USAGE"; exit 1; fi +## shift ; GNATS_ADDR="$1" +## EXPLICIT_GNATS_ADDR=true +## ;; +## -f | --file) if [ $# -eq 1 ]; then echo "$USAGE"; exit 1; fi +## shift ; IN_FILE="$1" +## if [ "$IN_FILE" != "-" -a ! -r "$IN_FILE" ]; then +## echo "$COMMAND: cannot read $IN_FILE" +## exit 1 +## fi +## ;; + -b | --batch) BATCH=true ;; + -p | -P | --print) PRINT=true ;; + -L | --list) FORMAT=norm ;; + -l | -CL | --lisp) FORMAT=lisp ;; +## --request-id) REQUEST_ID=true ;; + -h | --help) echo "$USAGE"; exit 0 ;; + -V | --version) echo "$VERSION"; exit 0 ;; + -*) echo "$USAGE" ; exit 1 ;; + *) echo "$USAGE" ; exit 1 +## if [ -z "$USER_GNATS_SITE" ]; then +## if [ ! -r "$DATADIR/gnats/$1" ]; then +## echo "$COMMAND: the GNATS site $1 does not have a categories list." +## exit 1 +## else +## # The site name is the alias they'll have to have created. +## USER_GNATS_SITE=$1 +## fi +## else +## echo "$USAGE" ; exit 1 +## fi + ;; + esac + shift +done + +if [ -n "$USER_GNATS_SITE" ]; then + GNATS_SITE=$USER_GNATS_SITE + GNATS_ADDR=$USER_GNATS_SITE-gnats +fi + +if [ "$SUBMITTER" = "unknown" -a -z "$REQUEST_ID" -a -z "$IN_FILE" ]; then + cat << '__EOF__' +It seems that send-pr is not installed with your unique submitter-id. +You need to run + + install-sid YOUR-SID + +where YOUR-SID is the identification code you received with `send-pr'. +`send-pr' will automatically insert this value into the template field +`>Submitter-Id'. If you've downloaded `send-pr' from the Net, use `net' +for this value. If you do not know your id, run `send-pr --request-id' to +get one from your support site. +__EOF__ + exit 1 +fi + +## if [ -r "$DATADIR/gnats/$GNATS_SITE" ]; then +## CATEGORIES=`grep -v '^#' $DATADIR/gnats/$GNATS_SITE | sort` +## else +## echo "$COMMAND: could not read $DATADIR/gnats/$GNATS_SITE for categories list." +## exit 1 +## fi +CATEGORIES="contrib cvs doc pcl-cvs portability" + +if [ -z "$CATEGORIES" ]; then + echo "$COMMAND: the categories list for $GNATS_SITE was empty!" + exit 1 +fi + +case "$FORMAT" in + lisp) echo "$CATEGORIES" | \ + awk 'BEGIN {printf "( "} {printf "(\"%s\") ",$0} END {printf ")\n"}' + exit 0 + ;; + norm) l=`echo "$CATEGORIES" | \ + awk 'BEGIN {max = 0; } { if (length($0) > max) { max = length($0); } } + END {print max + 1;}'` + c=`expr 70 / $l` + if [ $c -eq 0 ]; then c=1; fi + echo "$CATEGORIES" | \ + awk 'BEGIN {print "Known categories:"; i = 0 } + { printf ("%-'$l'.'$l's", $0); if ((++i % '$c') == 0) { print "" } } + END { print ""; }' + exit 0 + ;; +esac + +ORIGINATOR_C='<name of the PR author (one line)>' +ORGANIZATION_C='<organization of PR author (multiple lines)>' +CONFIDENTIAL_C='<[ yes | no ] (one line)>' +SYNOPSIS_C='<synopsis of the problem (one line)>' +SEVERITY_C='<[ non-critical | serious | critical ] (one line)>' +PRIORITY_C='<[ low | medium | high ] (one line)>' +CATEGORY_C='<name of the product (one line)>' +CLASS_C='<[ sw-bug | doc-bug | change-request | support ] (one line)>' +RELEASE_C='<release number or tag (one line)>' +ENVIRONMENT_C='<machine, os, target, libraries (multiple lines)>' +DESCRIPTION_C='<precise description of the problem (multiple lines)>' +HOW_TO_REPEAT_C='<code/input/activities to reproduce the problem (multiple lines)>' +FIX_C='<how to correct or work around the problem, if known (multiple lines)>' + +# Catch some signals. ($xs kludge needed by Sun /bin/sh) +xs=0 +trap 'rm -f $REF $TEMP; exit $xs' 0 +trap 'echo "$COMMAND: Aborting ..."; rm -f $REF $TEMP; xs=1; exit' 1 2 3 13 15 + +# If they told us to use a specific file, then do so. +if [ -n "$IN_FILE" ]; then + if [ "$IN_FILE" = "-" ]; then + # The PR is coming from the standard input. + if [ -n "$EXPLICIT_GNATS_ADDR" ]; then + sed -e "s;^[Tt][Oo]:.*;To: $GNATS_ADDR;" > $TEMP + else + cat > $TEMP + fi + else + # Use the file they named. + if [ -n "$EXPLICIT_GNATS_ADDR" ]; then + sed -e "s;^[Tt][Oo]:.*;To: $GNATS_ADDR;" $IN_FILE > $TEMP + else + cat $IN_FILE > $TEMP + fi + fi +else + + if [ -n "$PR_FORM" -a -z "$PRINT_INTERN" ]; then + # If their PR_FORM points to a bogus entry, then bail. + if [ ! -f "$PR_FORM" -o ! -r "$PR_FORM" -o ! -s "$PR_FORM" ]; then + echo "$COMMAND: can't seem to read your template file (\`$PR_FORM'), ignoring PR_FORM" + sleep 1 + PRINT_INTERN=bad_prform + fi + fi + + if [ -n "$PR_FORM" -a -z "$PRINT_INTERN" ]; then + cp $PR_FORM $TEMP || + ( echo "$COMMAND: could not copy $PR_FORM" ; xs=1; exit ) + else + for file in $TEMP $REF ; do + cat > $file << '__EOF__' +SEND-PR: -*- send-pr -*- +SEND-PR: Lines starting with `SEND-PR' will be removed automatically, as +SEND-PR: will all comments (text enclosed in `<' and `>'). +SEND-PR: +SEND-PR: Choose from the following categories: +SEND-PR: +__EOF__ + + # Format the categories so they fit onto lines. + l=`echo "$CATEGORIES" | \ + awk 'BEGIN {max = 0; } { if (length($0) > max) { max = length($0); } } + END {print max + 1;}'` + c=`expr 61 / $l` + if [ $c -eq 0 ]; then c=1; fi + echo "$CATEGORIES" | \ + awk 'BEGIN {printf "SEND-PR: "; i = 0 } + { printf ("%-'$l'.'$l's", $0); + if ((++i % '$c') == 0) { printf "\nSEND-PR: " } } + END { printf "\nSEND-PR:\n"; }' >> $file + + cat >> $file << __EOF__ +To: $GNATS_ADDR +Subject: +From: $FROM +Reply-To: $REPLY_TO +X-send-pr-version: $VERSION + + +>Submitter-Id: $SUBMITTER +>Originator: $ORIGINATOR +>Organization: +${ORGANIZATION-$ORGANIZATION_C} +>Confidential: $CONFIDENTIAL_C +>Synopsis: $SYNOPSIS_C +>Severity: $SEVERITY_C +>Priority: $PRIORITY_C +>Category: $CATEGORY_C +>Class: $CLASS_C +>Release: ${DEFAULT_RELEASE-$RELEASE_C} +>Environment: + $ENVIRONMENT_C +`[ -n "$SYSTEM" ] && echo System: $SYSTEM` +`[ -n "$ARCH" ] && echo Architecture: $ARCH` +`[ -n "$MACHINE" ] && echo Machine: $MACHINE` +>Description: + $DESCRIPTION_C +>How-To-Repeat: + $HOW_TO_REPEAT_C +>Fix: + $FIX_C +__EOF__ + done + fi + + if [ "$PRINT" = true -o "$PRINT_INTERN" = true ]; then + cat $TEMP + xs=0; exit + fi + + chmod u+w $TEMP + if [ -z "$REQUEST_ID" ]; then + eval $EDIT $TEMP + else + ed -s $TEMP << '__EOF__' +/^Subject/s/^Subject:.*/Subject: request for a customer id/ +/^>Category/s/^>Category:.*/>Category: send-pr/ +w +q +__EOF__ + fi + + if cmp -s $REF $TEMP ; then + echo "$COMMAND: problem report not filled out, therefore not sent" + xs=1; exit + fi +fi + +# +# Check the enumeration fields + +# This is a "sed-subroutine" with one keyword parameter +# (with workaround for Sun sed bug) +# +SED_CMD=' +/$PATTERN/{ +s||| +s|<.*>|| +s|^[ ]*|| +s|[ ]*$|| +p +q +}' + + +while [ -z "$REQUEST_ID" ]; do + CNT=0 + + # 1) Confidential + # + PATTERN=">Confidential:" + CONFIDENTIAL=`eval sed -n -e "\"$SED_CMD\"" $TEMP` + case "$CONFIDENTIAL" in + ""|yes|no) CNT=`expr $CNT + 1` ;; + *) echo "$COMMAND: \`$CONFIDENTIAL' is not a valid value for \`Confidential'." ;; + esac + # + # 2) Severity + # + PATTERN=">Severity:" + SEVERITY=`eval sed -n -e "\"$SED_CMD\"" $TEMP` + case "$SEVERITY" in + ""|non-critical|serious|critical) CNT=`expr $CNT + 1` ;; + *) echo "$COMMAND: \`$SEVERITY' is not a valid value for \`Severity'." + esac + # + # 3) Priority + # + PATTERN=">Priority:" + PRIORITY=`eval sed -n -e "\"$SED_CMD\"" $TEMP` + case "$PRIORITY" in + ""|low|medium|high) CNT=`expr $CNT + 1` ;; + *) echo "$COMMAND: \`$PRIORITY' is not a valid value for \`Priority'." + esac + # + # 4) Category + # + PATTERN=">Category:" + CATEGORY=`eval sed -n -e "\"$SED_CMD\"" $TEMP` + FOUND= + for C in $CATEGORIES + do + if [ "$C" = "$CATEGORY" ]; then FOUND=true ; break ; fi + done + if [ -n "$FOUND" ]; then + CNT=`expr $CNT + 1` + else + if [ -z "$CATEGORY" ]; then + echo "$COMMAND: you must include a Category: field in your report." + else + echo "$COMMAND: \`$CATEGORY' is not a known category." + fi + fi + # + # 5) Class + # + PATTERN=">Class:" + CLASS=`eval sed -n -e "\"$SED_CMD\"" $TEMP` + case "$CLASS" in + ""|sw-bug|doc-bug|change-request|support) CNT=`expr $CNT + 1` ;; + *) echo "$COMMAND: \`$CLASS' is not a valid value for \`Class'." + esac + + [ $CNT -lt 5 -a -z "$BATCH" ] && + echo "Errors were found with the problem report." + + while true; do + if [ -z "$BATCH" ]; then + $ECHON1 "a)bort, e)dit or s)end? $ECHON2" + read input + else + if [ $CNT -eq 5 ]; then + input=s + else + input=a + fi + fi + case "$input" in + a*) + if [ -z "$BATCH" ]; then + echo "$COMMAND: the problem report remains in $BAD and is not sent." + mv $TEMP $BAD + else + echo "$COMMAND: the problem report is not sent." + fi + xs=1; exit + ;; + e*) + eval $EDIT $TEMP + continue 2 + ;; + s*) + break 2 + ;; + esac + done +done +# +# Remove comments and send the problem report +# (we have to use patterns, where the comment contains regex chars) +# +# /^>Originator:/s;$ORIGINATOR;; +sed -e " +/^SEND-PR:/d +/^>Organization:/,/^>[A-Za-z-]*:/s;$ORGANIZATION_C;; +/^>Confidential:/s;<.*>;; +/^>Synopsis:/s;$SYNOPSIS_C;; +/^>Severity:/s;<.*>;; +/^>Priority:/s;<.*>;; +/^>Category:/s;$CATEGORY_C;; +/^>Class:/s;<.*>;; +/^>Release:/,/^>[A-Za-z-]*:/s;$RELEASE_C;; +/^>Environment:/,/^>[A-Za-z-]*:/s;$ENVIRONMENT_C;; +/^>Description:/,/^>[A-Za-z-]*:/s;$DESCRIPTION_C;; +/^>How-To-Repeat:/,/^>[A-Za-z-]*:/s;$HOW_TO_REPEAT_C;; +/^>Fix:/,/^>[A-Za-z-]*:/s;$FIX_C;; +" $TEMP > $REF + +if $MAIL_AGENT < $REF; then + echo "$COMMAND: problem report sent" + xs=0; exit +else + echo "$COMMAND: mysterious mail failure." + if [ -z "$BATCH" ]; then + echo "$COMMAND: the problem report remains in $BAD and is not sent." + mv $REF $BAD + else + echo "$COMMAND: the problem report is not sent." + fi + xs=1; exit +fi diff --git a/gnu/usr.bin/cvs/cvsinit/cvsinit b/gnu/usr.bin/cvs/cvsinit/cvsinit new file mode 100644 index 0000000..23c6651 --- /dev/null +++ b/gnu/usr.bin/cvs/cvsinit/cvsinit @@ -0,0 +1,161 @@ +#! /bin/sh +: +# +#ident "@(#)cvs:$Name: $:$Id: cvsinit.sh,v 1.7 1995/11/14 23:44:18 woods Exp $" +# Copyright (c) 1992, Brian Berliner +# +# You may distribute under the terms of the GNU General Public License as +# specified in the README file that comes with the CVS 1.4 kit. + +# This script should be run for each repository you create to help you +# setup your site for CVS. You may also run it to update existing +# repositories if you install a new version of CVS. + +# this line is edited by Makefile when creating cvsinit.inst +CVSLIB="/usr/share/examples/cvs" + +CVS_VERSION="cvs-1.6.3" + +# All purpose usage message, also suffices for --help and --version. +if test $# -gt 0; then + echo "cvsinit version $CVS_VERSION" + echo "usage: $0" + echo "(set CVSROOT to the repository that you want to initialize)" + exit 0 +fi + +# Make sure that the CVSROOT variable is set +if [ "x$CVSROOT" = x ]; then + echo "The CVSROOT environment variable is not set." + echo "" + echo "You should choose a location for your source repository" + echo "that can be shared by many developers. It also helps to" + echo "place the source repository on a file system that has" + echo "plenty of free space." + echo "" + echo "Please enter the full path for your CVSROOT source repository:" + read CVSROOT junk + unset junk + remind_cvsroot=yes +else + remind_cvsroot=no +fi + +# Now, create the $CVSROOT if it is not already there +if [ ! -d $CVSROOT ]; then + echo "Creating $CVSROOT..." + path= + for comp in `echo $CVSROOT | sed -e 's,/, ,g'`; do + path=$path/$comp + if [ ! -d $path ]; then + mkdir $path + fi + done +else + true +fi + +# Next, check for $CVSROOT/CVSROOT +if [ ! -d $CVSROOT/CVSROOT ]; then + if [ -d $CVSROOT/CVSROOT.adm ]; then + echo "You have the old $CVSROOT/CVSROOT.adm directory." + echo "I will rename it to $CVSROOT/CVSROOT for you..." + mv $CVSROOT/CVSROOT.adm $CVSROOT/CVSROOT + else + echo "Creating the $CVSROOT/CVSROOT directory..." + mkdir $CVSROOT/CVSROOT + fi +else + true +fi +if [ ! -d $CVSROOT/CVSROOT ]; then + echo "Unable to create $CVSROOT/CVSROOT." + echo "I give up." + exit 1 +fi + +# Create the special control files and templates within $CVSROOT/CVSROOT + +EXAMPLES="checkoutlist commitinfo cvswrappers editinfo loginfo modules +rcsinfo rcstemplate taginfo wrap unwrap" + +NEWSAMPLE=false +for info in $EXAMPLES; do + if [ -f $CVSROOT/CVSROOT/${info},v ]; then + if [ ! -f $CVSROOT/CVSROOT/$info ]; then + echo "Checking out $CVSROOT/CVSROOT/$info" + echo " from $CVSROOT/CVSROOT/${info},v..." + (cd $CVSROOT/CVSROOT; co -q $info) + fi + else + NEWSAMPLE=true + if [ -f $CVSROOT/CVSROOT/$info ]; then + echo "Checking in $CVSROOT/CVSROOT/${info},v" + echo " from $CVSROOT/CVSROOT/$info..." + else + echo "Creating a sample $CVSROOT/CVSROOT/$info file..." + case $info in + modules) + sed -n -e '/END_REQUIRED_CONTENT/q' \ + -e p $CVSLIB/examples/modules > $CVSROOT/CVSROOT/modules + ;; + rcstemplate) + cp $CVSLIB/examples/$info $CVSROOT/CVSROOT/$info + ;; + wrap|unwrap) + cp $CVSLIB/examples/$info $CVSROOT/CVSROOT/$info + chmod +x $CVSROOT/CVSROOT/$info + ;; + *) + # comment out everything in all the other examples.... + sed -e 's/^\([^#]\)/#\1/' $CVSLIB/examples/$info > $CVSROOT/CVSROOT/$info + ;; + esac + fi + (cd $CVSROOT/CVSROOT; ci -q -u -t/dev/null -m"initial checkin of $info" $info) + fi +done + +if $NEWSAMPLE ; then + echo "NOTE: You may wish to check out the CVSROOT module and edit any new" + echo "configuration files to match your local requirements." + echo "" +fi + +# check to see if there are any references to the old CVSROOT.adm directory +if grep CVSROOT.adm $CVSROOT/CVSROOT/modules >/dev/null 2>&1; then + echo "Warning: your $CVSROOT/CVSROOT/modules file still" + echo " contains references to the old CVSROOT.adm directory" + echo " You should really change these to the new CVSROOT directory" + echo "" +fi + +# These files are generated from the contrib files. +# FIXME: Is it really wise to overwrite possible local changes like this? +# Normal folks will keep these up to date by modifying the source in +# their CVS module and re-installing CVS, but is everyone OK with that? +# +# +CONTRIBS="log commit_prep log_accum cln_hist" +# +for contrib in $CONTRIBS; do + echo "Copying the new version of '${contrib}'" + echo " to $CVSROOT/CVSROOT for you..." + cp $CVSLIB/contrib/$contrib $CVSROOT/CVSROOT/$contrib +done + +# XXX - also add a stub for the cvsignore file + +# Turn on history logging by default +if [ ! -f $CVSROOT/CVSROOT/history ]; then + echo "Enabling CVS history logging..." + touch $CVSROOT/CVSROOT/history + chmod g+w $CVSROOT/CVSROOT/history + echo "(Remove $CVSROOT/CVSROOT/history to disable.)" +fi + +# finish up by running mkmodules +echo "All done! Running 'mkmodules' as my final step..." +mkmodules $CVSROOT/CVSROOT + +exit 0 diff --git a/gnu/usr.bin/cvs/cvsinit/cvsinit.8 b/gnu/usr.bin/cvs/cvsinit/cvsinit.8 new file mode 100644 index 0000000..1012d62 --- /dev/null +++ b/gnu/usr.bin/cvs/cvsinit/cvsinit.8 @@ -0,0 +1,142 @@ +.de Id +.ds Rv \\$4 +.ds Dt \\$5 +.. +.Id @(#)ccvs/man:$Name: $:$Id: cvsinit.8,v 1.2 1995/11/14 20:48:54 woods Exp $ +.TH CVSINIT 8 "\*(Dt" +.\" Full space in nroff; half space in troff +.de SP +.if n .sp +.if t .sp .5 +.. +.\" quoted command +.de ` +.RB ` "\|\\$1\|" '\\$2 +.. +.\" +.SH "NAME" +cvsinit \- Concurrent Versions System repository initialization script +.SH "SYNOPSIS" +.TP +.B cvsinit +.\" +.SH "DESCRIPTION" +.\" +The +.B cvsinit +script initializes a repository in the location specified by the +.SM CVSROOT +environment variable. +.SH "FILES" +For more detailed information on +.B cvs +supporting files, see +.BR cvs ( 5 ). +.LP +Files in source repositories (created by +.BR cvsinit ): +.TP +$CVSROOT/CVSROOT +Directory of global administrative files for repository. +.TP +$CVSROOT/commitinfo,v +Records programs for filtering +.` "cvs commit" +requests. +.TP +$CVSROOT/history +Log file of \fBcvs\fP transactions. +.TP +$CVSROOT/modules,v +Definitions for modules in this repository. +.TP +$CVSROOT/loginfo,v +Records programs for piping +.` "cvs commit" +log entries. +.TP +$CVSROOT/rcsinfo,v +Records pathnames to templates used during a +.` "cvs commit" +operation. +.TP +$CVSROOT/editinfo,v +Records programs for editing/validating +.` "cvs commit" +log entries. +.TP +$CVSROOT/log +Sample logging script for use in +.IR loginfo . +.TP +$CVSROOT/commit_prep +Sample logging script for use in +.I commitinfo +with the +.I log_accum +script +.TP +$CVSROOT/log_accum +Sample loggin script for use in +.I loginfo +with the +.I commit_prep +script +.\" +.SH "ENVIRONMENT VARIABLES" +.TP +.SM CVSROOT +Should contain the full pathname to the root of the +.B cvs +source repository (where the +.SM RCS +files are kept). This information must be available to \fBcvs\fP for +most commands to execute; if +.SM CVSROOT +is not set, or if you wish to override it for one invocation, you can +supply it on the command line: +.` "cvs \-d \fIcvsroot cvs_command\fP\|.\|.\|." +You may not need to set +.SM CVSROOT +if your \fBcvs\fP binary has the right path compiled in; use +.` "cvs \-v" +to display all compiled-in paths. +.\" +.SH "AUTHORS" +.TP +Dick Grune +Original author of the +.B cvs +shell script version posted to +.B comp.sources.unix +in the volume6 release of December, 1986. +Credited with much of the +.B cvs +conflict resolution algorithms. +.TP +Brian Berliner +Coder and designer of the +.B cvs +program itself in April, 1989, based on the original work done by Dick. +.TP +Jeff Polk +Helped Brian with the design of the +.B cvs +module and vendor branch support and author of the +.BR checkin ( 1 ) +shell script (the ancestor of +.` "cvs import"). +.SH "SEE ALSO" +.BR ci ( 1 ), +.BR co ( 1 ), +.BR cvs ( 5 ), +.BR diff ( 1 ), +.BR grep ( 1 ), +.BR mkmodules ( 1 ), +.BR patch ( 1 ), +.BR rcs ( 1 ), +.BR rcsdiff ( 1 ), +.BR rcsmerge ( 1 ), +.BR rlog ( 1 ), +.BR rm ( 1 ), +.BR sort ( 1 ). diff --git a/gnu/usr.bin/cvs/doc/cvsclient.texi b/gnu/usr.bin/cvs/doc/cvsclient.texi new file mode 100644 index 0000000..9c8f326 --- /dev/null +++ b/gnu/usr.bin/cvs/doc/cvsclient.texi @@ -0,0 +1,673 @@ +\input texinfo + +@setfilename cvsclient.info + +@node Top +@top CVS Client/Server + +This manual describes the client/server protocol used by CVS. It does +not describe how to use or administer client/server CVS; see the +regular CVS manual for that. + +@menu +* Goals:: Basic design decisions, requirements, scope, etc. +* Notes:: Notes on the current implementation +* How To:: How to remote your favorite CVS command +* Protocol Notes:: Possible enhancements, limitations, etc. of the protocol +* Protocol:: Complete description of the protocol +@end menu + +@node Goals +@chapter Goals + +@itemize @bullet +@item +Do not assume any access to the repository other than via this protocol. +It does not depend on NFS, rdist, etc. + +@item +Providing a reliable transport is outside this protocol. It is expected +that it runs over TCP, UUCP, etc. + +@item +Security and authentication are handled outside this protocol (but see +below about @samp{cvs kserver}). + +@item +This might be a first step towards adding transactions to CVS (i.e. a +set of operations is either executed atomically or none of them is +executed), improving the locking, or other features. The current server +implementation is a long way from being able to do any of these +things. The protocol, however, is not known to contain any defects +which would preclude them. + +@item +The server never has to have any CVS locks in place while it is waiting +for communication with the client. This makes things robust in the face +of flaky networks. + +@item +Data is transferred in large chunks, which is necessary for good +performance. In fact, currently the client uploads all the data +(without waiting for server responses), and then waits for one server +response (which consists of a massive download of all the data). There +may be cases in which it is better to have a richer interraction, but +the need for the server to release all locks whenever it waits for the +client makes it complicated. +@end itemize + +@node Notes +@chapter Notes on the Current Implementation + +The client is built in to the normal @code{cvs} program, triggered by a +@code{CVSROOT} variable containing a colon, for example +@code{cygnus.com:/rel/cvsfiles}. + +The client stores what is stored in checked-out directories (including +@file{CVS}). The way these are stored is totally compatible with +standard CVS. The server requires no storage other than the repository, +which also is totally compatible with standard CVS. + +The server is started by @code{cvs server}. There is no particularly +compelling reason for this rather than making it a separate program +which shares a lot of sources with cvs. + +The server can also be started by @code{cvs kserver}, in which case it +does an initial Kerberos authentication on stdin. If the authentication +succeeds, it subsequently runs identically to @code{cvs server}. + +The current server implementation can use up huge amounts of memory +when transmitting a lot of data over a slow link (i.e. the network is +slower than the server can generate the data). Avoiding this is +tricky because of the goal of not having the server block on the +network when it has locks open (this could lock the repository for +hours if things are running smoothly or longer if not). Several +solutions are possible. The two-pass design would involve first +noting what versions of everything we need (with locks in place) and +then sending the data, blocking on the network, with no locks needed. +The lather-rinse-repeat design would involve doing things as it does +now until a certain amount of server memory is being used (10M?), then +releasing locks, and trying the whole update again (some of it is +presumably already done). One problem with this is getting merges to +work right. The two-pass design appears to be the more elegant of the +two (it actually reduces the amount of time that locks need to be in +place), but people have expressed concerns about whether it would be +slower (because it traverses the repository twice). It is not clear +whether this is a real problem (looking for whether a file needs to be +updated and actually checking it out are done separately already), but +I don't think anyone has investigated carefully. One hybrid approach +which avoids the problem with merges would be to start out in one-pass +mode and switch to two-pass mode if data is backing up--but this +complicates the code and should be undertaken only if the pure +two-pass design is shown to be flawed. + +@node How To +@chapter How to add more remote commands + +It's the usual simple twelve step process. Let's say you're making +the existing @code{cvs fix} command work remotely. + +@itemize @bullet +@item +Add a declaration for the @code{fix} function, which already implements +the @code{cvs fix} command, to @file{server.c}. +@item +Now, the client side. +Add a function @code{client_fix} to @file{client.c}, which calls +@code{parse_cvsroot} and then calls the usual @code{fix} function. +@item +Add a declaration for @code{client_fix} to @file{client.h}. +@item +Add @code{client_fix} to the "fix" entry in the table of commands in +@file{main.c}. +@item +Now for the server side. +Add the @code{serve_fix} routine to @file{server.c}; make it do: +@example @code +static void +serve_fix (arg) + char *arg; +@{ + do_cvs_command (fix); +@} +@end example +@item +Add the server command @code{"fix"} to the table of requests in @file{server.c}. +@item +The @code{fix} function can now be entered in three different situations: +local (the old situation), client, and server. On the server side it probably +will not need any changes to cope. +Modify the @code{fix} function so that if it is run when the variable +@code{client_active} is set, it starts the server, sends over parsed +arguments and possibly files, sends a "fix" command to the server, +and handles responses from the server. Sample code: +@example @code + if (!client_active) @{ + /* Do whatever you used to do */ + @} else @{ + /* We're the local client. Fire up the remote server. */ + start_server (); + + if (local) + if (fprintf (to_server, "Argument -l\n") == EOF) + error (1, errno, "writing to server"); + send_option_string (options); + + send_files (argc, argv, local); + + if (fprintf (to_server, "fix\n") == EOF) + error (1, errno, "writing to server"); + err = get_responses_and_close (); + @} +@end example +@item +Build it locally. Copy the new version into somewhere on the +remote system, in your path so that @code{rsh host cvs} finds it. +Now you can test it. +@item +You may want to set the environment variable @code{CVS_CLIENT_PORT} to +-1 to prevent the client from contacting the server via a direct TCP +link. That will force the client to fall back to using @code{rsh}, +which will run your new binary. +@item +Set the environment variable @code{CVS_CLIENT_LOG} to a filename prefix +such as @file{/tmp/cvslog}. Whenever you run a remote CVS command, +the commands and responses sent across the client/server connection +will be logged in @file{/tmp/cvslog.in} and @file{/tmp/cvslog.out}. +Examine them for problems while you're testing. +@end itemize + +This should produce a good first cut at a working remote @code{cvs fix} +command. You may have to change exactly how arguments are passed, +whether files or just their names are sent, and how some of the deeper +infrastructure of your command copes with remoteness. + +@node Protocol Notes +@chapter Notes on the Protocol + +A number of enhancements are possible: + +@itemize @bullet +@item +The @code{Modified} request could be speeded up by sending diffs rather +than entire files. The client would need some way to keep the version +of the file which was originally checked out, which would double client +disk space requirements or require coordination with editors (e.g. maybe +it could use emacs numbered backups). This would also allow local +operation of @code{cvs diff} without arguments. + +@item +Have the client keep a copy of some part of the repository. This allows +all of @code{cvs diff} and large parts of @code{cvs update} and +@code{cvs ci} to be local. The local copy could be made consistent with +the master copy at night (but if the master copy has been updated since +the latest nightly re-sync, then it would read what it needs to from the +master). + +@item +Provide encryption using kerberos. + +@item +The current procedure for @code{cvs update} is highly sub-optimal if +there are many modified files. One possible alternative would be to +have the client send a first request without the contents of every +modified file, then have the server tell it what files it needs. Note +the server needs to do the what-needs-to-be-updated check twice (or +more, if changes in the repository mean it has to ask the client for +more files), because it can't keep locks open while waiting for the +network. Perhaps this whole thing is irrelevant if client-side +repositories are implemented, and the rcsmerge is done by the client. +@end itemize + +@node Protocol +@chapter The CVS client/server protocol + +@menu +* Entries Lines:: +* Modes:: +* Requests:: +* Responses:: +* Example:: +@end menu + +@node Entries Lines +@section Entries Lines + +Entries lines are transmitted as: + +@example +/ @var{name} / @var{version} / @var{conflict} / @var{options} / @var{tag_or_date} +@end example + +@var{tag_or_date} is either @samp{T} @var{tag} or @samp{D} @var{date} +or empty. If it is followed by a slash, anything after the slash +shall be silently ignored. + +@var{version} can be empty, or start with @samp{0} or @samp{-}, for no +user file, new user file, or user file to be removed, respectively. + +@var{conflict}, if it starts with @samp{+}, indicates that the file had +conflicts in it. The rest of @var{conflict} is @samp{=} if the +timestamp matches the file, or anything else if it doesn't. If +@var{conflict} does not start with a @samp{+}, it is silently ignored. + +@node Modes +@section Modes + +A mode is any number of repetitions of + +@example +@var{mode-type} = @var{data} +@end example + +separated by @samp{,}. + +@var{mode-type} is an identifier composed of alphanumeric characters. +Currently specified: @samp{u} for user, @samp{g} for group, @samp{o} for +other, as specified in POSIX. If at all possible, give these their +POSIX meaning and use other mode-types for other behaviors. For +example, on VMS it shouldn't be hard to make the groups behave like +POSIX, but you would need to use ACLs for some cases. + +@var{data} consists of any data not containing @samp{,}, @samp{\0} or +@samp{\n}. For @samp{u}, @samp{g}, and @samp{o} mode types, data +consists of alphanumeric characters, where @samp{r} means read, @samp{w} +means write, @samp{x} means execute, and unrecognized letters are +silently ignored. + +@node Requests +@section Requests + +File contents (noted below as @var{file transmission}) can be sent in +one of two forms. The simpler form is a number of bytes, followed by a +newline, followed by the specified number of bytes of file contents. +These are the entire contents of the specified file. Second, if both +client and server support @samp{gzip-file-contents}, a @samp{z} may +precede the length, and the `file contents' sent are actually compressed +with @samp{gzip}. The length specified is that of the compressed +version of the file. + +In neither case are the file content followed by any additional data. +The transmission of a file will end with a newline iff that file (or its +compressed form) ends with a newline. + +@table @code +@item Root @var{pathname} \n +Response expected: no. +Tell the server which @code{CVSROOT} to use. + +@item Valid-responses @var{request-list} \n +Response expected: no. +Tell the server what responses the client will accept. +request-list is a space separated list of tokens. + +@item valid-requests \n +Response expected: yes. +Ask the server to send back a @code{Valid-requests} response. + +@item Repository @var{repository} \n +Response expected: no. Tell the server what repository to use. This +should be a directory name from a previous server response. Note that +this both gives a default for @code{Entry } and @code{Modified } and +also for @code{ci} and the other commands; normal usage is to send a +@code{Repository } for each directory in which there will be an +@code{Entry } or @code{Modified }, and then a final @code{Repository } +for the original directory, then the command. + +@item Directory @var{local-directory} \n +Additional data: @var{repository} \n. This is like @code{Repository}, +but the local name of the directory may differ from the repository name. +If the client uses this request, it affects the way the server returns +pathnames; see @ref{Responses}. @var{local-directory} is relative to +the top level at which the command is occurring (i.e. the last +@code{Directory} or @code{Repository} which is sent before the command). + +@item Max-dotdot @var{level} \n +Tell the server that @var{level} levels of directories above the +directory which @code{Directory} requests are relative to will be +needed. For example, if the client is planning to use a +@code{Directory} request for @file{../../foo}, it must send a +@code{Max-dotdot} request with a @var{level} of at least 2. +@code{Max-dotdot} must be sent before the first @code{Directory} +request. + +@item Static-directory \n +Response expected: no. Tell the server that the directory most recently +specified with @code{Repository} or @code{Directory} should not have +additional files checked out unless explicitly requested. The client +sends this if the @code{Entries.Static} flag is set, which is controlled +by the @code{Set-static-directory} and @code{Clear-static-directory} +responses. + +@item Sticky @var{tagspec} \n +Response expected: no. Tell the server that the directory most recently +specified with @code{Repository} has a sticky tag or date @var{tagspec}. +The first character of @var{tagspec} is @samp{T} for a tag, or @samp{D} +for a date. The remainder of @var{tagspec} contains the actual tag or +date. + +@item Checkin-prog @var{program} \n +Response expected: no. Tell the server that the directory most recently +specified with @code{Directory} has a checkin program @var{program}. +Such a program would have been previously set with the +@code{Set-checkin-prog} response. + +@item Update-prog @var{program} \n +Response expected: no. Tell the server that the directory most recently +specified with @code{Directory} has an update program @var{program}. +Such a program would have been previously set with the +@code{Set-update-prog} response. + +@item Entry @var{entry-line} \n +Response expected: no. Tell the server what version of a file is on the +local machine. The name in @var{entry-line} is a name relative to the +directory most recently specified with @code{Repository}. If the user +is operating on only some files in a directory, @code{Entry} requests +for only those files need be included. If an @code{Entry} request is +sent without @code{Modified}, @code{Unchanged}, or @code{Lost} for that +file the meaning depends on whether @code{UseUnchanged} has been sent; +if it has been it means the file is lost, if not it means the file is +unchanged. + +@item Modified @var{filename} \n +Response expected: no. Additional data: mode, \n, file transmission. +Send the server a copy of one locally modified file. @var{filename} is +relative to the most recent repository sent with @code{Repository}. If +the user is operating on only some files in a directory, only those +files need to be included. This can also be sent without @code{Entry}, +if there is no entry for the file. + +@item Lost @var{filename} \n +Response expected: no. Tell the server that @var{filename} no longer +exists. The name is relative to the most recent repository sent with +@code{Repository}. This is used for any case in which @code{Entry} is +being sent but the file no longer exists. If the client has issued the +@code{UseUnchanged} request, then this request is not used. + +@item Unchanged @var{filename} \n +Response expected: no. Tell the server that @var{filename} has not been +modified in the checked out directory. The name is relative to the most +recent repository sent with @code{Repository}. This request can only be +issued if @code{UseUnchanged} has been sent. + +@item UseUnchanged \n +Response expected: no. Tell the server that the client will be +indicating unmodified files with @code{Unchanged}, and that files for +which no information is sent are nonexistent on the client side, not +unchanged. This is necessary for correct behavior since only the server +knows what possible files may exist, and thus what files are +nonexistent. + +@item Argument @var{text} \n +Response expected: no. +Save argument for use in a subsequent command. Arguments +accumulate until an argument-using command is given, at which point +they are forgotten. + +@item Argumentx @var{text} \n +Response expected: no. Append \n followed by text to the current +argument being saved. + +@item Global_option @var{option} \n +Transmit one of the global options @samp{-q}, @samp{-Q}, @samp{-l}, +@samp{-t}, @samp{-r}, or @samp{-n}. @var{option} must be one of those +strings, no variations (such as combining of options) are allowed. For +graceful handling of @code{valid-requests}, it is probably better to +make new global options separate requests, rather than trying to add +them to this request. + +@item expand-modules \n +Response expected: yes. Expand the modules which are specified in the +arguments. Returns the data in @code{Module-expansion} responses. Note +that the server can assume that this is checkout or export, not rtag or +rdiff; the latter do not access the working directory and thus have no +need to expand modules on the client side. + +@item co \n +@itemx update \n +@itemx ci \n +@itemx diff \n +@itemx tag \n +@itemx status \n +@itemx log \n +@itemx add \n +@itemx remove \n +@itemx rdiff \n +@itemx rtag \n +@itemx import \n +@itemx admin \n +@itemx export \n +@itemx history \n +@itemx release \n +Response expected: yes. Actually do a cvs command. This uses any +previous @code{Argument}, @code{Repository}, @code{Entry}, +@code{Modified}, or @code{Lost} requests, if they have been sent. The +last @code{Repository} sent specifies the working directory at the time +of the operation. No provision is made for any input from the user. +This means that @code{ci} must use a @code{-m} argument if it wants to +specify a log message. + +@item update-patches \n +This request does not actually do anything. It is used as a signal that +the server is able to generate patches when given an @code{update} +request. The client must issue the @code{-u} argument to @code{update} +in order to receive patches. + +@item gzip-file-contents @var{level} \n +This request asks the server to filter files it sends to the client +through the @samp{gzip} program, using the specified level of +compression. If this request is not made, the server must not do any +compression. + +This is only a hint to the server. It may still decide (for example, in +the case of very small files, or files that already appear to be +compressed) not to do the compression. Compression is indicated by a +@samp{z} preceding the file length. + +Availability of this request in the server indicates to the client that +it may compress files sent to the server, regardless of whether the +client actually uses this request. + +@item @var{other-request} @var{text} \n +Response expected: yes. +Any unrecognized request expects a response, and does not +contain any additional data. The response will normally be something like +@samp{error unrecognized request}, but it could be a different error if +a previous command which doesn't expect a response produced an error. +@end table + +When the client is done, it drops the connection. + +@node Responses +@section Responses + +After a command which expects a response, the server sends however many +of the following responses are appropriate. Pathnames are of the actual +files operated on (i.e. they do not contain @samp{,v} endings), and are +suitable for use in a subsequent @code{Repository} request. However, if +the client has used the @code{Directory} request, then it is instead a +local directory name relative to the directory in which the command was +given (i.e. the last @code{Directory} before the command). Then a +newline and a repository name (the pathname which is sent if +@code{Directory} is not used). Then the slash and the filename. For +example, for a file @file{i386.mh} which is in the local directory +@file{gas.clean/config} and for which the repository is +@file{/rel/cvsfiles/devo/gas/config}: + +@example +gas.clean/config/ +/rel/cvsfiles/devo/gas/config/i386.mh +@end example + +Any response always ends with @samp{error} or @samp{ok}. This indicates +that the response is over. + +@table @code +@item Valid-requests @var{request-list} \n +Indicate what requests the server will accept. @var{request-list} +is a space separated list of tokens. If the server supports sending +patches, it will include @samp{update-patches} in this list. The +@samp{update-patches} request does not actually do anything. + +@item Checked-in @var{pathname} \n +Additional data: New Entries line, \n. This means a file @var{pathname} +has been successfully operated on (checked in, added, etc.). name in +the Entries line is the same as the last component of @var{pathname}. + +@item New-entry @var{pathname} \n +Additional data: New Entries line, \n. Like @code{Checked-in}, but the +file is not up to date. + +@item Updated @var{pathname} \n +Additional data: New Entries line, \n, mode, \n, file transmission. A +new copy of the file is enclosed. This is used for a new revision of an +existing file, or for a new file, or for any other case in which the +local (client-side) copy of the file needs to be updated, and after +being updated it will be up to date. If any directory in pathname does +not exist, create it. + +@item Merged @var{pathname} \n +This is just like @code{Updated} and takes the same additional data, +with the one difference that after the new copy of the file is enclosed, +it will still not be up to date. Used for the results of a merge, with +or without conflicts. + +@item Patched @var{pathname} \n +This is just like @code{Updated} and takes the same additional data, +with the one difference that instead of sending a new copy of the file, +the server sends a patch produced by @samp{diff -u}. This client must +apply this patch, using the @samp{patch} program, to the existing file. +This will only be used when the client has an exact copy of an earlier +revision of a file. This response is only used if the @code{update} +command is given the @samp{-u} argument. + +@item Checksum @var{checksum}\n +The @var{checksum} applies to the next file sent over via +@code{Updated}, @code{Merged}, or @code{Patched}. In the case of +@code{Patched}, the checksum applies to the file after being patched, +not to the patch itself. The client should compute the checksum itself, +after receiving the file or patch, and signal an error if the checksums +do not match. The checksum is the 128 bit MD5 checksum represented as +32 hex digits. This response is optional, and is only used if the +client supports it (as judged by the @code{Valid-responses} request). + +@item Copy-file @var{pathname} \n +Additional data: @var{newname} \n. Copy file @var{pathname} to +@var{newname} in the same directory where it already is. This does not +affect @code{CVS/Entries}. + +@item Removed @var{pathname} \n +The file has been removed from the repository (this is the case where +cvs prints @samp{file foobar.c is no longer pertinent}). + +@item Remove-entry @var{pathname} \n +The file needs its entry removed from @code{CVS/Entries}, but the file +itself is already gone (this happens in response to a @code{ci} request +which involves committing the removal of a file). + +@item Set-static-directory @var{pathname} \n +This instructs the client to set the @code{Entries.Static} flag, which +it should then send back to the server in a @code{Static-directory} +request whenever the directory is operated on. @var{pathname} ends in a +slash; its purpose is to specify a directory, not a file within a +directory. + +@item Clear-static-directory @var{pathname} \n +Like @code{Set-static-directory}, but clear, not set, the flag. + +@item Set-sticky @var{pathname} \n +Additional data: @var{tagspec} \n. Tell the client to set a sticky tag +or date, which should be supplied with the @code{Sticky} request for +future operations. @var{pathname} ends in a slash; its purpose is to +specify a directory, not a file within a directory. The first character +of @var{tagspec} is @samp{T} for a tag, or @samp{D} for a date. The +remainder of @var{tagspec} contains the actual tag or date. + +@item Clear-sticky @var{pathname} \n +Clear any sticky tag or date set by @code{Set-sticky}. + +@item Set-checkin-prog @var{dir} \n +Additional data: @var{prog} \n. Tell the client to set a checkin +program, which should be supplied with the @code{Checkin-prog} request +for future operations. + +@item Set-update-prog @var{dir} \n +Additional data: @var{prog} \n. Tell the client to set an update +program, which should be supplied with the @code{Update-prog} request +for future operations. + +@item Module-expansion @var{pathname} \n +Return a file or directory which is included in a particular module. +@var{pathname} is relative to cvsroot, unlike most pathnames in +responses. + +@item M @var{text} \n +A one-line message for the user. + +@item E @var{text} \n +Same as @code{M} but send to stderr not stdout. + +@item error @var{errno-code} @samp{ } @var{text} \n +The command completed with an error. @var{errno-code} is a symbolic +error code (e.g. @code{ENOENT}); if the server doesn't support this +feature, or if it's not appropriate for this particular message, it just +omits the errno-code (in that case there are two spaces after +@samp{error}). Text is an error message such as that provided by +strerror(), or any other message the server wants to use. + +@item ok \n +The command completed successfully. +@end table + +@node Example +@section Example + +Lines beginning with @samp{c>} are sent by the client; lines beginning +with @samp{s>} are sent by the server; lines beginning with @samp{#} are +not part of the actual exchange. + +@example +c> Root /rel/cvsfiles +# In actual practice the lists of valid responses and requests would +# be longer +c> Valid-responses Updated Checked-in M ok error +c> valid-requests +s> Valid-requests Root co Modified Entry Repository ci Argument Argumentx +s> ok +# cvs co devo/foo +c> Argument devo/foo +c> co +s> Updated /rel/cvsfiles/devo/foo/foo.c +s> /foo.c/1.4/Mon Apr 19 15:36:47 1993 Mon Apr 19 15:36:47 1993// +s> 26 +s> int mein () @{ abort (); @} +s> Updated /rel/cvsfiles/devo/foo/Makefile +s> /Makefile/1.2/Mon Apr 19 15:36:47 1993 Mon Apr 19 15:36:47 1993// +s> 28 +s> foo: foo.c +s> $(CC) -o foo $< +s> ok +# In actual practice the next part would be a separate connection. +# Here it is shown as part of the same one. +c> Repository /rel/cvsfiles/devo/foo +# foo.c relative to devo/foo just set as Repository. +c> Entry /foo.c/1.4/Mon Apr 19 15:36:47 1993 Mon Apr 19 15:36:47 1993// +c> Entry /Makefile/1.2/Mon Apr 19 15:36:47 1993 Mon Apr 19 15:36:47 1993// +c> Modified foo.c +c> 26 +c> int main () @{ abort (); @} +# cvs ci -m <log message> foo.c +c> Argument -m +c> Argument Well, you see, it took me hours and hours to find this typo and I +c> Argumentx searched and searched and eventually had to ask John for help. +c> Argument foo.c +c> ci +s> Checked-in /rel/cvsfiles/devo/foo/foo.c +s> /foo.c/1.5/ Mon Apr 19 15:54:22 CDT 1993// +s> M Checking in foo.c; +s> M /cygint/rel/cvsfiles/devo/foo/foo.c,v <-- foo.c +s> M new revision: 1.5; previous revision: 1.4 +s> M done +s> ok +@end example +@bye diff --git a/gnu/usr.bin/cvs/examples/checkoutlist b/gnu/usr.bin/cvs/examples/checkoutlist new file mode 100644 index 0000000..45ee6dc --- /dev/null +++ b/gnu/usr.bin/cvs/examples/checkoutlist @@ -0,0 +1,20 @@ +# +#ident "@(#)cvs/examples:$Name: $:$Id: checkoutlist,v 1.2 1995/11/14 23:24:49 woods Exp $" +# +# The "checkoutlist" file is used to support additional version controlled +# administrative files in $CVSROOT/CVSROOT, such as template files. +# +# The first entry on a line is a filename which will be checked out from +# the corresponding RCS file in the $CVSROOT/CVSROOT directory. +# The remainder of the line is an error message to use if the file cannot +# be checked out. +# +# File format: +# +# [<whitespace>]<filename><whitespace><error message><end-of-line> +# +# comment lines begin with '#' +# +rcstemplate +wrap +unwrap diff --git a/gnu/usr.bin/cvs/examples/cvswrappers b/gnu/usr.bin/cvs/examples/cvswrappers new file mode 100644 index 0000000..c666292 --- /dev/null +++ b/gnu/usr.bin/cvs/examples/cvswrappers @@ -0,0 +1,29 @@ +# +#ident "@(#)cvs/examples:$Name: $:$Id: cvswrappers,v 1.3 1995/11/14 23:23:11 woods Exp $" +# +# This file describes wrappers and other binary files to CVS. +# +# Wrappers are the concept where directories of files are to be +# treated as a single file. The intended use is to wrap up a wrapper +# into a single tar such that the tar archive can be treated as a +# single binary file in CVS. +# +# To solve the problem effectively, it was also necessary to be able to +# prevent rcsmerge from merging these files. +# +# Format of wrapper file ($CVSROOT/CVSROOT/cvswrappers or .cvswrappers) +# +# wildcard [option value][option value]... +# +# where option is one of +# -f from cvs filter value: path to filter +# -t to cvs filter value: path to filter +# -m update methodology value: MERGE or COPY +# +# and value is a single-quote delimited value. +# +# +*.nib -f '$CVSROOT/CVSROOT/unwrap %s' -t '$CVSROOT/CVSROOT/wrap %s %s' +*.rtfd -f '$CVSROOT/CVSROOT/unwrap %s' -t '$CVSROOT/CVSROOT/wrap %s %s' +*.draw -f '$CVSROOT/CVSROOT/unwrap %s' -t '$CVSROOT/CVSROOT/wrap %s %s' +*.tiff -m 'COPY' diff --git a/gnu/usr.bin/cvs/examples/rcstemplate b/gnu/usr.bin/cvs/examples/rcstemplate new file mode 100644 index 0000000..c9a2d1e --- /dev/null +++ b/gnu/usr.bin/cvs/examples/rcstemplate @@ -0,0 +1,7 @@ +CVS: +CVS: WARNING: You are commiting a change to the main source repository. +CVS: +CVS: This change will be immediately available to all other users +CVS: of this repository! Please be sure your changes have been +CVS: adequately tested. +CVS: diff --git a/gnu/usr.bin/cvs/examples/taginfo b/gnu/usr.bin/cvs/examples/taginfo new file mode 100644 index 0000000..02de62b --- /dev/null +++ b/gnu/usr.bin/cvs/examples/taginfo @@ -0,0 +1,25 @@ +# +#ident "@(#)cvs/examples:$Name: $:$Id: taginfo,v 1.3 1995/11/14 23:27:52 woods Exp $" +# +# The "taginfo" file is used to control pre-tag checks. +# The filter on the right is invoked with the following arguments: +# +# $1 -- tagname +# $2 -- operation "add" for tag, "mov" for tag -F, and "del" for tag -d +# $3 -- repository +# $4-> file revision [file revision ...] +# +# A non-zero exit of the filter program will cause the tag to be aborted. +# +# The first entry on a line is a regular expression which is tested +# against the directory that the change is being committed to, relative +# to the $CVSROOT. For the first match that is found, then the remainder +# of the line is the name of the filter to run. +# +# If the repository name does not match any of the regular expressions in this +# file, the "DEFAULT" line is used, if it is specified. +# +# If the name "ALL" appears as a regular expression it is always used +# in addition to the first matching regex or "DEFAULT". +# +DEFAULT $CVSROOT/CVSROOT/tag_logging_program diff --git a/gnu/usr.bin/cvs/examples/unwrap b/gnu/usr.bin/cvs/examples/unwrap new file mode 100644 index 0000000..def0561 --- /dev/null +++ b/gnu/usr.bin/cvs/examples/unwrap @@ -0,0 +1,21 @@ +#! /bin/sh +# +# unwrap - extract the combined package (created with wrap) +# +#ident "@(#)cvs/examples:$Name: $:$Id: unwrap,v 1.1 1995/11/14 23:20:30 woods Exp $" + +# move the file to a new name with an extension +rm -rf $1.cvswrap +mv $1 $1.cvswrap + +# untar the file + +if `gzip -t $1.cvswrap > /dev/null 2>&1` +then + gzcat -d $1.cvswrap | gnutar --preserve --sparse -x -f - +else + gnutar --preserve --sparse -x -f $1.cvswrap +fi + +# remove the original +rm -rf $1.cvswrap diff --git a/gnu/usr.bin/cvs/examples/wrap b/gnu/usr.bin/cvs/examples/wrap new file mode 100644 index 0000000..b6a6a77 --- /dev/null +++ b/gnu/usr.bin/cvs/examples/wrap @@ -0,0 +1,21 @@ +#! /bin/sh +# +# wrap - Combine a directory into a single tar package. +# +#ident "@(#)cvs/examples:$Name: $:$Id: wrap,v 1.1 1995/11/14 23:20:32 woods Exp $" + +# This script is always called with the current directory set to +# where the file to be combined exists. but i may get called with a +# path to where cvs first started executing. (this probably should be +# fixed in cvs) so strip out all of the directory information. The +# first sed expression will only work if the path has a leading / +# if it doesn't the one in the if statement will work. +DIRNAME=`echo $1 | sed -e "s|/.*/||g"` +if [ ! -d $DIRNAME ] ; then + DIRNAME=`echo $1 | sed -e "s|.*/||g"` +fi +# +# Now tar up the directory but we now will only get a relative path +# even if the user did a cvs commit . at the top. +# +gnutar --preserve --sparse -cf - $DIRNAME | gzip --no-name --best -c > $2 diff --git a/gnu/usr.bin/cvs/lib/error.h b/gnu/usr.bin/cvs/lib/error.h new file mode 100644 index 0000000..7d4f535 --- /dev/null +++ b/gnu/usr.bin/cvs/lib/error.h @@ -0,0 +1,47 @@ +/* error.h -- declaration for error-reporting function + Copyright (C) 1995 Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#ifndef _error_h_ +#define _error_h_ + +#ifndef __attribute__ +/* This feature is available in gcc versions 2.5 and later. */ +# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5) || __STRICT_ANSI__ +# define __attribute__(Spec) /* empty */ +# endif +/* The __-protected variants of `format' and `printf' attributes + are accepted by gcc versions 2.6.4 (effectively 2.7) and later. */ +# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 7) +# define __format__ format +# define __printf__ printf +# endif +#endif + +#if __STDC__ +void error (int, int, const char *, ...) \ + __attribute__ ((__format__ (__printf__, 3, 4))); +#else +void error (); +#endif + +/* If non-zero, error will use the CVS protocol to report error + messages. This will only be set in the CVS server parent process; + most other code is run via do_cvs_command, which forks off a child + process and packages up its stderr in the protocol. */ +extern int error_use_protocol; + +#endif /* _error_h_ */ diff --git a/gnu/usr.bin/cvs/lib/filesubr.c b/gnu/usr.bin/cvs/lib/filesubr.c new file mode 100644 index 0000000..3a52691 --- /dev/null +++ b/gnu/usr.bin/cvs/lib/filesubr.c @@ -0,0 +1,640 @@ +/* filesubr.c --- subroutines for dealing with files + Jim Blandy <jimb@cyclic.com> + + This file is part of GNU CVS. + + GNU CVS is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; either version 2, or (at your option) any + later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* These functions were moved out of subr.c because they need different + definitions under operating systems (like, say, Windows NT) with different + file system semantics. */ + +#include "cvs.h" + +#ifndef lint +static const char rcsid[] = "$CVSid:$"; +USE(rcsid); +#endif + +/* + * I don't know of a convenient way to test this at configure time, or else + * I'd certainly do it there. + */ +#if defined(NeXT) +#define LOSING_TMPNAM_FUNCTION +#endif + +static int deep_remove_dir PROTO((const char *path)); + +/* + * Copies "from" to "to". + */ +void +copy_file (from, to) + const char *from; + const char *to; +{ + struct stat sb; + struct utimbuf t; + int fdin, fdout; + + if (trace) +#ifdef SERVER_SUPPORT + (void) fprintf (stderr, "%c-> copy(%s,%s)\n", + (server_active) ? 'S' : ' ', from, to); +#else + (void) fprintf (stderr, "-> copy(%s,%s)\n", from, to); +#endif + if (noexec) + return; + + if ((fdin = open (from, O_RDONLY)) < 0) + error (1, errno, "cannot open %s for copying", from); + if (fstat (fdin, &sb) < 0) + error (1, errno, "cannot fstat %s", from); + if ((fdout = creat (to, (int) sb.st_mode & 07777)) < 0) + error (1, errno, "cannot create %s for copying", to); + if (sb.st_size > 0) + { + char buf[BUFSIZ]; + int n; + + for (;;) + { + n = read (fdin, buf, sizeof(buf)); + if (n == -1) + { +#ifdef EINTR + if (errno == EINTR) + continue; +#endif + error (1, errno, "cannot read file %s for copying", from); + } + else if (n == 0) + break; + + if (write(fdout, buf, n) != n) { + error (1, errno, "cannot write file %s for copying", to); + } + } + +#ifdef HAVE_FSYNC + if (fsync (fdout)) + error (1, errno, "cannot fsync file %s after copying", to); +#endif + } + + if (close (fdin) < 0) + error (0, errno, "cannot close %s", from); + if (close (fdout) < 0) + error (1, errno, "cannot close %s", to); + + /* now, set the times for the copied file to match those of the original */ + memset ((char *) &t, 0, sizeof (t)); + t.actime = sb.st_atime; + t.modtime = sb.st_mtime; + (void) utime (to, &t); +} + +/* FIXME-krp: these functions would benefit from caching the char * & + stat buf. */ + +/* + * Returns non-zero if the argument file is a directory, or is a symbolic + * link which points to a directory. + */ +int +isdir (file) + const char *file; +{ + struct stat sb; + + if (stat (file, &sb) < 0) + return (0); + return (S_ISDIR (sb.st_mode)); +} + +/* + * Returns non-zero if the argument file is a symbolic link. + */ +int +islink (file) + const char *file; +{ +#ifdef S_ISLNK + struct stat sb; + + if (lstat (file, &sb) < 0) + return (0); + return (S_ISLNK (sb.st_mode)); +#else + return (0); +#endif +} + +/* + * Returns non-zero if the argument file exists. + */ +int +isfile (file) + const char *file; +{ + return isaccessible(file, F_OK); +} + +/* + * Returns non-zero if the argument file is readable. + */ +int +isreadable (file) + const char *file; +{ + return isaccessible(file, R_OK); +} + +/* + * Returns non-zero if the argument file is writable. + */ +int +iswritable (file) + const char *file; +{ + return isaccessible(file, W_OK); +} + +/* + * Returns non-zero if the argument file is accessable according to + * mode. If compiled with SETXID_SUPPORT also works if cvs has setxid + * bits set. + */ +int +isaccessible (file, mode) + const char *file; + const int mode; +{ +#ifdef SETXID_SUPPORT + struct stat sb; + int umask = 0; + int gmask = 0; + int omask = 0; + int uid; + + if (stat(file, &sb) == -1) + return 0; + if (mode == F_OK) + return 1; + + uid = geteuid(); + if (uid == 0) /* superuser */ + { + if (mode & X_OK) + return sb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH); + else + return 1; + } + + if (mode & R_OK) + { + umask |= S_IRUSR; + gmask |= S_IRGRP; + omask |= S_IROTH; + } + if (mode & W_OK) + { + umask |= S_IWUSR; + gmask |= S_IWGRP; + omask |= S_IWOTH; + } + if (mode & X_OK) + { + umask |= S_IXUSR; + gmask |= S_IXGRP; + omask |= S_IXOTH; + } + + if (sb.st_uid == uid) + return (sb.st_mode & umask) == umask; + else if (sb.st_gid == getegid()) + return (sb.st_mode & gmask) == gmask; + else + return (sb.st_mode & omask) == omask; +#else + return access(file, mode) == 0; +#endif +} + +/* + * Open a file and die if it fails + */ +FILE * +open_file (name, mode) + const char *name; + const char *mode; +{ + FILE *fp; + + if ((fp = fopen (name, mode)) == NULL) + error (1, errno, "cannot open %s", name); + return (fp); +} + +/* + * Make a directory and die if it fails + */ +void +make_directory (name) + const char *name; +{ + struct stat sb; + + if (stat (name, &sb) == 0 && (!S_ISDIR (sb.st_mode))) + error (0, 0, "%s already exists but is not a directory", name); + if (!noexec && mkdir (name, 0777) < 0) + error (1, errno, "cannot make directory %s", name); +} + +/* + * Make a path to the argument directory, printing a message if something + * goes wrong. + */ +void +make_directories (name) + const char *name; +{ + char *cp; + + if (noexec) + return; + + if (mkdir (name, 0777) == 0 || errno == EEXIST) + return; + if (! existence_error (errno)) + { + error (0, errno, "cannot make path to %s", name); + return; + } + if ((cp = strrchr (name, '/')) == NULL) + return; + *cp = '\0'; + make_directories (name); + *cp++ = '/'; + if (*cp == '\0') + return; + (void) mkdir (name, 0777); +} + +/* + * Change the mode of a file, either adding write permissions, or removing + * all write permissions. Either change honors the current umask setting. + */ +void +xchmod (fname, writable) + char *fname; + int writable; +{ + struct stat sb; + mode_t mode, oumask; + + if (stat (fname, &sb) < 0) + { + if (!noexec) + error (0, errno, "cannot stat %s", fname); + return; + } + oumask = umask (0); + (void) umask (oumask); + if (writable) + { + mode = sb.st_mode | (~oumask + & (((sb.st_mode & S_IRUSR) ? S_IWUSR : 0) + | ((sb.st_mode & S_IRGRP) ? S_IWGRP : 0) + | ((sb.st_mode & S_IROTH) ? S_IWOTH : 0))); + } + else + { + mode = sb.st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH) & ~oumask; + } + + if (trace) +#ifdef SERVER_SUPPORT + (void) fprintf (stderr, "%c-> chmod(%s,%o)\n", + (server_active) ? 'S' : ' ', fname, mode); +#else + (void) fprintf (stderr, "-> chmod(%s,%o)\n", fname, mode); +#endif + if (noexec) + return; + + if (chmod (fname, mode) < 0) + error (0, errno, "cannot change mode of file %s", fname); +} + +/* + * Rename a file and die if it fails + */ +void +rename_file (from, to) + const char *from; + const char *to; +{ + if (trace) +#ifdef SERVER_SUPPORT + (void) fprintf (stderr, "%c-> rename(%s,%s)\n", + (server_active) ? 'S' : ' ', from, to); +#else + (void) fprintf (stderr, "-> rename(%s,%s)\n", from, to); +#endif + if (noexec) + return; + + if (rename (from, to) < 0) + error (1, errno, "cannot rename file %s to %s", from, to); +} + +/* + * link a file, if possible. + */ +int +link_file (from, to) + const char *from; + const char *to; +{ + if (trace) +#ifdef SERVER_SUPPORT + (void) fprintf (stderr, "%c-> link(%s,%s)\n", + (server_active) ? 'S' : ' ', from, to); +#else + (void) fprintf (stderr, "-> link(%s,%s)\n", from, to); +#endif + if (noexec) + return (0); + + return (link (from, to)); +} + +/* + * unlink a file, if possible. + */ +int +unlink_file (f) + const char *f; +{ + if (trace) +#ifdef SERVER_SUPPORT + (void) fprintf (stderr, "%c-> unlink(%s)\n", + (server_active) ? 'S' : ' ', f); +#else + (void) fprintf (stderr, "-> unlink(%s)\n", f); +#endif + if (noexec) + return (0); + + return (unlink (f)); +} + +/* + * Unlink a file or dir, if possible. If it is a directory do a deep + * removal of all of the files in the directory. Return -1 on error + * (in which case errno is set). + */ +int +unlink_file_dir (f) + const char *f; +{ + if (trace) +#ifdef SERVER_SUPPORT + (void) fprintf (stderr, "%c-> unlink_file_dir(%s)\n", + (server_active) ? 'S' : ' ', f); +#else + (void) fprintf (stderr, "-> unlink_file_dir(%s)\n", f); +#endif + if (noexec) + return (0); + + if (unlink (f) != 0) + { + /* under NEXTSTEP errno is set to return EPERM if + * the file is a directory,or if the user is not + * allowed to read or write to the file. + * [This is probably a bug in the O/S] + * other systems will return EISDIR to indicate + * that the path is a directory. + */ + if (errno == EISDIR || errno == EPERM) + return deep_remove_dir (f); + else + /* The file wasn't a directory and some other + * error occured + */ + return -1; + } + /* We were able to remove the file from the disk */ + return 0; +} + +/* Remove a directory and everything it contains. Returns 0 for + * success, -1 for failure (in which case errno is set). + */ + +static int +deep_remove_dir (path) + const char *path; +{ + DIR *dirp; + struct dirent *dp; + char buf[PATH_MAX]; + + if ( rmdir (path) != 0 && errno == ENOTEMPTY ) + { + if ((dirp = opendir (path)) == NULL) + /* If unable to open the directory return + * an error + */ + return -1; + + while ((dp = readdir (dirp)) != NULL) + { + if (strcmp (dp->d_name, ".") == 0 || + strcmp (dp->d_name, "..") == 0) + continue; + + sprintf (buf, "%s/%s", path, dp->d_name); + + if (unlink (buf) != 0 ) + { + if (errno == EISDIR || errno == EPERM) + { + if (deep_remove_dir (buf)) + { + closedir (dirp); + return -1; + } + } + else + { + /* buf isn't a directory, or there are + * some sort of permision problems + */ + closedir (dirp); + return -1; + } + } + } + closedir (dirp); + return rmdir (path); + } + /* Was able to remove the directory return 0 */ + return 0; +} + +/* Read NCHARS bytes from descriptor FD into BUF. + Return the number of characters successfully read. + The number returned is always NCHARS unless end-of-file or error. */ +static size_t +block_read (fd, buf, nchars) + int fd; + char *buf; + size_t nchars; +{ + char *bp = buf; + size_t nread; + + do + { + nread = read (fd, bp, nchars); + if (nread == (size_t)-1) + { +#ifdef EINTR + if (errno == EINTR) + continue; +#endif + return (size_t)-1; + } + + if (nread == 0) + break; + + bp += nread; + nchars -= nread; + } while (nchars != 0); + + return bp - buf; +} + + +/* + * Compare "file1" to "file2". Return non-zero if they don't compare exactly. + */ +int +xcmp (file1, file2) + const char *file1; + const char *file2; +{ + char *buf1, *buf2; + struct stat sb1, sb2; + int fd1, fd2; + int ret; + + if ((fd1 = open (file1, O_RDONLY)) < 0) + error (1, errno, "cannot open file %s for comparing", file1); + if ((fd2 = open (file2, O_RDONLY)) < 0) + error (1, errno, "cannot open file %s for comparing", file2); + if (fstat (fd1, &sb1) < 0) + error (1, errno, "cannot fstat %s", file1); + if (fstat (fd2, &sb2) < 0) + error (1, errno, "cannot fstat %s", file2); + + /* A generic file compare routine might compare st_dev & st_ino here + to see if the two files being compared are actually the same file. + But that won't happen in CVS, so we won't bother. */ + + if (sb1.st_size != sb2.st_size) + ret = 1; + else if (sb1.st_size == 0) + ret = 0; + else + { + /* FIXME: compute the optimal buffer size by computing the least + common multiple of the files st_blocks field */ + size_t buf_size = 8 * 1024; + size_t read1; + size_t read2; + + buf1 = xmalloc (buf_size); + buf2 = xmalloc (buf_size); + + do + { + read1 = block_read (fd1, buf1, buf_size); + if (read1 == (size_t)-1) + error (1, errno, "cannot read file %s for comparing", file1); + + read2 = block_read (fd2, buf2, buf_size); + if (read2 == (size_t)-1) + error (1, errno, "cannot read file %s for comparing", file2); + + /* assert (read1 == read2); */ + + ret = memcmp(buf1, buf2, read1); + } while (ret == 0 && read1 == buf_size); + + free (buf1); + free (buf2); + } + + (void) close (fd1); + (void) close (fd2); + return (ret); +} + +#ifdef LOSING_TMPNAM_FUNCTION +char *tmpnam(char *s) +{ + static char value[L_tmpnam+1]; + + if (s){ + strcpy(s,"/tmp/cvsXXXXXX"); + mktemp(s); + return s; + }else{ + strcpy(value,"/tmp/cvsXXXXXX"); + mktemp(s); + return value; + } +} +#endif + +/* Return non-zero iff FILENAME is absolute. + Trivial under Unix, but more complicated under other systems. */ +int +isabsolute (filename) + const char *filename; +{ + return filename[0] == '/'; +} + + +/* Return a pointer into PATH's last component. */ +char * +last_component (path) + char *path; +{ + char *last = strrchr (path, '/'); + + if (last) + return last + 1; + else + return path; +} diff --git a/gnu/usr.bin/cvs/lib/getline.c b/gnu/usr.bin/cvs/lib/getline.c new file mode 100644 index 0000000..c699461 --- /dev/null +++ b/gnu/usr.bin/cvs/lib/getline.c @@ -0,0 +1,126 @@ +/* getline.c -- Replacement for GNU C library function getline + +Copyright (C) 1993 Free Software Foundation, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* Written by Jan Brittenson, bson@gnu.ai.mit.edu. */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sys/types.h> +#include <stdio.h> +#define NDEBUG +#include <assert.h> + +#if STDC_HEADERS +#include <stdlib.h> +#else +char *malloc (), *realloc (); +#endif + +/* Always add at least this many bytes when extending the buffer. */ +#define MIN_CHUNK 64 + +/* Read up to (and including) a TERMINATOR from STREAM into *LINEPTR + + OFFSET (and null-terminate it). *LINEPTR is a pointer returned from + malloc (or NULL), pointing to *N characters of space. It is realloc'd + as necessary. Return the number of characters read (not including the + null terminator), or -1 on error or EOF. */ + +int +getstr (lineptr, n, stream, terminator, offset) + char **lineptr; + size_t *n; + FILE *stream; + char terminator; + int offset; +{ + int nchars_avail; /* Allocated but unused chars in *LINEPTR. */ + char *read_pos; /* Where we're reading into *LINEPTR. */ + int ret; + + if (!lineptr || !n || !stream) + return -1; + + if (!*lineptr) + { + *n = MIN_CHUNK; + *lineptr = malloc (*n); + if (!*lineptr) + return -1; + } + + nchars_avail = *n - offset; + read_pos = *lineptr + offset; + + for (;;) + { + register int c = getc (stream); + + /* We always want at least one char left in the buffer, since we + always (unless we get an error while reading the first char) + NUL-terminate the line buffer. */ + + assert(*n - nchars_avail == read_pos - *lineptr); + if (nchars_avail < 2) + { + if (*n > MIN_CHUNK) + *n *= 2; + else + *n += MIN_CHUNK; + + nchars_avail = *n + *lineptr - read_pos; + *lineptr = realloc (*lineptr, *n); + if (!*lineptr) + return -1; + read_pos = *n - nchars_avail + *lineptr; + assert(*n - nchars_avail == read_pos - *lineptr); + } + + if (c == EOF || ferror (stream)) + { + /* Return partial line, if any. */ + if (read_pos == *lineptr) + return -1; + else + break; + } + + *read_pos++ = c; + nchars_avail--; + + if (c == terminator) + /* Return the line. */ + break; + } + + /* Done - NUL terminate and return the number of chars read. */ + *read_pos = '\0'; + + ret = read_pos - (*lineptr + offset); + return ret; +} + +int +getline (lineptr, n, stream) + char **lineptr; + size_t *n; + FILE *stream; +{ + return getstr (lineptr, n, stream, '\n', 0); +} diff --git a/gnu/usr.bin/cvs/lib/getline.h b/gnu/usr.bin/cvs/lib/getline.h new file mode 100644 index 0000000..30bcc25 --- /dev/null +++ b/gnu/usr.bin/cvs/lib/getline.h @@ -0,0 +1,15 @@ +#ifndef _getline_h_ +#define _getline_h_ 1 + +#include <stdio.h> + +#if defined (__GNUC__) || (defined (__STDC__) && __STDC__) +#define __PROTO(args) args +#else +#define __PROTO(args) () +#endif /* GCC. */ + +int + getline __PROTO ((char **_lineptr, size_t *_n, FILE *_stream)); + +#endif /* _getline_h_ */ diff --git a/gnu/usr.bin/cvs/lib/md5.c b/gnu/usr.bin/cvs/lib/md5.c new file mode 100644 index 0000000..4ad99cd --- /dev/null +++ b/gnu/usr.bin/cvs/lib/md5.c @@ -0,0 +1,277 @@ +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ + +#include "config.h" + +#if HAVE_STRING_H || STDC_HEADERS +#include <string.h> /* for memcpy() */ +#endif + +/* Add prototype support. */ +#ifndef PROTO +#if defined (USE_PROTOTYPES) ? USE_PROTOTYPES : defined (__STDC__) +#define PROTO(ARGS) ARGS +#else +#define PROTO(ARGS) () +#endif +#endif + +#include "md5.h" + +void byteReverse PROTO ((unsigned char *buf, unsigned longs)); + +#ifndef ASM_MD5 +/* + * Note: this code is harmless on little-endian machines. + */ +void byteReverse (buf, longs) + unsigned char *buf; + unsigned longs; +{ + uint32 t; + do { + t = (uint32)((unsigned)buf[3]<<8 | buf[2]) << 16 | + ((unsigned)buf[1]<<8 | buf[0]); + *(uint32 *)buf = t; + buf += 4; + } while (--longs); +} +#endif + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void +MD5Init(ctx) + struct MD5Context *ctx; +{ + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void +MD5Update(ctx, buf, len) + struct MD5Context *ctx; + unsigned char const *buf; + unsigned len; +{ + uint32 t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((uint32)len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if ( t ) { + unsigned char *p = (unsigned char *)ctx->in + t; + + t = 64-t; + if (len < t) { + memcpy(p, buf, len); + return; + } + memcpy(p, buf, t); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32 *)ctx->in); + buf += t; + len -= t; + } + + /* Process data in 64-byte chunks */ + + while (len >= 64) { + memcpy(ctx->in, buf, 64); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32 *)ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + memcpy(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void +MD5Final(digest, ctx) + unsigned char digest[16]; + struct MD5Context *ctx; +{ + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset(p, 0, count); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32 *)ctx->in); + + /* Now fill the next block with 56 bytes */ + memset(ctx->in, 0, 56); + } else { + /* Pad block to 56 bytes */ + memset(p, 0, count-8); + } + byteReverse(ctx->in, 14); + + /* Append length in bits and transform */ + ((uint32 *)ctx->in)[ 14 ] = ctx->bits[0]; + ((uint32 *)ctx->in)[ 15 ] = ctx->bits[1]; + + MD5Transform(ctx->buf, (uint32 *)ctx->in); + byteReverse((unsigned char *)ctx->buf, 4); + memcpy(digest, ctx->buf, 16); + memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */ +} + +#ifndef ASM_MD5 + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +void +MD5Transform(buf, in) + uint32 buf[4]; + uint32 const in[16]; +{ + register uint32 a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[ 0]+0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[ 1]+0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[ 2]+0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[ 3]+0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[ 4]+0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[ 5]+0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[ 6]+0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[ 7]+0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[ 8]+0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[ 9]+0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10]+0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11]+0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12]+0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13]+0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14]+0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15]+0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[ 1]+0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[ 6]+0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11]+0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[ 0]+0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[ 5]+0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10]+0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15]+0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[ 4]+0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[ 9]+0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14]+0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[ 3]+0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[ 8]+0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13]+0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[ 2]+0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[ 7]+0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12]+0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[ 5]+0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[ 8]+0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11]+0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14]+0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[ 1]+0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[ 4]+0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[ 7]+0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10]+0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13]+0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[ 0]+0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[ 3]+0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[ 6]+0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[ 9]+0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12]+0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15]+0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[ 2]+0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[ 0]+0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[ 7]+0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14]+0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[ 5]+0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12]+0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[ 3]+0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10]+0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[ 1]+0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[ 8]+0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15]+0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[ 6]+0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13]+0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[ 4]+0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11]+0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[ 2]+0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[ 9]+0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} +#endif diff --git a/gnu/usr.bin/cvs/lib/md5.h b/gnu/usr.bin/cvs/lib/md5.h new file mode 100644 index 0000000..bfe79cc --- /dev/null +++ b/gnu/usr.bin/cvs/lib/md5.h @@ -0,0 +1,31 @@ +#ifndef MD5_H +#define MD5_H + +#if SIZEOF_LONG == 4 +typedef unsigned long uint32; +#else +#if SIZEOF_INT == 4 +typedef unsigned int uint32; +#else +Congratulations! You get to rewrite this code so that it does not require +a 32-bit integer type! (Or maybe you just need to reconfigure.) +#endif +#endif + +struct MD5Context { + uint32 buf[4]; + uint32 bits[2]; + unsigned char in[64]; +}; + +void MD5Init PROTO((struct MD5Context *context)); +void MD5Update PROTO((struct MD5Context *context, unsigned char const *buf, unsigned len)); +void MD5Final PROTO((unsigned char digest[16], struct MD5Context *context)); +void MD5Transform PROTO((uint32 buf[4], uint32 const in[16])); + +/* + * This is needed to make RSAREF happy on some MS-DOS compilers. + */ +typedef struct MD5Context MD5_CTX; + +#endif /* !MD5_H */ diff --git a/gnu/usr.bin/cvs/lib/run.c b/gnu/usr.bin/cvs/lib/run.c new file mode 100644 index 0000000..6a06a38 --- /dev/null +++ b/gnu/usr.bin/cvs/lib/run.c @@ -0,0 +1,533 @@ +/* run.c --- routines for executing subprocesses. + + This file is part of GNU CVS. + + GNU CVS is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; either version 2, or (at your option) any + later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#include "cvs.h" + +#ifdef HAVE_VPRINTF +#if defined (USE_PROTOTYPES) ? USE_PROTOTYPES : defined (__STDC__) +#include <stdarg.h> +#define VA_START(args, lastarg) va_start(args, lastarg) +#else +#include <varargs.h> +#define VA_START(args, lastarg) va_start(args) +#endif +#else +#define va_alist a1, a2, a3, a4, a5, a6, a7, a8 +#define va_dcl char *a1, *a2, *a3, *a4, *a5, *a6, *a7, *a8; +#endif + +static void run_add_arg PROTO((const char *s)); +static void run_init_prog PROTO((void)); + +extern char *strtok (); + +/* + * To exec a program under CVS, first call run_setup() to setup any initial + * arguments. The options to run_setup are essentially like printf(). The + * arguments will be parsed into whitespace separated words and added to the + * global run_argv list. + * + * Then, optionally call run_arg() for each additional argument that you'd like + * to pass to the executed program. + * + * Finally, call run_exec() to execute the program with the specified arguments. + * The execvp() syscall will be used, so that the PATH is searched correctly. + * File redirections can be performed in the call to run_exec(). + */ +static char *run_prog; +static char **run_argv; +static int run_argc; +static int run_argc_allocated; + +/* VARARGS */ +#if defined (HAVE_VPRINTF) && (defined (USE_PROTOTYPES) ? USE_PROTOTYPES : defined (__STDC__)) +void +run_setup (const char *fmt,...) +#else +void +run_setup (fmt, va_alist) + char *fmt; + va_dcl +#endif +{ +#ifdef HAVE_VPRINTF + va_list args; +#endif + char *cp; + int i; + + run_init_prog (); + + /* clean out any malloc'ed values from run_argv */ + for (i = 0; i < run_argc; i++) + { + if (run_argv[i]) + { + free (run_argv[i]); + run_argv[i] = (char *) 0; + } + } + run_argc = 0; + + /* process the varargs into run_prog */ +#ifdef HAVE_VPRINTF + VA_START (args, fmt); + (void) vsprintf (run_prog, fmt, args); + va_end (args); +#else + (void) sprintf (run_prog, fmt, a1, a2, a3, a4, a5, a6, a7, a8); +#endif + + /* put each word into run_argv, allocating it as we go */ + for (cp = strtok (run_prog, " \t"); cp; cp = strtok ((char *) NULL, " \t")) + run_add_arg (cp); +} + +void +run_arg (s) + const char *s; +{ + run_add_arg (s); +} + +/* VARARGS */ +#if defined (HAVE_VPRINTF) && (defined (USE_PROTOTYPES) ? USE_PROTOTYPES : defined (__STDC__)) +void +run_args (const char *fmt,...) +#else +void +run_args (fmt, va_alist) + char *fmt; + va_dcl +#endif +{ +#ifdef HAVE_VPRINTF + va_list args; +#endif + + run_init_prog (); + + /* process the varargs into run_prog */ +#ifdef HAVE_VPRINTF + VA_START (args, fmt); + (void) vsprintf (run_prog, fmt, args); + va_end (args); +#else + (void) sprintf (run_prog, fmt, a1, a2, a3, a4, a5, a6, a7, a8); +#endif + + /* and add the (single) argument to the run_argv list */ + run_add_arg (run_prog); +} + +static void +run_add_arg (s) + const char *s; +{ + /* allocate more argv entries if we've run out */ + if (run_argc >= run_argc_allocated) + { + run_argc_allocated += 50; + run_argv = (char **) xrealloc ((char *) run_argv, + run_argc_allocated * sizeof (char **)); + } + + if (s) + run_argv[run_argc++] = xstrdup (s); + else + run_argv[run_argc] = (char *) 0; /* not post-incremented on purpose! */ +} + +static void +run_init_prog () +{ + /* make sure that run_prog is allocated once */ + if (run_prog == (char *) 0) + run_prog = xmalloc (10 * 1024); /* 10K of args for _setup and _arg */ +} + +int +run_exec (stin, stout, sterr, flags) + char *stin; + char *stout; + char *sterr; + int flags; +{ + int shin, shout, sherr; + int mode_out, mode_err; + int status; + int rc = -1; + int rerrno = 0; + int pid, w; + +#ifdef POSIX_SIGNALS + sigset_t sigset_mask, sigset_omask; + struct sigaction act, iact, qact; + +#else +#ifdef BSD_SIGNALS + int mask; + struct sigvec vec, ivec, qvec; + +#else + RETSIGTYPE (*istat) (), (*qstat) (); +#endif +#endif + + if (trace) + { +#ifdef SERVER_SUPPORT + (void) fprintf (stderr, "%c-> system(", (server_active) ? 'S' : ' '); +#else + (void) fprintf (stderr, "-> system("); +#endif + run_print (stderr); + (void) fprintf (stderr, ")\n"); + } + if (noexec && (flags & RUN_REALLY) == 0) + return (0); + + /* make sure that we are null terminated, since we didn't calloc */ + run_add_arg ((char *) 0); + + /* setup default file descriptor numbers */ + shin = 0; + shout = 1; + sherr = 2; + + /* set the file modes for stdout and stderr */ + mode_out = mode_err = O_WRONLY | O_CREAT; + mode_out |= ((flags & RUN_STDOUT_APPEND) ? O_APPEND : O_TRUNC); + mode_err |= ((flags & RUN_STDERR_APPEND) ? O_APPEND : O_TRUNC); + + if (stin && (shin = open (stin, O_RDONLY)) == -1) + { + rerrno = errno; + error (0, errno, "cannot open %s for reading (prog %s)", + stin, run_argv[0]); + goto out0; + } + if (stout && (shout = open (stout, mode_out, 0666)) == -1) + { + rerrno = errno; + error (0, errno, "cannot open %s for writing (prog %s)", + stout, run_argv[0]); + goto out1; + } + if (sterr && (flags & RUN_COMBINED) == 0) + { + if ((sherr = open (sterr, mode_err, 0666)) == -1) + { + rerrno = errno; + error (0, errno, "cannot open %s for writing (prog %s)", + sterr, run_argv[0]); + goto out2; + } + } + + /* Make sure we don't flush this twice, once in the subprocess. */ + fflush (stdout); + fflush (stderr); + + /* The output files, if any, are now created. Do the fork and dups */ +#ifdef HAVE_VFORK + pid = vfork (); +#else + pid = fork (); +#endif + if (pid == 0) + { + if (shin != 0) + { + (void) dup2 (shin, 0); + (void) close (shin); + } + if (shout != 1) + { + (void) dup2 (shout, 1); + (void) close (shout); + } + if (flags & RUN_COMBINED) + (void) dup2 (1, 2); + else if (sherr != 2) + { + (void) dup2 (sherr, 2); + (void) close (sherr); + } + + /* dup'ing is done. try to run it now */ + (void) execvp (run_argv[0], run_argv); + error (0, errno, "cannot exec %s", run_argv[0]); + _exit (127); + } + else if (pid == -1) + { + rerrno = errno; + goto out; + } + + /* the parent. Ignore some signals for now */ +#ifdef POSIX_SIGNALS + if (flags & RUN_SIGIGNORE) + { + act.sa_handler = SIG_IGN; + (void) sigemptyset (&act.sa_mask); + act.sa_flags = 0; + (void) sigaction (SIGINT, &act, &iact); + (void) sigaction (SIGQUIT, &act, &qact); + } + else + { + (void) sigemptyset (&sigset_mask); + (void) sigaddset (&sigset_mask, SIGINT); + (void) sigaddset (&sigset_mask, SIGQUIT); + (void) sigprocmask (SIG_SETMASK, &sigset_mask, &sigset_omask); + } +#else +#ifdef BSD_SIGNALS + if (flags & RUN_SIGIGNORE) + { + memset ((char *) &vec, 0, sizeof (vec)); + vec.sv_handler = SIG_IGN; + (void) sigvec (SIGINT, &vec, &ivec); + (void) sigvec (SIGQUIT, &vec, &qvec); + } + else + mask = sigblock (sigmask (SIGINT) | sigmask (SIGQUIT)); +#else + istat = signal (SIGINT, SIG_IGN); + qstat = signal (SIGQUIT, SIG_IGN); +#endif +#endif + + /* wait for our process to die and munge return status */ +#ifdef POSIX_SIGNALS + while ((w = waitpid (pid, &status, 0)) == -1 && errno == EINTR) + ; +#else + while ((w = wait (&status)) != pid) + { + if (w == -1 && errno != EINTR) + break; + } +#endif + if (w == -1) + { + rc = -1; + rerrno = errno; + } + else if (WIFEXITED (status)) + rc = WEXITSTATUS (status); + else if (WIFSIGNALED (status)) + { + if (WTERMSIG (status) == SIGPIPE) + error (1, 0, "broken pipe"); + rc = 2; + } + else + rc = 1; + + /* restore the signals */ +#ifdef POSIX_SIGNALS + if (flags & RUN_SIGIGNORE) + { + (void) sigaction (SIGINT, &iact, (struct sigaction *) NULL); + (void) sigaction (SIGQUIT, &qact, (struct sigaction *) NULL); + } + else + (void) sigprocmask (SIG_SETMASK, &sigset_omask, (sigset_t *) NULL); +#else +#ifdef BSD_SIGNALS + if (flags & RUN_SIGIGNORE) + { + (void) sigvec (SIGINT, &ivec, (struct sigvec *) NULL); + (void) sigvec (SIGQUIT, &qvec, (struct sigvec *) NULL); + } + else + (void) sigsetmask (mask); +#else + (void) signal (SIGINT, istat); + (void) signal (SIGQUIT, qstat); +#endif +#endif + + /* cleanup the open file descriptors */ + out: + if (sterr) + (void) close (sherr); + out2: + if (stout) + (void) close (shout); + out1: + if (stin) + (void) close (shin); + + out0: + if (rerrno) + errno = rerrno; + return (rc); +} + +void +run_print (fp) + FILE *fp; +{ + int i; + + for (i = 0; i < run_argc; i++) + { + (void) fprintf (fp, "'%s'", run_argv[i]); + if (i != run_argc - 1) + (void) fprintf (fp, " "); + } +} + +FILE * +Popen (cmd, mode) + const char *cmd; + const char *mode; +{ + if (trace) +#ifdef SERVER_SUPPORT + (void) fprintf (stderr, "%c-> Popen(%s,%s)\n", + (server_active) ? 'S' : ' ', cmd, mode); +#else + (void) fprintf (stderr, "-> Popen(%s,%s)\n", cmd, mode); +#endif + if (noexec) + return (NULL); + + return (popen (cmd, mode)); +} + +extern int evecvp PROTO((char *file, char **argv)); + +int +piped_child (command, tofdp, fromfdp) + char **command; + int *tofdp; + int *fromfdp; +{ + int pid; + int to_child_pipe[2]; + int from_child_pipe[2]; + + if (pipe (to_child_pipe) < 0) + error (1, errno, "cannot create pipe"); + if (pipe (from_child_pipe) < 0) + error (1, errno, "cannot create pipe"); + + pid = fork (); + if (pid < 0) + error (1, errno, "cannot fork"); + if (pid == 0) + { + if (dup2 (to_child_pipe[0], STDIN_FILENO) < 0) + error (1, errno, "cannot dup2"); + if (close (to_child_pipe[1]) < 0) + error (1, errno, "cannot close"); + if (close (from_child_pipe[0]) < 0) + error (1, errno, "cannot close"); + if (dup2 (from_child_pipe[1], STDOUT_FILENO) < 0) + error (1, errno, "cannot dup2"); + + execvp (command[0], command); + error (1, errno, "cannot exec"); + } + if (close (to_child_pipe[0]) < 0) + error (1, errno, "cannot close"); + if (close (from_child_pipe[1]) < 0) + error (1, errno, "cannot close"); + + *tofdp = to_child_pipe[1]; + *fromfdp = from_child_pipe[0]; + return pid; +} + + +void +close_on_exec (fd) + int fd; +{ +#if defined (FD_CLOEXEC) && defined (F_SETFD) + if (fcntl (fd, F_SETFD, 1)) + error (1, errno, "can't set close-on-exec flag on %d", fd); +#endif +} + +/* + * dir = 0 : main proc writes to new proc, which writes to oldfd + * dir = 1 : main proc reads from new proc, which reads from oldfd + */ + +int +filter_stream_through_program (oldfd, dir, prog, pidp) + int oldfd, dir; + char **prog; + pid_t *pidp; +{ + int p[2], newfd; + pid_t newpid; + + if (pipe (p)) + error (1, errno, "cannot create pipe"); + newpid = fork (); + if (pidp) + *pidp = newpid; + switch (newpid) + { + case -1: + error (1, errno, "cannot fork"); + case 0: + /* child */ + if (dir) + { + /* write to new pipe */ + close (p[0]); + dup2 (oldfd, 0); + dup2 (p[1], 1); + } + else + { + /* read from new pipe */ + close (p[1]); + dup2 (p[0], 0); + dup2 (oldfd, 1); + } + /* Should I be blocking some signals here? */ + execvp (prog[0], prog); + error (1, errno, "couldn't exec %s", prog[0]); + default: + /* parent */ + close (oldfd); + if (dir) + { + /* read from new pipe */ + close (p[1]); + newfd = p[0]; + } + else + { + /* write to new pipe */ + close (p[0]); + newfd = p[1]; + } + close_on_exec (newfd); + return newfd; + } +} diff --git a/gnu/usr.bin/cvs/lib/save-cwd.c b/gnu/usr.bin/cvs/lib/save-cwd.c new file mode 100644 index 0000000..1bdf791 --- /dev/null +++ b/gnu/usr.bin/cvs/lib/save-cwd.c @@ -0,0 +1,141 @@ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <stdio.h> + +#ifdef STDC_HEADERS +# include <stdlib.h> +#endif + +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif + +#ifdef HAVE_FCNTL_H +# include <fcntl.h> +#else +# include <sys/file.h> +#endif + +#ifdef HAVE_DIRECT_H +# include <direct.h> +#endif + +#ifdef HAVE_IO_H +# include <io.h> +#endif + +#include <errno.h> +# ifndef errno +extern int errno; +#endif + +#include "save-cwd.h" +#include "error.h" + +char *xgetwd __PROTO((void)); + +/* Record the location of the current working directory in CWD so that + the program may change to other directories and later use restore_cwd + to return to the recorded location. This function may allocate + space using malloc (via xgetwd) or leave a file descriptor open; + use free_cwd to perform the necessary free or close. Upon failure, + no memory is allocated, any locally opened file descriptors are + closed; return non-zero -- in that case, free_cwd need not be + called, but doing so is ok. Otherwise, return zero. */ + +int +save_cwd (cwd) + struct saved_cwd *cwd; +{ + static int have_working_fchdir = 1; + + cwd->desc = -1; + cwd->name = NULL; + + if (have_working_fchdir) + { +#ifdef HAVE_FCHDIR + cwd->desc = open (".", O_RDONLY); + if (cwd->desc < 0) + { + error (0, errno, "cannot open current directory"); + return 1; + } + +# if __sun__ || sun + /* On SunOS 4, fchdir returns EINVAL if accounting is enabled, + so we have to fall back to chdir. */ + if (fchdir (cwd->desc)) + { + if (errno == EINVAL) + { + close (cwd->desc); + cwd->desc = -1; + have_working_fchdir = 0; + } + else + { + error (0, errno, "current directory"); + close (cwd->desc); + cwd->desc = -1; + return 1; + } + } +# endif /* __sun__ || sun */ +#else +#define fchdir(x) (abort (), 0) + have_working_fchdir = 0; +#endif + } + + if (!have_working_fchdir) + { + cwd->name = xgetwd (); + if (cwd->name == NULL) + { + error (0, errno, "cannot get current directory"); + return 1; + } + } + return 0; +} + +/* Change to recorded location, CWD, in directory hierarchy. + If "saved working directory", NULL)) + */ + +int +restore_cwd (cwd, dest) + const struct saved_cwd *cwd; + const char *dest; +{ + int fail = 0; + if (cwd->desc >= 0) + { + if (fchdir (cwd->desc)) + { + error (0, errno, "cannot return to %s", + (dest ? dest : "saved working directory")); + fail = 1; + } + } + else if (chdir (cwd->name) < 0) + { + error (0, errno, "%s", cwd->name); + fail = 1; + } + return fail; +} + +void +free_cwd (cwd) + struct saved_cwd *cwd; +{ + if (cwd->desc >= 0) + close (cwd->desc); + if (cwd->name) + free (cwd->name); +} + diff --git a/gnu/usr.bin/cvs/lib/save-cwd.h b/gnu/usr.bin/cvs/lib/save-cwd.h new file mode 100644 index 0000000..f9802f8 --- /dev/null +++ b/gnu/usr.bin/cvs/lib/save-cwd.h @@ -0,0 +1,20 @@ +#ifndef SAVE_CWD_H +#define SAVE_CWD_H 1 + +struct saved_cwd + { + int desc; + char *name; + }; + +#if defined (__GNUC__) || (defined (__STDC__) && __STDC__) +#define __PROTO(args) args +#else +#define __PROTO(args) () +#endif /* GCC. */ + +int save_cwd __PROTO((struct saved_cwd *cwd)); +int restore_cwd __PROTO((const struct saved_cwd *cwd, const char *dest)); +void free_cwd __PROTO((struct saved_cwd *cwd)); + +#endif /* SAVE_CWD_H */ diff --git a/gnu/usr.bin/cvs/lib/xgetwd.c b/gnu/usr.bin/cvs/lib/xgetwd.c new file mode 100644 index 0000000..8fe4ec1 --- /dev/null +++ b/gnu/usr.bin/cvs/lib/xgetwd.c @@ -0,0 +1,79 @@ +/* xgetwd.c -- return current directory with unlimited length + Copyright (C) 1992 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* Derived from xgetcwd.c in e.g. the GNU sh-utils. */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "system.h" + +#include <stdio.h> +#include <errno.h> +#ifndef errno +extern int errno; +#endif +#include <sys/types.h> + +#ifndef HAVE_GETWD +char *getwd (); +#define GETWD(buf, max) getwd (buf) +#else +char *getcwd (); +#define GETWD(buf, max) getcwd (buf, max) +#endif + +/* Amount by which to increase buffer size when allocating more space. */ +#define PATH_INCR 32 + +char *xmalloc (); +char *xrealloc (); + +/* Return the current directory, newly allocated, arbitrarily long. + Return NULL and set errno on error. */ + +char * +xgetwd () +{ + char *cwd; + char *ret; + unsigned path_max; + + errno = 0; + path_max = (unsigned) PATH_MAX; + path_max += 2; /* The getcwd docs say to do this. */ + + cwd = xmalloc (path_max); + + errno = 0; + while ((ret = GETWD (cwd, path_max)) == NULL && errno == ERANGE) + { + path_max += PATH_INCR; + cwd = xrealloc (cwd, path_max); + errno = 0; + } + + if (ret == NULL) + { + int save_errno = errno; + free (cwd); + errno = save_errno; + return NULL; + } + return cwd; +} |