/* ** Copyright (c) 2007 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 version 2 as published by the Free Software Foundation. ** ** 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 cross link manifests */ #include "config.h" #include "manifest.h" #include #if INTERFACE /* ** A parsed manifest */ struct Manifest { Blob content; /* The original content blob */ char *zComment; /* Decoded comment */ double rDate; /* Time in the "D" line */ char *zUser; /* Name of the user */ char *zRepoCksum; /* MD5 checksum of the baseline content */ int nFile; /* Number of F lines */ int nFileAlloc; /* Slots allocated in aFile[] */ struct { char *zName; /* Name of a file */ char *zUuid; /* UUID of the file */ } *aFile; int nParent; /* Number of parents */ int nParentAlloc; /* Slots allocated in azParent[] */ char **azParent; /* UUIDs of parents */ }; #endif /* ** Clear the memory allocated in a manifest object */ void manifest_clear(Manifest *p){ blob_reset(&p->content); free(p->aFile); free(p->azParent); memset(p, 0, sizeof(*p)); } /* ** Parse a manifest blob into a Manifest object. The Manifest ** object takes over the input blob and will free it when the ** Manifest object is freed. Zeros are inserted into the blob ** as string terminators so that blob should not be used again. ** ** Return TRUE if the content really is a manifest. Return FALSE ** if there are syntax errors. ** ** The pContent is reset. If TRUE is returned, then pContent will ** be reset when the Manifest object is cleared. If FALSE is ** returned then the Manifest object is cleared automatically ** and pContent is reset before the return. */ int manifest_parse(Manifest *p, Blob *pContent){ int seenHeader = 0; int i; Blob line, token, a1, a2, a3; memset(p, 0, sizeof(*p)); memcpy(&p->content, pContent, sizeof(p->content)); blob_zero(pContent); pContent = &p->content; blob_zero(&a1); blob_zero(&a2); md5sum_init(); while( blob_line(pContent, &line) ){ char *z = blob_buffer(&line); if( z[0]=='-' ){ if( strncmp(z, "-----BEGIN PGP ", 15)!=0 ){ goto manifest_syntax_error; } if( seenHeader ){ break; } while( blob_line(pContent, &line)>1 ){} if( blob_line(pContent, &line)==0 ) break; z = blob_buffer(&line); } seenHeader = 1; if( blob_token(&line, &token)!=1 ) goto manifest_syntax_error; if( z[0]=='F' ){ char *zName, *zUuid; md5sum_step_text(blob_buffer(&line), blob_size(&line)+1); if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; if( blob_token(&line, &a2)==0 ) goto manifest_syntax_error; if( blob_token(&line, &a3)!=0 ) goto manifest_syntax_error; zName = blob_terminate(&a1); zUuid = blob_terminate(&a2); if( blob_size(&a2)!=UUID_SIZE ) goto manifest_syntax_error; if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error; defossilize(zName); if( !file_is_simple_pathname(zName) ){ goto manifest_syntax_error; } if( p->nFile>=p->nFileAlloc ){ p->nFileAlloc = p->nFileAlloc*2 + 10; p->aFile = realloc(p->aFile, p->nFileAlloc*sizeof(p->aFile[0]) ); if( p->aFile==0 ) fossil_panic("out of memory"); } i = p->nFile++; p->aFile[i].zName = zName; p->aFile[i].zUuid = zUuid; if( i>0 && strcmp(p->aFile[i-1].zName, zName)>=0 ){ goto manifest_syntax_error; } }else if( z[0]=='C' ){ md5sum_step_text(blob_buffer(&line), blob_size(&line)+1); if( p->zComment!=0 ) goto manifest_syntax_error; if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; p->zComment = blob_terminate(&a1); defossilize(p->zComment); }else if( z[0]=='D' ){ char *zDate; md5sum_step_text(blob_buffer(&line), blob_size(&line)+1); if( p->rDate!=0.0 ) goto manifest_syntax_error; if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; zDate = blob_terminate(&a1); p->rDate = db_double(0.0, "SELECT julianday(%Q)", zDate); }else if( z[0]=='U' ){ md5sum_step_text(blob_buffer(&line), blob_size(&line)+1); if( p->zUser!=0 ) goto manifest_syntax_error; if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; p->zUser = blob_terminate(&a1); defossilize(p->zUser); }else if( z[0]=='R' ){ md5sum_step_text(blob_buffer(&line), blob_size(&line)+1); if( p->zRepoCksum!=0 ) goto manifest_syntax_error; if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; if( blob_size(&a1)!=32 ) goto manifest_syntax_error; p->zRepoCksum = blob_terminate(&a1); if( !validate16(p->zRepoCksum, 32) ) goto manifest_syntax_error; }else if( z[0]=='P' ){ md5sum_step_text(blob_buffer(&line), blob_size(&line)+1); while( blob_token(&line, &a1) ){ char *zUuid; if( blob_size(&a1)!=UUID_SIZE ) goto manifest_syntax_error; zUuid = blob_terminate(&a1); if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error; if( p->nParent>=p->nParentAlloc ){ p->nParentAlloc = p->nParentAlloc*2 + 5; p->azParent = realloc(p->azParent, p->nParentAlloc*sizeof(char*)); if( p->azParent==0 ) fossil_panic("out of memory"); } i = p->nParent++; p->azParent[i] = zUuid; } }else if( z[0]=='Z' ){ int rc; Blob hash; if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; if( blob_size(&a1)!=32 ) goto manifest_syntax_error; if( !validate16(blob_buffer(&a1), 32) ) goto manifest_syntax_error; md5sum_finish(&hash); rc = blob_compare(&hash, &a1); blob_reset(&hash); if( rc!=0 ) goto manifest_syntax_error; }else{ goto manifest_syntax_error; } } if( !seenHeader ) goto manifest_syntax_error; md5sum_init(); return 1; manifest_syntax_error: md5sum_init(); manifest_clear(p); return 0; } /* ** Add a single entry to the mlink table. Also add the filename to ** the filename table if it is not there already. */ static void add_one_mlink( int mid, /* The record ID of the manifest */ const char *zFromUuid, /* UUID for the mlink.pid field */ const char *zToUuid, /* UUID for the mlink.fid field */ const char *zFilename /* Filename */ ){ int fnid, pid, fid; fnid = db_int(0, "SELECT fnid FROM filename WHERE name=%Q", zFilename); if( fnid==0 ){ db_multi_exec("INSERT INTO filename(name) VALUES(%Q)", zFilename); fnid = db_last_insert_rowid(); } if( zFromUuid==0 ){ pid = 0; }else{ pid = uuid_to_rid(zFromUuid, 1); } if( zToUuid==0 ){ fid = 0; }else{ fid = uuid_to_rid(zToUuid, 1); } db_multi_exec( "INSERT INTO mlink(mid,pid,fid,fnid)" "VALUES(%d,%d,%d,%d)", mid, pid, fid, fnid ); if( pid && fid ){ content_deltify(pid, fid, 0); } } /* ** Add mlink table entries associated with manifest cid. ** There is an mlink entry for every file that changed going ** from pid to cid. ** ** Deleted files have mlink.fid=0. ** Added files have mlink.pid=0. ** Edited files have both mlink.pid!=0 and mlink.fid!=0 */ static void add_mlink(int pid, Manifest *pParent, int cid, Manifest *pChild){ Manifest other; Blob otherContent; int i, j; if( db_exists("SELECT 1 FROM mlink WHERE mid=%d", cid) ){ return; } assert( pParent==0 || pChild==0 ); if( pParent==0 ){ pParent = &other; content_get(pid, &otherContent); }else{ pChild = &other; content_get(cid, &otherContent); } if( blob_size(&otherContent)==0 ) return; if( manifest_parse(&other, &otherContent)==0 ) return; content_deltify(pid, cid, 0); for(i=j=0; inFile && jnFile; ){ int c = strcmp(pParent->aFile[i].zName, pChild->aFile[j].zName); if( c<0 ){ add_one_mlink(cid, pParent->aFile[i].zUuid, 0, pParent->aFile[i].zName); i++; }else if( c>0 ){ add_one_mlink(cid, 0, pChild->aFile[j].zUuid, pChild->aFile[j].zName); j++; }else{ if( strcmp(pParent->aFile[i].zUuid, pChild->aFile[j].zUuid)!=0 ){ add_one_mlink(cid, pParent->aFile[i].zUuid, pChild->aFile[j].zUuid, pChild->aFile[j].zName); } i++; j++; } } while( inFile ){ add_one_mlink(cid, pParent->aFile[i].zUuid, 0, pParent->aFile[i].zName); i++; } while( jnFile ){ add_one_mlink(cid, 0, pChild->aFile[j].zUuid, pChild->aFile[j].zName); j++; } manifest_clear(&other); } /* ** Scan record rid/pContent to see if it is a manifest. If ** it is a manifest, then populate the mlink, plink, ** filename, and event tables with cross-reference information. */ int manifest_crosslink(int rid, Blob *pContent){ int i; Manifest m; Stmt q; if( manifest_parse(&m, pContent)==0 ){ return 0; } db_begin_transaction(); for(i=0; i