/*
** Copyright (c) 2002 D. Richard Hipp
**
** 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 library; if not, write to the
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA 02111-1307, USA.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains code used to call svnlook to get information
** about repository's history and update the CHNG and FILECHNG
** tables according to the content of it's output. All the other
** Subversion-specific stuff should also be found here.
*/
#include "config.h"
#include <time.h>
#include <sys/times.h>
#include <sys/stat.h>
#include <errno.h>
#include "svn.h"
/*
** Instance of following structure holds information about revision
*/
typedef struct Revision Revision;
struct Revision {
char zAuthor[100]; /* Author name */
int nDate; /* Revision date */
int nMsgLength; /* Number of characters in log message */
char *zMessage; /* Log message */
};
/*
** This func is supposed to deal with "svn copy" and update FILECHNG and
** FILE for each file in copied directory.
*/
static void svn_insert_copied_files(
int cn,
int nRev,
const char *zNewDir,
const char *zOldDir,
int skipInsertFile
){
int i;
size_t nLen;
char **azTree;
char *zTmp;
/* We need paths without leading '/' here since that is what we
** already have in db.
*/
while( zNewDir[0]=='/' ) zNewDir++;
while( zOldDir[0]=='/' ) zOldDir++;
zTmp = mprintf("%s/", zOldDir);
if( zTmp==0 ) return;
nLen = strlen(zTmp)-1;
/* TODO: make sure zOldDir always ends with / if it's not empty */
/* This query should return only files that are not deleted.
*/
azTree = db_query(
"SELECT t1.filename, t1.chngtype FROM filechng t1 "
"INNER JOIN (SELECT filename, max(vers) AS maxvers FROM filechng "
" WHERE filename LIKE '%q%%' GROUP BY filename) t2 ON "
"t1.filename=t2.filename AND t1.vers=t2.maxvers AND t1.chngtype<>2",
zTmp
);
free(zTmp);
if( azTree==0 ) return;
for(i=0; azTree[i]; i+=2){
char *zFile = mprintf( "%s%s", zNewDir, &(azTree[i])[nLen]);
int nFLen = strlen(zFile);
if( nFLen && zFile[nFLen-1]!='/' ){
db_execute(
"REPLACE INTO filechng(cn,filename,vers,prevvers,chngtype) "
"VALUES(%d,'%q',%d,'%s',1);",
cn, zFile, nRev, ( azTree[i+1][0]=='0' ) ? "" : azTree[i+1]
);
}
if( !skipInsertFile ) insert_file(zFile, cn);
free(zFile);
}
db_query_free(azTree);
}
/*
** This func is supposed to deal with "svn delete" on direcories and update
** FILECHNG for each file and dir in deleted directory.
*/
static void svn_delete_dir(
int cn,
int nRev,
const char *zDir,
int skipInsertFile
){
int i;
char **azTree;
/* We need paths without leading '/' here since that is what we
** already have in db.
*/
while( zDir[0]=='/' ) zDir++;
azTree = db_query(
"SELECT t1.filename, t1.vers FROM filechng t1 "
"INNER JOIN (SELECT filename, max(vers) AS maxvers FROM filechng "
" WHERE filename LIKE '%q%%' GROUP BY filename) t2 ON "
"t1.filename=t2.filename AND t1.vers=t2.maxvers AND t1.chngtype<>2",
zDir
);
if( azTree==0 ) return;
for(i=0; azTree[i]; i+=2){
int nFLen = strlen(azTree[i]);
if( nFLen && azTree[i][nFLen-1]!='/' ){
db_execute(
"REPLACE INTO filechng(cn,filename,vers,prevvers,chngtype) "
"VALUES(%d,'%q',%d,'%s',2);",
cn, azTree[i], nRev, ( azTree[i+1][0]=='0' ) ? "" : azTree[i+1]
);
}
if( !skipInsertFile ) insert_file(azTree[i], cn);
}
db_query_free(azTree);
}
/*
** A little hackish way to determine if we even need to call "svnlook youngest".
*/
static int svn_did_repository_change( const char * zRoot, time_t since ){
struct stat statbuf;
char *zFilename = mprintf("%s/db/revisions",zRoot); /* bdb */
if( stat(zFilename, &statbuf) ){
free(zFilename);
zFilename = mprintf("%s/db/current",zRoot); /* fsfs */
if( stat(zFilename, &statbuf) ){
free(zFilename);
return 1; /* don't know, say yes */
}
}
free(zFilename);
/* Should run every hour, just in case there was some timing problems... */
return statbuf.st_mtime > since || (time(NULL)-since)>3600;
}
/*
** Process recent activity in the Subversion repository.
**
** If any errors occur, output an error page and exit.
**
** If the "isReread" flag is set, it means the history file should be
** reread to pick up changes that we may have missed earlier.
*/
static int svn_history_update(int isReread){
const char *zRoot;
char **azResult;
FILE *in;
int nRev; /* Current revison number in loop */
int cnum = 0; /* check-in number to use for this checkin */
int next_cnum; /* next unused check-in number */
int nErr = 0;
char *zCmd;
char zLine[2000];
int nBaseRevision = 0; /* Revision we last seen, and have stored in db */
int nHeadRevision = 0; /* Latest revision in repository */
int nLine;
int isInitialScan; /* If set delay updating FILE till after the last revision */
Revision *pRev;
db_execute("BEGIN");
/* Get the path to local repository and last revision number we have in db
* If there's no repository defined, bail and wait until the admin sets one.
*/
zRoot = db_config("cvsroot","");
if( zRoot[0]==0 ) return 1;
nBaseRevision = atoi(db_config("historysize","0"));
if( nBaseRevision
&& !svn_did_repository_change(zRoot,atoi(db_config("svnlastupdate","0"))) ){
db_execute("COMMIT");
return 1;
}
isInitialScan = (nBaseRevision==0);
/* Get the number of latest revision in repository
*/
zCmd = mprintf("svnlook youngest '%s' 2>/dev/null", quotable_string(zRoot));
in = popen(zCmd, "r");
if( in==0 ){
error_init(&nErr);
@ <li><p>Unable to execute the following command:
@ <blockquote><pre>
@ %h(zCmd)
@ </pre></blockquote></p></li>
error_finish(nErr);
free(zCmd);
return -1;
}
free(zCmd);
if( fgets(zLine, sizeof(zLine), in) ){
nHeadRevision = atoi(zLine);
}
pclose(in);
/* This could mean that repository is empty, no revisions in it yet.
** But this could also mean that atoi() failed. And that would be bad since
** it means svnlook's output format changed.
*/
if( nHeadRevision==0 ){
error_init(&nErr);
@ <li><p>Repository '%h(zRoot)' appears empty. Latest revision
@ seems to be '%h(zLine)'</p></li>
error_finish(nErr);
return -1;
}
/* See if there are some new revisions in repository
*/
if( nHeadRevision==nBaseRevision ) {
/* No changes to the repository since our last scan
** Exit without doing anything
*/
db_execute("UPDATE config SET value=%d WHERE name='svnlastupdate';", time(NULL)+1);
db_execute("COMMIT");
return 0;
}
/* Find the next available change number
*/
azResult = db_query("SELECT max(cn) FROM chng");
next_cnum = atoi(azResult[0])+1;
db_query_free(azResult);
pRev = (Revision *) malloc( sizeof(*pRev) );
/*
** Parse output of svnlook changes and svnlook info to get the info we need
** We do this for each revision we miss from db
*/
for(nRev=nBaseRevision+1; nRev<=nHeadRevision; nRev++){
int nAddChng = 1;
int isMsgEnd = 0;
/* Example output of "svnlook info" is as follows:
**
** chorlya <-Author
** 2005-08-02 01:51:45 +0200 (Tue, 02 Aug 2005) <-Date
** 26 <- # of chars in message
** Changes to some make files <- multiline message
** <- followed by blank line
*/
zCmd = mprintf("svnlook info -r %d '%s' 2>/dev/null",
nRev, quotable_string(zRoot));
in = popen(zCmd, "r");
if( in==0 ){
error_init(&nErr);
@ <li><p>Unable to execute the following command:
@ <blockquote><pre>
@ %h(zCmd)
@ </pre></blockquote></p></li>
error_finish(nErr);
free(zCmd);
return -1;
}
free(zCmd);
nLine = 0;
while( fgets(zLine,sizeof(zLine),in) ){
if( nLine==0 ){
remove_newline(zLine);
bprintf(pRev->zAuthor, sizeof(pRev->zAuthor), "%.90s", zLine);
}else if( nLine==1 ){
struct tm sTm;
/* Since timestamp is localtime, we need to subtract timezone offset
** to get timestamp in UTC
*/
int nOffstHr, nOffstMn; /* Timezone offset hour and minute */
zLine[25] = 0;
if( sscanf(zLine,"%d-%d-%d %d:%d:%d %3d%2d",
&sTm.tm_year, &sTm.tm_mon, &sTm.tm_mday,
&sTm.tm_hour, &sTm.tm_min, &sTm.tm_sec,
&nOffstHr, &nOffstMn)==8 ){
sTm.tm_year -= 1900;
sTm.tm_mon--;
/* We subtract our timezone offset from tm.tm_sec since tm_sec
** is just added to rest of the timestamp without any calculations
** being performed on it. Because of that tm_sec can be negative!
*/
sTm.tm_sec -= (nOffstHr>0) ? (nOffstHr*60 + nOffstMn)*60
: (nOffstHr*60 - nOffstMn)*60 ;
pRev->nDate = mkgmtime(&sTm);
}
}else if( nLine==2 ){
pRev->nMsgLength = atoi(zLine);
if( pRev->nMsgLength==0 ){
isMsgEnd = 1;
pRev->zMessage = "";
break; /* No comment here */
}else{
/* Allocate storage space for comment.
*/
pRev->zMessage = (char *) malloc(pRev->nMsgLength+16);
if( pRev->zMessage==NULL ){
break; /* malloc() failed */
}
}
}else{
/* Before we begin concatenating lines of comment make sure we start
** with empty string.
*/
if( nLine==3 ){
pRev->zMessage[0] = 0;
}
/* Concat comment lines into one string */
strcat(pRev->zMessage, zLine);
}
if( isMsgEnd ) break;
nLine++;
}
pclose(in);
if( isReread ){
char *zPrior = db_short_query(
"SELECT cn FROM chng WHERE date=%d AND user='%q'",
pRev->nDate, pRev->zAuthor
);
if( zPrior==0 || (cnum = atoi(zPrior))<=0 ){
cnum = next_cnum++;
}else{
nAddChng = 0;
}
if(zPrior) free(zPrior);
}else{
cnum = next_cnum++;
}
/* We assume there can't ever be an empty commit, so we just INSERT
** TODO: check information read from "svnlook info" for assumed pattern.
** If it deviates from it too much, abort.
*/
if( nAddChng ){
if( pRev->zMessage==0 ) pRev->zMessage = "";
db_execute(
"INSERT INTO chng(cn, date, branch, milestone, user, message) "
"VALUES(%d,%d,'',0,'%q','%q');",
cnum, pRev->nDate, pRev->zAuthor, pRev->zMessage
);
xref_checkin_comment(cnum, pRev->zMessage);
}
/* Now that we've got the common revision info, that is same for every
** file in this revision, we need to get the list of files that changed
** in this revision and populate filechng table with it.
**
** Example output of "svnlook changed" is as follows:
** A trunk/vendors/deli/
** A trunk/vendors/deli/chips.txt
** A trunk/vendors/deli/sandwich.txt
** A trunk/vendors/deli/pickle.txt
**
** First column is svn update-style status letter.
** Then there are 3 columns that are of no intrest to us,
** and then comes the file path.
*/
zCmd = mprintf("svnlook changed -r %d '%s' 2>/dev/null",
nRev, quotable_string(zRoot));
in = popen(zCmd, "r");
if( in==0 ){
error_init(&nErr);
@ <li><p>Unable to execute the following command:
@ <blockquote><pre>
@ %h(zCmd)
@ </pre></blockquote></p></li>
error_finish(nErr);
free(zCmd);
return -1;
}
free(zCmd);
while( fgets(zLine,sizeof(zLine),in) && zLine[0]!='\n' && zLine[0]!='\r' ){
int nChngType = 0;
const char *zFilename = &zLine[4];
char *zPrevVersion;
size_t nPos, nLen = strlen(zLine);
if( nLen<4 ) continue;
/*
** First char indicates what happend to file/dir in this revision.
** Following table shows the meaning of some expected values for first
** char and how we map that to filechng field in database:
**
** Char Meaning chngtype
** =========================================
** U File updated (modified) 0
** A File added to repository 1
** D File deleted (removed) 2
**
** First strip CR and LF chars from right end of filename
*/
remove_newline(&zLine[4]);
if( zLine[0]=='U' ){
nChngType = 0;
}else if( zLine[0]=='D' ){
nChngType = 2;
/* If dir was deleted we need to delete all files and dirs in it.
*/
nPos = strlen(zFilename)-1;
if( zFilename[nPos]=='/' ){
svn_delete_dir(cnum, nRev, zFilename, isInitialScan);
}
}else if( zLine[0]=='A' ){
/* If this is the first revision there can't be any copies in
** repository yet.
*/
if( nRev>1 ){
/* TODO: check to make sure dir is actually a copy before calling
** svn_insert_copied_files() since this func is pretty slow.
*/
nPos = strlen(zFilename)-1;
if( zFilename[nPos]=='/' ){
FILE *history;
char zHistLine[2000];
char *zCurrPath = NULL;
int i=0, nHistErr=0;
zCmd = mprintf("svnlook history -r %d '%s' '%s' 2>/dev/null", nRev,
quotable_string(zRoot), quotable_string(zFilename));
history = popen(zCmd, "r");
if( history==0 ){
error_init(&nErr);
@ <li><p>Unable to execute the following command:
@ <blockquote><pre>
@ %h(zCmd)
@ </pre></blockquote></p></li>
error_finish(nErr);
free(zCmd);
return -1;
}
free(zCmd);
while( fgets(zHistLine,sizeof(zHistLine),history) && i<4 && !nHistErr ){
remove_newline(zHistLine);
switch( i ){
case 0:
if( strncmp(zHistLine, "REVISION PATH", 15)!=0 ) nHistErr++;
break;
case 1:
if( strncmp(zHistLine, "-------- ----", 15)!=0 ) nHistErr++;
break;
case 2:
/* We need to store this just so we can compare it
** with next line.
*/
zCurrPath = mprintf("%s", &zHistLine[11]);
break;
case 3:
/* If paths changed, this dir was copied.
*/
if( strcmp(zCurrPath, &zHistLine[11])!=0 ){
svn_insert_copied_files(cnum, nRev, zCurrPath,
&zHistLine[11], isInitialScan);
}
break;
}
i++;
}
pclose(history);
if( zCurrPath ) free(zCurrPath);
}
}
nChngType = 1;
}else{
nChngType = -1; /* TODO: do something smart(tm) here :D */
continue;
}
zPrevVersion = db_short_query(
"SELECT vers FROM filechng WHERE filename='%q' AND cn<%d "
"ORDER BY cn DESC;",
zFilename, cnum
);
db_execute(
"REPLACE INTO filechng(cn,filename,vers,prevvers,chngtype) "
"VALUES(%d,'%q',%d,'%q',%d);",
cnum, zFilename, nRev, zPrevVersion ? zPrevVersion : "", nChngType
);
if(zPrevVersion) free(zPrevVersion);
if( !isInitialScan ) insert_file(zFilename, cnum);
}
pclose(in);
}
free(pRev);
/* We delayed populating FILE till now on initial scan */
if( isInitialScan ){
update_file_table_with_lastcn();
}
/* Update the "historysize" entry so that we know last revision number that
** we have in db, and "svnlastupdate" to keep those calls to svnlook youngest
** to minimum.
*/
db_execute("UPDATE config SET value=%d WHERE name='historysize';", nHeadRevision);
db_execute("UPDATE config SET value=%d WHERE name='svnlastupdate';", time(NULL)+1);
db_config(0,0);
/* Commit all changes to the database
*/
db_execute("COMMIT;");
error_finish(nErr);
return nErr ? -1 : 0;
}
/*
** Diff two versions of a file, handling all exceptions.
**
** If oldVersion is NULL or "0", then this function will output the
** text of version newVersion of the file instead of doing a diff.
*/
static int svn_diff_versions(
const char *oldVersion,
const char *newVersion,
const char *zFile
){
const char *zTemplate;
char *zCmd;
FILE *in;
int i;
const char *azSubst[16];
char zLine[2][2000];
int nBuf, inFile;
char *z;
long long nLine;
int file_cat; /* set when we want "svnlook cat" instead of "svnlook diff" */
if( zFile==0 ) return -1;
/* If this is the first time file is checked into repository (Added),
** don't diff it just display it's contents
*/
z = mprintf("%s", newVersion);
previous_version(z, zFile);
if( z==0 || z[0]==0 )
file_cat = 1; /* svnlook cat */
else
file_cat = 0; /* svnlook diff */
free(z);
/* Find the command used to compute the file difference.
*/
azSubst[0] = "F";
azSubst[1] = zFile;
azSubst[2] = "V1";
azSubst[3] = oldVersion;
azSubst[4] = "V2";
azSubst[5] = newVersion;
azSubst[6] = "RP";
azSubst[7] = db_config("cvsroot", "");
azSubst[8] = "V";
azSubst[9] = newVersion;
azSubst[10] = 0;
if( file_cat==1 ){
zTemplate = db_config("filelist","svnlook cat -r '%V' '%RP' '%F' 2>/dev/null");
}else{
zTemplate = db_config("filediff","svnlook diff -r '%V2' '%RP' 2>/dev/null");
}
zCmd = subst(zTemplate, azSubst);
in = popen(zCmd, "r");
free(zCmd);
if( in==0 ) return -1;
if( file_cat==0 ){
/* We're looking for something like this:
**
** Modified: trunk/vendors/deli/sandwich.txt
** ==============================================================================
**
** So first we look for a delimiter line since it should be more uniqe
** then one before it. When we find it, we go one line back and check
** if that is the file we're after. That line can begin with any element
** of zMarker.
*/
const char zMarker[3][11] = { "Modified: ", "Added: ", "Deleted: " };
int nMarkerLen[3] = { 10, 7, 9 };
nBuf = nLine = inFile = 0;
@ <pre>
while( fgets(zLine[nBuf], sizeof(zLine[nBuf]), in) ){
if( strncmp(zLine[nBuf], "===================================================================", 67)==0
&& (zLine[nBuf][67]=='\n' || zLine[nBuf][67]=='\r')
){
if( inFile ){
/* This is the begging of some other file, and end of ours */
break;
} else {
/* If previous line begins with one of our markers,
** it could be what we're looking for
*/
for(i=0; i<sizeof(nMarkerLen)/sizeof(nMarkerLen[0]); i++)
if( strncmp(zLine[(nBuf+1)%2], zMarker[i], nMarkerLen[i])==0 )
break;
if( i<sizeof(nMarkerLen)/sizeof(nMarkerLen[0]) ){
/* We found one of the markers on previous line! Now we need to
** check if our filename is present, which would mean we found
** our file and can strat sending it to browser
*/
int iLen = strlen(zFile);
if( strncmp(&zLine[(nBuf+1)%2][nMarkerLen[i]], zFile, iLen)==0
&& (zLine[(nBuf+1)%2][nMarkerLen[i]+iLen]=='\n'
|| zLine[(nBuf+1)%2][nMarkerLen[i]+iLen]=='\r')
){
inFile = 1;
}
}
}
}
if( inFile ){
/* We can't print current line yet since it may be end of our
** file in diff output, so we print previous line here
*/
cgi_printf("%h", zLine[(nBuf+1)%2]);
}
nBuf = ++nLine % 2;
}
@ </pre>
} else {
output_pipe_as_html(in,1);
pclose(in);
}
return 0;
}
static int svn_dump_version(const char *zVersion, const char *zFile,int bRaw){
char *zCmd;
int rc = -1;
if( !zVersion || !zVersion[0] ){
zCmd = mprintf("svnlook cat '%s' '%s' 2>/dev/null",
db_config("cvsroot", ""), quotable_string(zFile) );
}else{
zCmd = mprintf("svnlook cat -r '%s' '%s' '%s' 2>/dev/null",
quotable_string(zVersion), db_config("cvsroot", ""), quotable_string(zFile) );
}
rc = common_dumpfile( zCmd, zVersion, zFile, bRaw );
free(zCmd);
return rc;
}
static int svn_diff_chng(int cn, int bRaw){
const char *zRoot;
char *zRev;
char *zCmd;
char zLine[2000];
FILE *in;
zRev = db_short_query("SELECT vers FROM filechng WHERE cn=%d", cn);
if( !zRev || !zRev[0] ) return -1; /* Invalid check-in number */
zRoot = db_config("cvsroot", "");
zCmd = mprintf("svnlook diff -r '%s' '%s' 2>/dev/null",
quotable_string(zRev), quotable_string(zRoot));
free(zRev);
in = popen(zCmd, "r");
free(zCmd);
if( in==0 ) return -1;
if( bRaw ){
while( !feof(in) ){
int amt = fread(zLine, 1, sizeof(zLine), in);
if( amt<=0 ) break;
cgi_append_content(zLine, amt);
}
}else{
output_pipe_as_html(in,1);
}
pclose(in);
return 0;
}
/*
** svntrac can import users only from svnserver's users file. If you use some
** other authentication method you'll have to enter users manually or import
** them in some other way.
*/
/*
** Load the names of all users listed in users file into a temporary
** table "tuser". First locate the users file and figureout what permisions
** have authenticated users.
*/
static void svn_read_users_file(const char *zSvnRoot){
FILE *f;
int i, bInSection=0;
char *zFile, *zKey, *zValue;
char zLine[2000];
char *zAnonAccess, *zAuthAccess;
/* We set these to Subversion defaults in case they are not defined
** explicitly in .conf file.
*/
zAnonAccess = "o"; /* TODO: use this to set anon user's perms */
zAuthAccess = "io";
db_execute(
"CREATE TEMP TABLE tuser(id UNIQUE ON CONFLICT IGNORE,pswd,sysid,cap);"
);
if( zSvnRoot==0 ){
zSvnRoot = db_config("cvsroot","");
}
zFile = mprintf("%s/conf/svnserve.conf", zSvnRoot);
f = fopen(zFile, "r");
free(zFile);
/*
** This is how Subversion .conf file should look like:
**
** # This is commnet
** [general] # Section name
** anon-access = read # What can anon users do
** auth-access = write # What can auth users do
** password-db = passwd # Absolute or relative location of users file
** realm = My First Repository # This is of no intrest to us
** [some_other_section]
*/
if( f ){
while( fgets(zLine, sizeof(zLine), f) ){
remove_newline(zLine);
for(i=0; zLine[i] && isspace(zLine[i]); i++){}
if( zLine[i]==0 || zLine[i]=='#' ) continue;
if( zLine[i]=='[' ){
/* If we are already in section, then this marks the end of our section.
** If not, this might be the start of our section.
*/
if( bInSection ){
bInSection = 0;
break;
} else if( strncmp(&zLine[i], "[general]", 9)==0 ){
bInSection = 1;
continue;
}
}
/* If we're not in our section there is nothing to look for in this line.
** TODO: I made an assumption here that all keys for key-value pairs in
** .conf files have to start with alnum char. Check if this really
** is the case.
*/
if( !bInSection || !isalnum(zLine[i]) ) continue;
/* Extract key-value pair */
zKey = &zLine[i];
for(; zLine[i] && !isspace(zLine[i]); i++){}
zLine[i++] = 0;
for(; zLine[i] && isspace(zLine[i]); i++){}
if( zLine[i++]!='=' ) continue; /* Not valid key-value pair */
for(; zLine[i] && isspace(zLine[i]); i++){}
zValue = &zLine[i];
for(; zLine[i] && !isspace(zLine[i]); i++){}
zLine[i] = 0; /* Make sure we rtrim our value */
/* We handel each key differently */
if( strcmp(zKey, "anon-access")==0 ){
if( strcmp(zValue, "none")==0 ){ zAnonAccess=""; continue; }
if( strcmp(zValue, "read")==0 ){ zAnonAccess="o"; continue; }
if( strcmp(zValue, "write")==0 ){ zAnonAccess="io"; continue; }
}
if( strcmp(zKey, "auth-access")==0 ){
if( strcmp(zValue, "write")==0 ){ zAuthAccess="io"; continue; }
if( strcmp(zValue, "read")==0 ){ zAuthAccess="o"; continue; }
if( strcmp(zValue, "none")==0 ){ zAuthAccess=""; continue; }
}
if( strcmp(zKey, "password-db")==0 ){
if( zValue[0]=='/' ){
/* We've got absolute path, no problem here */
zFile = mprintf("%s", zValue);
continue;
} else {
/* We've got relative path */
zFile = mprintf("%s/conf/%s", zSvnRoot, zValue);
continue;
}
}
}
fclose(f);
}
/* If we didn't find path to users file, exit */
if( !zFile || !zFile[0] ) return;
f = fopen(zFile, "r");
free(zFile);
/* Read username->password pairs from file and store them in db
** Passwords are stored in clear text.
*/
if( f ){
bInSection = 0;
while( fgets(zLine, sizeof(zLine), f) ){
char zBuf[3];
char zSeed[100];
const char *z;
remove_newline(zLine);
for(i=0; zLine[i] && isspace(zLine[i]); i++){}
if( zLine[i]==0 || zLine[i]=='#' ) continue;
if( zLine[i]=='[' ){
/* If we are already in section, then this marks the end of our section.
** If not, this might be the start of our section.
*/
if( bInSection ){
bInSection = 0;
break;
} else if( strncmp(&zLine[i], "[users]", 7)==0 ){
bInSection = 1;
continue;
}
}
/* If we're not in our section there is nothing to look for in this line.
** TODO: I made an assumption here that all usernames have to start
** with alnum char. Check if this really is the case.
*/
if( !bInSection || !isalnum(zLine[i]) ) continue;
/* Each line here is key-value pair, or in our case uname-passwd pair */
zKey = &zLine[i];
for(; zLine[i] && !isspace(zLine[i]); i++){}
zLine[i++] = 0;
for(; zLine[i] && isspace(zLine[i]); i++){}
if( zLine[i++]!='=' ) continue; /* Not valid key-value pair */
for(; zLine[i] && isspace(zLine[i]); i++){}
zValue = &zLine[i];
for(; zLine[i] && !isspace(zLine[i]); i++){}
zLine[i] = 0; /* Make sure we rtrim our value */
/* We need to encrypt passwords */
bprintf(zSeed,sizeof(zSeed),"%d%.20s",getpid(),zKey);
z = crypt(zSeed, "aa");
zBuf[0] = z[2];
zBuf[1] = z[3];
zBuf[2] = 0;
db_execute("INSERT INTO tuser VALUES('%q','%q','','%s');",
zKey, crypt(zValue, zBuf), zAuthAccess);
}
fclose(f);
}
}
/*
** Read svnserve users file and record infomation gleaned from that file
** in the local database.
*/
static int svn_user_read(void){
db_add_functions();
db_execute("BEGIN");
svn_read_users_file(0);
db_execute(
"REPLACE INTO user(id,name,email,passwd,capabilities) "
" SELECT n.id, o.name, o.email, n.pswd, "
" cap_or(cap_and(o.capabilities, 'aknrsw'), n.cap) "
" FROM tuser as n, user as o WHERE n.id=o.id;"
"INSERT OR IGNORE INTO user(id,name,email,passwd,capabilities) "
" SELECT id, id, '', pswd,"
" cap_or((SELECT capabilities FROM user WHERE id='anonymous'),cap) "
" FROM tuser;"
"COMMIT;"
);
return 0;
}
void init_svn(){
g.scm.zSCM = "svn";
g.scm.zName = "Subversion";
g.scm.pxHistoryUpdate = svn_history_update;
g.scm.pxDiffVersions = svn_diff_versions;
g.scm.pxDiffChng = svn_diff_chng;
g.scm.pxIsFileAvailable = 0; /* use the database */
g.scm.pxDumpVersion = svn_dump_version;
g.scm.pxUserRead = svn_user_read;
}
syntax highlighted by Code2HTML, v. 0.9.1