 CVSADMIN backend documentation :

The point in designing this was : Never do it again.  I didn't wanted to 
recode the entire backend or to add alot of special-case code as I got a bunch
of "could you add this-little-thing"... So I designed something overly
flexible. Which makes it overly complicated. Oh well. This doc may help you
understand it, if you really want to.

First I will talk about the internal implementation (data structures, 
handling functions, blah blah blah), then I'll talk about the public API.
If you only want to write an application that uses it, and don't give a damn
about the internal stuff, you may safely skip it.

Note: I call repository information "database" in this doc and in the code,
      don't ask.

1 - Internal code information
-----------------------------

 Data structures :

   struct ca_db    -  This holds a database information
   struct ca_user  -  This holds information about a user
   struct ca_hfile -  This holds information and method pointers for a
                      handled file.

 Now, to play with these:

  Common functions :

	struct foo_object *foo_new(...);

	Creates a new foo_object.

        /******************************************************/

        int foo_free(foo_object);
 
        This frees foo_object's internal stuff.  See note in foo_init() about
        free-ing your objects.
 
         /******************************************************/

  CA_DB:
     int ca_db_try_hfile(struct ca_cb *, char *path, int (*readmethod)(),
                         int (*writemethod)());

        Try to handle a file.  Path is the file's full path, {read,write}methods
        are methods to read/write the handled file, if it exists. Returns 1 if
        we added it, 0 otherwise.

        /******************************************************/

     int ca_db_del_hfile(struct ca_db *, sturct ca_hfile *);
        
        Remove the hfile from the database

        /******************************************************/

     int ca_db_add_user(struct ca_db *, struct ca_user *);

        Add the user to the database.

        /******************************************************/

     int ca_db_del_user(struct ca_db *, struct ca_user *);

        Delete the user from the database.

        /******************************************************/

  CA_USER:
      Nothing.

  CA_HFILE:
      
     int ca_hfile_read(struct ca_hfile *);

        Call this file's read method.

        /******************************************************/

    int ca_hfile_write(struct ca_hfile *);

        Call this file's write method.

        /******************************************************/

2 - Public API
--------------

   This is what application writers need to know. You will never, ever, get
   one of the internal data structures.  This may make the code a little bit
   slower (i.e. searching for the username in the repository at each call,etc),
   but it has the advantage to give me total freedom in what I do to break the
   backend without breaking your application.

  API description:
  ---------------
      When you start your application, you must get a repository "id",
      something like a file descriptor, which you'll use in all other
      functions.  You call cvsadmin_open() to get it.  A id is an integer
      > 0, and cvsadmin_open() returns -1 on error.
      
      You then have a surprisingly small number of functions to modify users'
      information.

  API functions list:
  ------------------

      int cvsadmin_open(char *location, bool use_cvs);

         Try to open repository at location (if use_cvs==0, location must be
         a local repository (AKA: start with a '/' or a :local: prefix)

     int cvsadmin_close(int id);
        
         Close this repository. Writes out all information (if needed), then
         frees it.  After this, "id" is invalid.

     int cvsadmin_set_user_info(int id, char *uname, char *pass,
                                        char *sysuser, char *email);

          Sets user information.  If user "uname" doesn't already exists, it
          is created with the given information.  All NULL fields are
          ignored, so if you want to change "foo"'s email, you'd do something
          like:

  if (cvsadmin_set_user_info(repository, "foo", NULL, NULL, "new@email") < 0)
   { /* error handling */
   }
          
          This is the only way to change user information.

     int cvsadmin_rename_user(int id, char *oname, char *nname);
           
          Well, this renames user "oname" to "nname".  Fails if "nname" already
          exists or oname doesn't

     int cvsadmin_get_user_info(int id, char *uname, char **pass,
                                        char **sysuser, char **email);

          Gets information about user "uname".  All non-NULL char** are set
          to point to an internal string. Note this:  After you cvsadmin_close
          the repository, these strings are INVALID.  You must copy them if
          you want to keep them after that.
  
     int cvsadmin_del_user(int id, char *uname);
        
          Deletes user "uname" from the repository.

     char **cvsadmin_get_user_list(int id);
          
          Get a NULL-terminated array of usernames in this repository.  The
          list must be freed with cvsadmin_free_str_list().

     void cvsadmin_free_str_list(char **list);
        
          Free a cvsadmin-generated string list.  Currently only 
          cvsadmin_get_user_list() returns one.

     void cvsadmin_sync(int id);
          Sync the repository to disk.
 
     void cvsadmin_cleanup();
         
          Close all openned repositories.  This calls cvsadmin_close(), so
          you're warned, data you got from cvsadmin_*() might be invalid
          after that.

       
   Example program:
   ---------------
         The best example program is probably cvsadmin itself, but if you
         want something simpler, here's a email changer:

/******************** chemail.c **********************************/
#include "backend.h"

#include <stdio.h>
#include <stdlib.h>

int repository;

int main(int argc, char **argv)
{
 int i;
 char *uname,*email;
 char *loc;

 /* 
  * Get the repository's location from the default $CVSROOT variable.
  * You could read this from the command-line or hardcode it (not
  * recommended)
  */
 loc = getenv("CVSROOT");
 if (!loc)
  { fprintf(stderr, "No CVSROOT set!!\n");
    exit(1);
  }
 /*
  * Now, see if we can open it
  */
 repository = cvsadmin_open(loc,0);
 if (repository < 0)
  { fprintf(stderr, "cvsadmin_open failed\n");
    exit(1);
  }
 /*
  * The command-line arguments are composed of a pair of
  *   user email_address
  */
 for (i = 1; i < argc - 1; i += 2)
  { uname=argv[i];
    email=argv[i+1];
    if (cvsadmin_set_user_info(repository,uname,NULL,NULL,email) < 0)
     { fprintf(stderr, "set_user_info problem :(\n");
     }
  }
 /*
  * We're done, throw up resources and write out new users information.
  */
 cvsadmin_close(repository);

 return 0;
}
/******************************************************************/

  You'd compile it like :
   % gcc -Wall chemail.c backend.o util.o xmalloc.o -o chemail

  The backend.o, util.o and xmalloc.o are needed for the backend to link
  together.  This kinda sucks.


3 - End
-------

  This is the end.  If you have any problem or see weird behavior of 
  cvsadmin or just want to comment on it, feel free to contact me at
  limitln@cooptel.qc.ca.
