/* ** Copyright (c) 2011 Roy S. Keene ** ** 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: ** fossil@rkeene.org ** http://www.rkeene.org/ ** ******************************************************************************* ** ** This file contains code used to generate USTAR gzip'd archives. */ #include #include #include "config.h" #include "tar.h" /* Convert a Fossil date to a UNIX date */ static unsigned long get_unix_time_from_fossil(double rDate) { unsigned long unixTime; unixTime = (rDate - 2440587.5) * 86400.0; return(unixTime); } /* ** Initialize a new tar.gz archive. */ void tgz_open(Blob *pTar) { blob_zero(pTar); return; } void tgz_close(Blob *pTar) { unsigned char gzip_header[10], gzip_trailer[8]; unsigned long tar_length, tar_checksum; char tar_header[512]; Blob temp_blob; /* Terminate with 2 blank blocks */ memset(tar_header, '\0', sizeof(tar_header)); blob_append(pTar, tar_header, sizeof(tar_header)); blob_append(pTar, tar_header, sizeof(tar_header)); /* Store CRC32 and length of uncompressed data for later use */ tar_length = blob_size(pTar); tar_checksum = crc32(0, NULL, 0); tar_checksum = crc32(tar_checksum, (unsigned char *) blob_buffer(pTar), blob_size(pTar)); /* Compress data */ blob_compress(pTar, pTar); /* GZIP Header from From RFC 1952 */ /** ID1 and ID2 **/ gzip_header[0] = 0x1F; gzip_header[1] = 0x8B; /** Compression Method (CM) **/ gzip_header[2] = 8; /* Deflate */ /** Flags (FLG) **/ gzip_header[3] = 0; /** Modification Time (MTIME) **/ gzip_header[4] = 0; gzip_header[5] = 0; gzip_header[6] = 0; gzip_header[7] = 0; /** Extra Flags, type of compression used (XFL) **/ gzip_header[8] = 0; /* Default */ /** Operating System (OS) **/ gzip_header[9] = 255; /** Uncompressed data CRC32 (CRC32) **/ gzip_trailer[3] = (tar_checksum >> 24) & 0xff; gzip_trailer[2] = (tar_checksum >> 16) & 0xff; gzip_trailer[1] = (tar_checksum >> 8) & 0xff; gzip_trailer[0] = (tar_checksum >> 0) & 0xff; /** Uncompressed size (ISIZE) **/ gzip_trailer[7] = (tar_length >> 24) & 0xff; gzip_trailer[6] = (tar_length >> 16) & 0xff; gzip_trailer[5] = (tar_length >> 8) & 0xff; gzip_trailer[4] = (tar_length >> 0) & 0xff; /* Combine header, data, and trailer into temporary blob */ blob_zero(&temp_blob); /** Append gzip header to buffer **/ blob_append(&temp_blob, (char *) gzip_header, sizeof(gzip_header)); /** Append data to buffer **/ if (blob_size(pTar) >= 10) { blob_append(&temp_blob, blob_buffer(pTar) + 6, blob_size(pTar) - 10); } /** Append trailer to buffer **/ blob_append(&temp_blob, (char *) gzip_trailer, sizeof(gzip_trailer)); /* Copy temporary blob to output blob */ blob_copy(pTar, &temp_blob); /* Release temporary blob */ blob_zero(&temp_blob); return; } /* ** Append a single file to a growing tar.gz archive. ** ** pFile is the file to be appended. zName is the name ** that the file should be saved as. */ void tgz_add_file(Blob *pTar, const char *zName, const Blob *pFile, unsigned long file_mtime, int executable) { char tar_header[512], tar_padding[512]; char *local_zName = NULL, *dirname, *filename; unsigned long tar_checksum; unsigned int tar_mode, tar_type; int tar_padding_bytes; int i; if (strlen(zName) < 100) { filename = (char *) zName; dirname = ""; } else { local_zName = strdup(zName); /* * Split the filename into 2 parts, the directory name and * the file name. USTAR allows us to fit longer file names * if we do this. */ filename = strrchr(local_zName, '/'); if (filename) { *filename = '\0'; filename++; dirname = local_zName; } else { filename = local_zName; dirname = ""; } } /* Verify that the file attributes can fit into a USTAR header */ if (strlen(filename) >= 100) { if (local_zName) { free(local_zName); } return; } if (strlen(dirname) >= 155) { if (local_zName) { free(local_zName); } return; } if (blob_size(pFile) >= 0x200000000LLU) { if (local_zName) { free(local_zName); } return; } /* Determine file attributes */ /** Type **/ tar_type = 0; /* Regular file */ /** Permissions **/ if (executable) { tar_mode = 0755; } else { tar_mode = 0644; } /* Create USTAR header */ /** Clear header **/ memset(tar_header, '\0', sizeof(tar_header)); /** File name **/ memcpy(tar_header, filename, strlen(filename)); /** Attributes **/ sprintf(tar_header + 100, "%07o", tar_mode); /** User ID (Numeric) **/ sprintf(tar_header + 108, "%07o", 0); /** Group ID (Numeric) **/ sprintf(tar_header + 116, "%07o", 0); /** File Size **/ sprintf(tar_header + 124, "%011o", (unsigned int) blob_size(pFile)); /** Modification Time **/ sprintf(tar_header + 136, "%011lo", file_mtime); /** Checksum dummy value (will be replaced later) **/ sprintf(tar_header + 148, " "); /** File Type **/ sprintf(tar_header + 156, "%1o", (unsigned int) tar_type); /** US-TAR Header Marker **/ sprintf(tar_header + 257, "ustar"); /** ??? **/ sprintf(tar_header + 263, " "); /** User ID (Text) **/ sprintf(tar_header + 265, "%s", "root"); /** Group ID (Text) **/ sprintf(tar_header + 297, "%s", "root"); /** Directory Name **/ sprintf(tar_header + 345, "%s", dirname); /* Compute header checksum */ tar_checksum = 0; for (i = 0; i < sizeof(tar_header); i++) { tar_checksum += (unsigned char) tar_header[i]; } /** Checksum **/ sprintf(tar_header + 148, "%06lo", tar_checksum); /* Clean up */ if (local_zName) { free(local_zName); } /** Append header to buffer **/ blob_append(pTar, tar_header, sizeof(tar_header)); /* Append data */ /** Append contents **/ blob_append(pTar, blob_buffer(pFile), blob_size(pFile)); /** Append padding to align to 512 byte blocks **/ if ((blob_size(pFile) % sizeof(tar_padding)) != 0) { memset(tar_padding, '\0', sizeof(tar_padding)); tar_padding_bytes = sizeof(tar_padding) - (blob_size(pFile) % sizeof(tar_padding)); blob_append(pTar, tar_padding, tar_padding_bytes); } return; } /* ** COMMAND: test-filetgz ** ** Generate a tar.gz archive specified by the first argument that ** contains files given in the second and subsequent arguments. */ void filetgz_cmd(void) { int i; Blob targz; Blob file; if (g.argc < 3) { usage("ARCHIVE FILE...."); } tgz_open(&targz); for (i = 3; i < g.argc; i++) { blob_zero(&file); blob_read_from_file(&file, g.argv[i]); tgz_add_file(&targz, g.argv[i], &file, 0, 0); blob_reset(&file); } tgz_close(&targz); blob_write_to_file(&targz, g.argv[2]); } /* ** Given the RID for a manifest, construct a tar.gz archive containing ** all files in the corresponding baseline. ** ** If RID is for an object that is not a real manifest, then the ** resulting tar.gz archive contains a single file which is the RID ** object. ** ** If the RID object does not exist in the repository, then ** pTar is zeroed. ** ** zDir is a "synthetic" subdirectory which all targzped files get ** added to as part of the targz file. It may be 0 or an empty string, ** in which case it is ignored. The intention is to create a targz which ** politely expands into a subdir instead of filling your current dir ** with source files. For example, pass a UUID or "ProjectName". ** */ void tgz_of_baseline(int rid, Blob *pTar, const char *zDir) { Blob mfile, hash, file; Manifest *pManifest; ManifestFile *pFile; Blob filename; unsigned long nFileTimeStamp; int bExecutable; int nPrefix; content_get(rid, &mfile); if (blob_size(&mfile) == 0) { blob_zero(pTar); return; } blob_zero(&hash); blob_zero(&filename); tgz_open(pTar); if (zDir && zDir[0]) { blob_appendf(&filename, "%s/", zDir); } nPrefix = blob_size(&filename); pManifest = manifest_get(rid, CFTYPE_MANIFEST); if (pManifest) { char *zName; nFileTimeStamp = get_unix_time_from_fossil(pManifest->rDate); if (db_get_boolean("manifest", 0)) { blob_append(&filename, "manifest", -1); zName = blob_str(&filename); tgz_add_file(pTar, zName, &mfile, nFileTimeStamp, 0); 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); tgz_add_file(pTar, zName, &hash, nFileTimeStamp, 0); blob_reset(&hash); } manifest_file_rewind(pManifest); while ((pFile = manifest_file_next(pManifest,0)) !=0) { int fid; fid = uuid_to_rid(pFile->zUuid, 0); if (fid) { if (pFile->zPerm && strchr(pFile->zPerm, 'x')) { bExecutable = 1; } else { bExecutable = 0; } content_get(fid, &file); blob_resize(&filename, nPrefix); blob_append(&filename, pFile->zName, -1); zName = blob_str(&filename); tgz_add_file(pTar, zName, &file, nFileTimeStamp, bExecutable); blob_reset(&file); } } } else { blob_reset(&mfile); } manifest_destroy(pManifest); blob_reset(&filename); tgz_close(pTar); } /* ** COMMAND: tgz ** ** Usage: %fossil tgz VERSION OUTPUTFILE [--name DIRECTORYNAME] ** ** Generate a tar.gz archive for a specified version. If the --name option is ** used, it argument becomes the name of the top-level directory in the ** resulting tar.gz archive. 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 baseline_tgz_cmd(void) { int rid; Blob targz; 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 ); } tgz_of_baseline(rid, &targz, zName); blob_write_to_file(&targz, g.argv[3]); } /* ** WEBPAGE: tgz ** URL: /tgz/RID.tgz ** ** Generate a tar.gz archive for the baseline. ** Return that tar.gz archive as the HTTP reply content. */ void baseline_tgz_page(void) { int rid; char *zName, *zRid; int nName, nRid; Blob targz; 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); 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; } tgz_of_baseline(rid, &targz, zName); free(zName); free(zRid); cgi_set_content(&targz); cgi_set_content_type("application/x-compressed"); }