Attachment "tar.c" to
ticket [831b932d]
added by
anonymous
2011-03-03 09:52:30.
/*
** 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 <assert.h>
#include <zlib.h>
#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");
}