CVS 存取管理


相信熟習編寫程式的朋友應該知道版本儲存的重要性, 或者你已經學會一個人使用 CVS, 但你又懂得如果設定多位程式編寫員共同分享一個 CVS 模組時的存取權限嗎? 我剛找到一篇精而簡的介紹, 分享一下。

Managing Access with CVS


CVS is the Concurrent Versioning System - the most popular free
source code manager around. Pretty much every open source or free
software developer has at some time or other crossed swords with
it. In addition, many companies use CVS as a cheaper alternative
to more costly SCMs such as Perforce or ClearCase to manage their
internal source code.

A source code manager allows you to "remember" old known-good
states of your source code, track the changes to your code over
time, and allow development by several developers on the same
source code at the same time. CVS provides basic versioning
functionality, including branching (allowing several versions of
the source code to be developer concurrently) and tagging
(remembering snapshots of the repository). It has a well-earned
reputation as a solid and reliable piece of software.

One of the key issues when dealing with source code management is
deciding who can get at it. In the open source and free software
world, we generally want as many people as possible to have
access to our software, but we don't want just anyone changing
our copy of it. Nor do we want to allow just anyone access to our
machine to do with it as they will.

On commercial projects, the group of people who are allowed access
to source code is usually as small as possible, even within the
company. This article addresses the rarely-addressed issue of the
mechanisms available for controlling access to source code stored
in CVS repositories.

In this article, we look at common project set-ups, and present a
scheme adequate to the needs of the project. The access strategy
that you use will depend on your situation. In many cases, the
most sophisticated scheme requires more administering than a lone
developer is required to spend, for example. The goal of this
article is to give an idea of the simplest scheme that you will
have to use to cater for your particular situation.

The Lone Developer

So you've just started on a pet project - your code compiles,
only just, and you'd like to start adding features and polishing
interfaces to the stage where you are happy enough to let other
people look at it. You're the only one that will have access to
the code, and you are doing all your development in the machine
housing the code.

First, set aside some space for your repository. You'll need r/w
access to the directory (somewhere in /home is fine). For the
purposes of the article, we'll assume your user-name is john, and
that your project sources are in $HOME/project.

john@bolsh:~$ mkdir cvs
john@bolsh:~$ ls -l
total 8
drwxr-xr-x    2 john     john         4096 Dec 26 16:38 cvs
drwxr-xr-x    2 john     john         4096 Dec 26 16:52 project

Then Let CVS know you want to store your sources in that
directory, initialise CVS, and import your sources. Et voilà -
you're ready to CVS away to your heart's content.

john@bolsh:~$ export CVSROOT=/home/john/cvs/
john@bolsh:~$ cvs init
john@bolsh:~$ ls cvs
john@bolsh:~$ cd project/
john@bolsh:~/project$ cvs import -m "Initialising sources in CVS" project john start
N project/main.c
N project/logic.h
N project/logic.c
N project/Makefile

No conflicts created by this import

john@bolsh:~/project$ cd ..
john@bolsh:~$ rm -rf project/
john@bolsh:~$ cvs co project
cvs checkout: Updating project
U project/Makefile
U project/logic.c
U project/logic.h
U project/main.c

Woohoo! Now, if you make changes to any files in your project,
cvs commit captures those changes forever. And you are all set to
hack away in security on your project. For the moment, we won't
worry too much about how CVS does what it does (later we will have
to think a little bit about it) - if you want to go exploring in
~/cvs/project or CVSROOT, feel free.

The project grows

The thing that you've always dreamed of has happened - your
project has become popular, and you're experiencing a bazaar
effect - you are being flooded with patches by mail and are
spending all your time committing them and not enough time
developing yourself. You decide that the time is right to start
trusting some other people to check in stuff to your project.
There are a few problems, though - they want to work in their own
development environment, not on your machine.

No problem! You can grant them access to your machine and CVS
knows any number of ways to connect. In all these cases, you'll
need to create an account on your machine for all the developers
you trust.

In any case, once we are dealing with multiple developers, we
need to know how file permissions issues affect a CVS repository.

Letting people in

If we have a look at the permissions in our repository, we'll
see that every ,v file has permissions 444 (read-only for
everyone), and all directories have permissions 776 (that is,
read-write for user & group, and readable by anyone else).
Those are reasonable permissions, but they imply that all your
CVS users belong to the same group as the repository.

To allow all trusted CVS users access to the repository, we will
create a cvsusers group. To do this, add


to /etc/group to create a cvsusers group, and add john and jack
to it. Note, 4901 is not special, it's any number not already in
use as a group id or user id - I used it because it's the default
port for a CVS pserver, which creates a nice link between the

Now, we should to change the group of all files in the repository
to cvsusers (we only need to change the group of the directories,
but we may as well do it for all files to be consistent).

john@bolsh:~$ find cvs/ -exec chgrp cvsusers {} \;

This still leaves us with a sticky problem. The easiest way to
see the problem is with an example. John has a friend Jack that
he's decided to trust to write to the repository. So Jack gets an
account on John's machine, logs in, sets CVSROOT to point at the
repository and checks out the project to work away on it. After
making some changes, he checks them in. Let's see what happens...

Before Jack checks anything in, the files in $CVSROOT/project
look like this...

john@bolsh:~/cvs/project$ ls -l
total 16
-r--r--r--    1 john     cvsusers      538 Dec 26 16:56 Makefile,v
-r--r--r--    1 jack     cvsusers      709 Dec 27 21:47 logic.c,v
-r--r--r--    1 john     cvsusers      606 Dec 28 16:55 logic.h,v
-r--r--r--    1 john     cvsusers      541 Dec 26 16:56 main.c,v

Now Jack makes a change, and checks it in...

jack@bolsh:~/project$ cvs update
cvs server: Updating .
M logic.h
jack@bolsh:~/project$ cvs commit -m "made a change"
cvs commit: Examining .
Checking in logic.h;
/home/john/cvs/project/logic.h,v  <--  logic.h
new revision: 1.3; previous revision: 1.2

And let's look at those permissions now...

john@bolsh:~/cvs/project$ ls -l
total 16
-r--r--r--    1 john     cvsusers      538 Dec 26 16:56 Makefile,v
-r--r--r--    1 jack     cvsusers      709 Dec 27 21:47 logic.c,v
-r--r--r--    1 jack     jack          737 Dec 28 17:23 logic.h,v
-r--r--r--    1 john     cvsusers      541 Dec 26 16:56 main.c,v

But we just changed the file to be john:cvsusers - so what's
going on? Well, CVS is running as jack, and jack's default group
is jack. So when jack commits his change, and cvs reconstructed
the ,v file, naturally the file's ownership gets changed too. In
this case, that's fine - every user can still read the file and
the directory has the same permissions. But what if Jack wants to
add some files to a new subdirectory?

jack@bolsh:~/project$ cvs add subdir
? subdir/test
Directory /home/john/cvs/project/subdir added to the repository
jack@bolsh:~/project$ cvs add subdir/test 
cvs server: scheduling file `subdir/test' for addition
cvs server: use 'cvs commit' to add this file permanently
jack@bolsh:~/project$ cvs commit -m "adding new file in subdir"
cvs commit: Examining .
cvs commit: Examining subdir
RCS file: /home/john/cvs/project/subdir/test,v
Checking in subdir/test;
/home/john/cvs/project/subdir/test,v  <--  test
initial revision: 1.1

And now in the repository, we have...

john@bolsh:~/cvs/project$ ls -l
total 20
-r--r--r--    1 john     cvsusers      538 Dec 26 16:56
-r--r--r--    1 jack     cvsusers      709 Dec 27 21:47 logic.c,v
-r--r--r--    1 jack     jack          737 Dec 28 17:23 logic.h,v
-r--r--r--    1 john     cvsusers      541 Dec 26 16:56 main.c,v
drwxrwxr-x    2 jack     jack         4096 Dec 28 17:29 subdir

As expected, subdir is owned by the user jack, and the group
jack. But what does this mean? To see, let's try and update
John's working copy of the repository now.

john@bolsh:~/project$ cvs update -d 
cvs update: Updating .
U logic.c
U logic.h
cvs update: Updating subdir
cvs update: failed to create lock directory for `/home/john/cvs/project/subdir'
(/home/john/cvs/project/subdir/#cvs.lock): Permission denied
cvs update: failed to obtain dir lock in repository `/home/john/cvs/project/subdir'
cvs [update aborted]: read lock failed - giving up

Gah! John no longer has even read-only access to the
subdirectory. We can fix that, but whatever happens he will
definitely not have r/w access.

We need to ensure that anyone writing to the repository does so
using the cvsusers group. The way we do this is by setting the
setgid bit on the parent directories. This means that when
someone "executes" the directory (by changing directory into it),
their group automatically becomes the same as the parent

By running the command
find $CVSROOT -type d -exec chmod g+s {} \;
we ensure that files get created as we want them. Let's rewind a
little, and (after running the command above) add that subdir and
file again.

john@bolsh:~/cvs/project$ ls -al
total 28
drwxrwsr-x    3 john     cvsusers     4096 Dec 28 17:39 .
drwxrwsr-x    5 john     cvsusers     4096 Dec 27 21:45 ..
-r--r--r--    1 john     cvsusers      538 Dec 26 16:56 Makefile,v
-r--r--r--    1 jack     cvsusers      709 Dec 27 21:47 logic.c,v
-r--r--r--    1 jack     cvsusers      737 Dec 28 17:23 logic.h,v
-r--r--r--    1 john     cvsusers      541 Dec 26 16:56 main.c,v
drwxrwsr-x    2 jack     cvsusers     4096 Dec 28 17:39 subdir

Note that the permissions on the repository root, on the project
directory (.. and . respectively), as well as on the newly
created directory are all g+s - a listing of subdir shows that
all files and directories are created with cvsusers as the group
owners too.

Lock files

Earlier when John couldn't even check out a read-only copy of the
repository, I said "we can fix that" - here's how. When a CVS
client is trying to check out or update a repository, it creates
lockfiles in the repository to let otehr CVS processes know that
it's currently reading. These lock files aren't blocking - unlike
when you are checking in a file - but CVS needs to create them
anyway for its internal data structures.

To create these lock files somewhere other than in the same
directory as the RCS files, uncomment the folowing line in


and set it to point to somewhere innocuous that all cvs users can
write to - I use $CVSROOT/.lock with permissions 777. Now even if
we don't have write access to the directory containing the ,v
files, we can check out the sources.

We're now all set for multiple users - in fact multiple local
users can already use the repository as it is. We're now going to
look at the various ways to connect to a CVS server from remote
machines - which is what makes CVS really useful, after all.

Connecting via rsh

CVS can connect over rsh to your machine, and then communicate
with a remote cvs process it starts when it gets there. One of
the problems is that to be able to do this, you need to disable
password authentication for the rsh connection method. Another is
that rsh needs to be enabled in /etc/inetd.conf. And a third is
that using this method all CVS users need a shell account on the
server, which you might not want to do.

With rsh, to disable password checking from a trusted remote
machine, we add a line to ~/.rhosts - for a remote user jack on
his machine, that means that jack has to add
the following line to his ~/.rhosts on john's machine:  jack

To enable the rsh (or sheel) service, we uncomment this line in
/etc/inetd.conf (happily, this is usually disabled by default,
being quite insecure)

shell stream tcp nowait root /usr/sbin/tcpd /usr/sbin/in.rshd

We also need to ensure that the shell service is listed in
/etc/services (although this isn't usually a concern) - it is on
port 514 by default.

Now when Jack runs "rsh -l jack 'which cvs'" he
will see the path to cvs on your machine, without being asked
for a password.

To take advantage of the method, we set the CVSROOT to let CVS
know how to connect to the server, make sure that the CVS
repository is r/w for Jack, and we're all set

jack@pitbull:~$ export CVSROOT=:ext:jack@bolsh:/home/john/cvs
jack@pitbull:~$ cvs co project
cvs server: Updating project
U project/Makefile
U project/logic.c
U project/logic.h
U project/main.c

There is one other big disadvantage with this method I didn't
mention - all network communications over rsh are unencrypted,
which means that any packet sniffer can get hold of your source
code, as well as monitor the authentication conversations that
the server and client have. While this might not be a major issue
with open source projects, the commercial take-up of CVS might be
slowed a little if there were no alternative.

Securing communications (connecting over ssh)

Rather than rsh, we can use ssh, which is a secure protocol,
and also supports compressed communication streams. To use ssh
rather than rsh for source transfer, we set the environment
variable CVS_RSH on the client side to ssh and on the server
side we still need to disable password authentication.

With ssh, we do that by creating a public/private key-pair on the
client, and on the server we add the public key to the list of
authorized client keys, which is stored in
~/.ssh/authorized_keys or ~/.ssh/authorized_keys2 (depending on
whether ssh is using protocol version 1 or 2).

jack@pitbull:~$ ssh-keygen -t dsa 
Generating public/private dsa key pair.
Enter file in which to save the key (/home/jack/.ssh/id_dsa): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/jack/.ssh/id_dsa.
Your public key has been saved in /home/jack/.ssh/
The key fingerprint is:
jack@pitbull:~$ scp .ssh/ jack@bolsh:~/.ssh/authorized_keys2
jack@pitbull:~$ export CVS_RSH=ssh
jack@pitbull:~$ echo $CVSROOT 
jack@pitbull:~$ cvs co project
Enter passphrase for key '/home/jack/.ssh/id_dsa':
cvs server: Updating project
U project/Makefile
U project/logic.c
U project/logic.h
U project/main.c

There are lots of good things about using ssh - there's
passphrase authentication for the client private key, and all
communications are encrypted. However, there is no requirement to
use a passphrase, and many people don't bother. So your machine
may be exposed if a client machine is exploited. And since
clients need a shell account on your machine, you are exposing
yourself a little to malicious attacks. If, however, you trust
everyone who you ever let at your source code, this is a good
scheme. This is the set-up that many companies use for CVS.

Using CVS as a client/server

Now your project has grown pretty big, and a nightmare scenario
happened - a black-hat cracker got onto a machine of one of your
trusted developers, and noticed that CVSROOT started with ext -
he logged straight onto your machine without even being asked for
a password, and has wreaked havoc.

You've learnt your lesson, and have decided that untrusted CVS
users shouldn't get shell accounts on your machine. You also
insist on some kind of password authentication on the server

CVS has it's own in-built client-server abilities, which offer
several advantages over rsh based methods, namely

  1. Password authentication.
  2. Server-side compression of streams.
  3. Users don't need shell access on the server.
  4. Remote users can map to local users for the duration of the connection.
  5. Remote users can have a CVS password different to the system password.

This means that you have some protection in the event of someone
cracking a client side computer. Nevertheless, read/write access to
a CVS repository allows you to execute more or less arbitrary
commands on the server anyway, so don't get too lulled into a
false sense of security.

Because of the little problem above, the CVS server process
doesn't run as root. Well, it does for a second, while it finds
out what local user your cvs username maps onto, and then it does
a setuid and setgid. This can be a source of security flaws if
there are problems with the cvs server. We also restrict r/w access
to the CVSROOT directory to be similar to those on /etc - since
administrative files housed there may be used maliciously.

To enable CVS as a server, we need to add the service to
/etc/services first (the default CVS port is 2401) and enable the
server in the inetd (or xinetd) superserver.

In /etc/services, add

cvspserver      2401/tcp

in /etc/inetd.conf, add

cvspserver stream tcp nowait root /usr/bin/cvs cvs -f --allow-root=/home/john/cvs pserver

In brief, this means that when a connection to the server arrives
on port 2401, it will be passed onto a cvs pserver process running
as root. The process needs to run as root to setuid and setgid of
the CVS user after connectng to the server.

Now if we set our CVSROOT on a remote machine to the following:

jack@pitbull:~$ export CVSROOT=:pserver:jack@bolsh:/home/john/cvs
jack@pitbull:~$ cvs login
Logging in to
CVS password: 
jack@pitbull:~$ cvs co project
cvs server: Updating project
U project/Makefile
U project/logic.c
U project/logic.h
U project/main.c

Cool! Our pserver is up and running. But there's a problem - CVS
stores your CVS passwords locally (by default in a file called
.cvspass in your home directory), and we're using system
authorisation to check passwords - that means that anyone can
fairly easily get at jack's password for the CVS server.

To avoid this problem, we will set up a password system to have
password authentication done internally by CVS. CVS users will
now have their system password, and their CVS password. The CVS
passwords are stored in CVSROOT/passwd under the CVS root node.

In $CVSROOT/CVSROOT, there are a number of administrative files -
in the repository we created above, the list is as follows...

john@bolsh:~/cvs$ ls CVSROOT/
Emptydir        config         editinfo,v  modules,v  rcsinfo,v
checkoutlist    config,v       history     notify     taginfo
checkoutlist,v  cvswrappers    loginfo     notify,v   taginfo,v
commitinfo      cvswrappers,v  loginfo,v   passwd     val-tags
commitinfo,v    editinfo       modules     rcsinfo    verifymsg

The ,v files are the RCS files which go to make up our versioning
information - this means that the administrative database is
versionned too and can be checked out just like any other source
by anyone with access to the repository.

We don't want people to be able to see the passwd
file that easily, so we are not going to add it to the repository
- it is just a flat text file with line in the format


If we leave an empty password, then any password will authorise
entry. If we leave out a local username that our CVS user will
map to, we need to have a user of that name created locally.

The following script, which is in the CVS book (published by
Coriolis, and partly available free online at,
permits you to generate passwords
for users. The passwords are encoded with the standard unix
crypt() function.

jack@pitbull:~$ cat /usr/local/bin/genpasswd

srand (time());
my $randletter = "(int (rand (26)) + (int (rand (1) + .5) % 2 ?  65 : 97))";
my $salt = sprintf ("%c%c", eval $randletter, eval $randletter);
my $plaintext = shift;
my $crypttext = crypt ($plaintext, $salt);

print "${crypttext}\n";

This is an extremely simple script - run it as follows...

jack@pitbull:~$ genpasswd hairylegs

Then mail the password to the repository admin, and ask him to
add the line


to the CVSROOT/passwd entry for you. Then next time you do
a cvs login, enter "hairylegs" and you're in.

By default, CVS will look in the system password file for a match
if it doesn't find one in CVSROOT/passwd - to stop it from doing
this, we check out the administrative database, and uncomment a
line in CVSROOT/config.

john@bolsh:~$ cvs co CVSROOT
cvs checkout: Updating CVSROOT
U CVSROOT/checkoutlist
U CVSROOT/commitinfo
U CVSROOT/config
U CVSROOT/cvswrappers
U CVSROOT/editinfo
U CVSROOT/loginfo
U CVSROOT/modules
U CVSROOT/notify
U CVSROOT/rcsinfo
U CVSROOT/taginfo
U CVSROOT/verifymsg

Now in CVSROOT/config, uncomment the line (or add it)

john@bolsh:~/CVSROOT$ cvs update
cvs update: Updating .
M config
john@bolsh:~/CVSROOT$ cvs commit -m "set SystemAuth=no to disable system password check"
cvs commit: Examining .
Checking in config;
/home/john/cvs/CVSROOT/config,v  <--  config
new revision: 1.2; previous revision: 1.1
cvs commit: Rebuilding administrative file database

Now we have the capacity to add users who *only* have cvs access
and can't otherwise log onto the machine at all. To add a CVS
only user, first add the user locally as normal, and set his
password in /etc/passwd (or /etc/shadow) to *. Then add an entry
with his CVS password to CVSROOT/passwd. He will be able to
connect to the CVS repository, but will never be able to log in.

Adding read-only users

This is really easy - the CVSROOT/readers file contains a list of
all CVS users with read-only permissions on the repository.

We create a local user called anonymous (and set the password to
* in /etc/passwd), and add


to CVSROOT/passwd to allow him to log on without a password, and
finally we add just


to the file CVSROOT/readers.

Often, rather than do this, bigger software projects maintain two
repositories - a read/write copy for development, and a read-only
copy which is a few hours behind for people who just want the
latest sources. This system relieves a lot of stress from the
server if there are lots of read-only users, but usually traffic
is low enough that there isn't really a need to do that.

It's worth explaining how the administrative files database
works. When we checked in the config file earlier, we saw the
line "cvs commit: Rebuilding administrative file database" at the
end. Since the files in CVSROOT need to be read by CVS processes
running on the server, the files themselves are created there,
alongside their ,v partners. We saw this earlier. But with newer
files which aren't created by default (such as the readers file
above), if we want to keep versioning information on the files,
we must add them to the repository with cvs add & cvs commit.

However, the files are not automatically generated from the ,v
files by default - we need to instruct CVS which files we would
like to have automatically constructed. The file in which we do
that is CVSROOT/checkoutlist (don't ask me, I didn't name it). We
can add our readers file to checkoutlist, cvs add it to the
repository, and it will be available to anyone who can check out

Module-level control

So you've become a regular industry - your machine is hosting
dozens of different projects, and they're not necessarily
related. Using as an example, you have people
working on galeon, and people working on the gimp. And they all
have permission to check in anything they want wherever they
want. You have been receiving a lot of complaints from module
owners about unauthorised check-ins, and they want you to do
something about it.

There are two ways you can address the idea of module-level
permissions in CVS. One is a little more risky, but requires
little or no administration. The other requires some work and
often demands more time from the CVS person that they're willing
to spend.

The pointy stick approach

Two weeks after getting CVS checkin permissions on her
favourite project, jill is browsing through the sources of
another project housed on the same server when she sees
something that just has to be a bug - feelng friendly, she
makes a change to "fix" the bug, commits the change and goes
back to her favourite project again.

Two days later all hell breaks loose - the other project is
hopelessly broken, and several development users have reported
data loss. It turns out that Jill in her eagerness removed an
essential cross-check and caused the bug. The module owner
sends Jill a big flame, and CCs you baying for blood.

Anyone can make a mistake once, so you send Jill a polite mail
telling her that for all modules other than the one she's
approved for, she should mail a patch to the developpers like
everyone else. She assures you it won't happen again.

A couple of months later, she's browsing again, and finds a
project which seems idle, and which doesn't conform to the
coding standards of the project. She runs all the sources
through indent, and checks them in.

The next day, the developer of the module reports to you that
he's just found out that he has a load of work resolving
conflicts for every single change he's made to the code in a
major re-write that he's spent a month working on. By doing
things like adding or removing witespace, Jill has caused all
of his source to conflict with the repository, so he has to
manually go through each source file resolving the conflicts.
What makes matters worse is that he is the only person who is
supposed to be checking any code into the module.

This time you send a much firmer mail to Jill. You tell her
that as this is the second time this has happened, she is now
getting her last chance. In addition, you send a mail to
everyone with commit access pointing out what Jill has done as
an example of behaviour which is unacceptable.

Jill gets a little bit annoyed at being picked on, but she
says she sees the light and won't do it again. She sends a
heartfelt apology to the module owner she's messed up, and all
is well again.

Six months later, when the heat has died down, Jill is again
potterring about when she again checks in code which breaks
stuff into a module that she isn't supposed to be writing to.
You hear about it from the module owner, and send her a polite
mail telling her that her commit priveleges have been
indefinitely suspended.

The "three strikes and your out" approach works very well -
usually people get the message after the first time that just
because they can check things in doesn't mean they should. But
sometimes it's not a feasible approach - if you are a
commercial developer, for example, it's not reasonable to cut
off your commit priveleges completely. The ideal would be for
us to have a way to control who can check in code on a
repository by repository basis.

To every module its group

We have seen earlier how creating a cvsusers group helped with
the coordination of the work of several developers. We can
extend this approach to permit directory level check-in

In our example, let's say that the module "cujo" is to be r/w
for jack and john, and the module "carrie" is r/w for john and
jill. We will create two groups, g_cujo and g_carrie, and add
the appropriate users to each - in /etc/group we add


Now in the repository (as root), run

   find $CVSROOT/cujo -exec chgrp g_cujo {} \;
   find $CVSROOT/carrie -exec chgrp g_carrie {} \;

ensuring, as before, that all directories have the gid bit

Now if we have a look in the repository...

   john@bolsh:~/cvs$ ls -l 
   total 16
   drwxrwsr-x    3 john     cvsadmin     4096 Dec 28 19:42 CVSROOT
   drwxrwsr-x    2 john     g_carrie     4096 Dec 28 19:35 carrie
   drwxrwsr-x    2 john     g_cujo       4096 Dec 28 19:40 cujo

and if Jack tries to commit a change to carrie...

   jack@bolsh:~/carrie$ cvs update
   cvs server: Updating .
   M test
   jack@bolsh:~/carrie$ cvs commit -m "test"
   cvs commit: Examining .
   Checking in test;
   /home/john/cvs/carrie/test,v  <--  test
   new revision: 1.2; previous revision: 1.1
   cvs [server aborted]: could not open lock file
   `/home/john/cvs/carrie/,test,': Permission denied

But in cujo, there is no problem.

   jack@bolsh:~/cujo$ cvs update
   cvs server: Updating .
   M test
   jack@bolsh:~/cujo$ cvs commit -m "Updating test"
   cvs commit: Examining .
   Checking in test;
   /home/john/cvs/cujo/test,v  <--  test
   new revision: 1.2; previous revision: 1.1

The procedure for adding a user is now a little more
complicated that it might be. To create a new CVS user, we
have to create a system user, add them to the groups
corresponding to the modules they may write to, and (if you're
using a pserver method) generate a password for them, and add
an entry to CVSROOT/passwd.

To add a project, we need to create a group, import the
sources, change the groups on all the files in the repository
and make sure the set gid on execution bit is set on all
directories inside the module, and add the relevant users to
the group.

There is undoubtedly more administration needed to do all this
than when we jab people with a pointy stick. In that method,
we never have to add a system user or a group or change the
groups on directories - all that is taken care of once we set
up the repository. This means that an unpriveleged user can be
the CVS admin without ever having root priveleges on the


In this article, we've presented a number of ways that we can use
CVS with the Unix permissions system to grant & restrict access
to source code in a repository. The factors we need to consider
when doing this are often in conflict - for example between ease
of administration and granularity of control, or ease of use and
server security.

I haven't covered all of the security methods available to a CVS
adminitrator - but these are certainly the most common. For more
information on CVS administration and use, the definitive guide
is the Cederqvist manual (info cvs or
) which is the
officially maintained documentation. There is also the most
excellent red-bean book, published by Coriolis, part of which is
available online (
- Open Source
Development with CVS by Karl Fogel) which presents many real-life
situations and helpful hints for repository administration and
CVS use.

Useful links

The official CVS manual ("Cederqvist")

Open Source Development with CVS

Chrooted tunnelled read-write CVS server

Copyright David Neary, 2002