/* ** Copyright (c) 2011 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the Simplified BSD License (also ** known as the "2-Clause License" or "FreeBSD License".) ** 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. ** ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** ** This file contains code used to generate tarballs. */ #include #include #include "config.h" #include "tar.h" /* ** State information for the tarball builder. */ static struct tarball_t { unsigned char *aHdr; /* Space for building headers */ char *zSpaces; /* Spaces for padding */ char *zPrevDir; /* Name of directory for previous entry */ } tball; /* ** Begin the process of generating a tarball. ** ** Initialize the GZIP compressor and the table of directory names. */ static void tar_begin(void){ assert( tball.aHdr==0 ); tball.aHdr = fossil_malloc(512+512+256); memset(tball.aHdr, 0, 512+512+256); tball.zSpaces = (char*)&tball.aHdr[512]; tball.zPrevDir = (char*)&tball.zSpaces[512]; memcpy(&tball.aHdr[108], "0000000", 8); /* Owner ID */ memcpy(&tball.aHdr[116], "0000000", 8); /* Group ID */ memcpy(&tball.aHdr[257], "ustar ", 7); /* Format */ gzip_begin(); db_multi_exec( "CREATE TEMP TABLE dir(name UNIQUE);" ); } /* ** Build a header for a file or directory and write that header ** into the growing tarball. */ static void tar_add_header( const char *zName, /* Name of the object */ int nName, /* Number of characters in zName */ int iMode, /* Mode. 0644 or 0755 */ unsigned int mTime, /* File modification time */ int iSize, /* Size of the object in bytes */ int iType /* Type of object. 0==file. 5==directory */ ){ unsigned int cksum = 0; int i; if( nName>100 ){ memcpy(&tball.aHdr[345], zName, nName-100); memcpy(tball.aHdr, &zName[nName-100], 100); memset(&tball.aHdr[245+nName], 0, 267-nName); }else{ memcpy(tball.aHdr, zName, nName); memset(&tball.aHdr[nName], 0, 100-nName); memset(&tball.aHdr[345], 0, 167); } sqlite3_snprintf(8, (char*)&tball.aHdr[100], "%07o", iMode); sqlite3_snprintf(12, (char*)&tball.aHdr[124], "%011o", iSize); sqlite3_snprintf(12, (char*)&tball.aHdr[136], "%011o", mTime); memset(&tball.aHdr[148], ' ', 8); tball.aHdr[156] = iType + '0'; for(i=0; i<512; i++) cksum += tball.aHdr[i]; sqlite3_snprintf(7, (char*)&tball.aHdr[148], "%06o", cksum); tball.aHdr[154] = 0; gzip_step((char*)tball.aHdr, 512); } /* ** Recursively add an directory entry for the given file if those ** directories have not previously been seen. */ static void tar_add_directory_of( const char *zName, /* Name of directory including final "/" */ int nName, /* Characters in zName */ unsigned int mTime /* Modification time */ ){ int i; for(i=nName-1; i>0 && zName[i]!='/'; i--){} if( i<=0 ) return; if( tball.zPrevDir[i]==0 && memcmp(tball.zPrevDir, zName, i)==0 ) return; db_multi_exec("INSERT OR IGNORE INTO dir VALUES('%.*q')", i, zName); if( sqlite3_changes(g.db)==0 ) return; tar_add_directory_of(zName, i-1, mTime); tar_add_header(zName, i, 0755, mTime, 0, 5); memcpy(tball.zPrevDir, zName, i); tball.zPrevDir[i] = 0; } /* ** Add a single file to the growing tarball. */ static void tar_add_file( const char *zName, /* Name of the file. nul-terminated */ Blob *pContent, /* Content of the file */ int isExe, /* True for executable files */ unsigned int mTime /* Last modification time of the file */ ){ int nName = strlen(zName); int n = blob_size(pContent); int lastPage; if( nName>=250 ){ fossil_fatal("name too long for ustar format: \"%s\"", zName); } tar_add_directory_of(zName, nName, mTime); tar_add_header(zName, nName, isExe ? 0755 : 0644, mTime, n, 0); if( n ){ gzip_step(blob_buffer(pContent), n); lastPage = n % 512; if( lastPage!=0 ){ gzip_step(tball.zSpaces, 512 - lastPage); } } } /* ** Finish constructing the tarball. Put the content of the tarball ** in Blob pOut. */ static void tar_finish(Blob *pOut){ db_multi_exec("DROP TABLE dir"); gzip_step(tball.zSpaces, 512); gzip_step(tball.zSpaces, 512); gzip_finish(pOut); fossil_free(tball.aHdr); tball.aHdr = 0; } /* ** COMMAND: test-tarball ** ** Generate a GZIP-compresssed tarball in the file given by the first argument ** that contains files given in the second and subsequent arguments. */ void test_tarball_cmd(void){ int i; Blob zip; Blob file; if( g.argc<3 ){ usage("ARCHIVE FILE...."); } sqlite3_open(":memory:", &g.db); tar_begin(); for(i=3; irDate - 2440587.5)*86400.0; if( db_get_boolean("manifest", 0) ){ blob_append(&filename, "manifest", -1); zName = blob_str(&filename); tar_add_file(zName, &mfile, 0, mTime); sha1sum_blob(&mfile, &hash); blob_reset(&mfile); blob_append(&hash, "\n", 1); blob_resize(&filename, nPrefix); blob_append(&filename, "manifest.uuid", -1); zName = blob_str(&filename); tar_add_file(zName, &hash, 0, mTime); blob_reset(&hash); } manifest_file_rewind(pManifest); while( (pFile = manifest_file_next(pManifest,0))!=0 ){ int fid = uuid_to_rid(pFile->zUuid, 0); if( fid ){ content_get(fid, &file); blob_resize(&filename, nPrefix); blob_append(&filename, pFile->zName, -1); zName = blob_str(&filename); tar_add_file(zName, &file, manifest_file_mperm(pFile), mTime); blob_reset(&file); } } }else{ sha1sum_blob(&mfile, &hash); blob_append(&filename, blob_str(&hash), 16); zName = blob_str(&filename); mTime = db_int64(0, "SELECT (julianday('now') - 2440587.5)*86400.0;"); tar_add_file(zName, &mfile, 0, mTime); } manifest_destroy(pManifest); blob_reset(&mfile); blob_reset(&filename); tar_finish(pTar); } /* ** COMMAND: tarball ** ** Usage: %fossil tarball VERSION OUTPUTFILE [--name DIRECTORYNAME] ** ** Generate a compressed tarball for a specified version. If the --name ** option is used, its argument becomes the name of the top-level directory ** in the resulting tarball. If --name is omitted, the top-level directory ** named is derived from the project name, the check-in date and time, and ** the artifact ID of the check-in. */ void tarball_cmd(void){ int rid; Blob tarball; const char *zName; zName = find_option("name", 0, 1); db_find_and_open_repository(0, 0); if( g.argc!=4 ){ usage("VERSION OUTPUTFILE"); } rid = name_to_rid(g.argv[2]); if( zName==0 ){ zName = db_text("default-name", "SELECT replace(%Q,' ','_') " " || strftime('_%%Y-%%m-%%d_%%H%%M%%S_', event.mtime) " " || substr(blob.uuid, 1, 10)" " FROM event, blob" " WHERE event.objid=%d" " AND blob.rid=%d", db_get("project-name", "unnamed"), rid, rid ); } tarball_of_checkin(rid, &tarball, zName); blob_write_to_file(&tarball, g.argv[3]); blob_reset(&tarball); } /* ** WEBPAGE: tarball ** URL: /tarball/RID.tar.gz ** ** Generate a compressed tarball for a checkin. ** Return that tarball as the HTTP reply content. */ void tarball_page(void){ int rid; char *zName, *zRid; int nName, nRid; Blob tarball; login_check_credentials(); if( !g.okZip ){ login_needed(); return; } zName = mprintf("%s", PD("name","")); nName = strlen(zName); zRid = mprintf("%s", PD("uuid","")); nRid = strlen(zRid); if( nName>7 && strcmp(&zName[nName-7], ".tar.gz")==0 ){ /* Special case: Remove the ".tar.gz" suffix. */ nName -= 7; zName[nName] = 0; }else{ /* If the file suffix is not ".tar.gz" then just remove the ** suffix up to and including the last "." */ for(nName=strlen(zName)-1; nName>5; nName--){ if( zName[nName]=='.' ){ zName[nName] = 0; break; } } } rid = name_to_rid(nRid?zRid:zName); if( rid==0 ){ @ Not found return; } if( nRid==0 && nName>10 ) zName[10] = 0; tarball_of_checkin(rid, &tarball, zName); free( zName ); free( zRid ); cgi_set_content(&tarball); cgi_set_content_type("application/x-compressed"); }