Attachment Details
Not logged in
Overview

Artifact ID: f1828309e0536ce6ece3abc097f5286aaadae039
Ticket: 831b932db4d1714780ad67d1e3abe353fffc8023
Date: 2011-03-03 09:52:30
User: anonymous
Artifact Attached: 313a4d25774dc462a75db492103e863e59826f3c
Filename:tar.c
Description:Simple "tar.c", based on "zip.c" to add a "tgz" command.
Content Appended
     1  /*
     2  ** Copyright (c) 2011 Roy S. Keene
     3  **
     4  ** This program is free software; you can redistribute it and/or
     5  ** modify it under the terms of the Simplified BSD License (also
     6  ** known as the "2-Clause License" or "FreeBSD License".)
     7  
     8  ** This program is distributed in the hope that it will be useful,
     9  ** but without any warranty; without even the implied warranty of
    10  ** merchantability or fitness for a particular purpose.
    11  **
    12  ** Author contact information:
    13  **   fossil@rkeene.org
    14  **   http://www.rkeene.org/
    15  **
    16  *******************************************************************************
    17  **
    18  ** This file contains code used to generate USTAR gzip'd archives.
    19  */
    20  #include <assert.h>
    21  #include <zlib.h>
    22  #include "config.h"
    23  #include "tar.h"
    24  
    25  /* Convert a Fossil date to a UNIX date */
    26  static unsigned long get_unix_time_from_fossil(double rDate) {
    27  	unsigned long unixTime;
    28  
    29  	unixTime = (rDate - 2440587.5) * 86400.0;
    30  
    31  	return(unixTime);
    32  }
    33  
    34  
    35  /*
    36   ** Initialize a new tar.gz archive.
    37   */
    38  void tgz_open(Blob *pTar) {
    39  	blob_zero(pTar);
    40  
    41  	return;
    42  }
    43  
    44  void tgz_close(Blob *pTar) {
    45  	unsigned char gzip_header[10], gzip_trailer[8];
    46  	unsigned long tar_length, tar_checksum;
    47  	char tar_header[512];
    48  	Blob temp_blob;
    49  
    50  	/* Terminate with 2 blank blocks */
    51  	memset(tar_header, '\0', sizeof(tar_header));
    52  	blob_append(pTar, tar_header, sizeof(tar_header));
    53  	blob_append(pTar, tar_header, sizeof(tar_header));
    54  
    55  	/* Store CRC32 and length of uncompressed data for later use */
    56  	tar_length = blob_size(pTar);
    57  	tar_checksum = crc32(0, NULL, 0);
    58  	tar_checksum = crc32(tar_checksum, (unsigned char *) blob_buffer(pTar), blob_size(pTar));
    59  
    60  	/* Compress data */
    61  	blob_compress(pTar, pTar);
    62  
    63  	/* GZIP Header from From RFC 1952 */
    64  	/** ID1 and ID2 **/
    65  	gzip_header[0] = 0x1F;
    66  	gzip_header[1] = 0x8B;
    67  
    68  	/** Compression Method (CM) **/
    69  	gzip_header[2] = 8; /* Deflate */
    70  
    71  	/** Flags (FLG) **/
    72  	gzip_header[3] = 0;
    73  
    74  	/** Modification Time (MTIME) **/
    75  	gzip_header[4] = 0;
    76  	gzip_header[5] = 0;
    77  	gzip_header[6] = 0;
    78  	gzip_header[7] = 0;
    79  
    80  	/** Extra Flags, type of compression used (XFL) **/
    81  	gzip_header[8] = 0; /* Default */
    82  
    83  	/** Operating System (OS) **/
    84  	gzip_header[9] = 255;
    85  
    86  	/** Uncompressed data CRC32 (CRC32) **/
    87  	gzip_trailer[3] = (tar_checksum >> 24) & 0xff;
    88  	gzip_trailer[2] = (tar_checksum >> 16) & 0xff;
    89  	gzip_trailer[1] = (tar_checksum >>  8) & 0xff;
    90  	gzip_trailer[0] = (tar_checksum >>  0) & 0xff;
    91  
    92  	/** Uncompressed size (ISIZE) **/
    93  	gzip_trailer[7] = (tar_length >> 24) & 0xff;
    94  	gzip_trailer[6] = (tar_length >> 16) & 0xff;
    95  	gzip_trailer[5] = (tar_length >>  8) & 0xff;
    96  	gzip_trailer[4] = (tar_length >>  0) & 0xff;
    97  
    98  	/* Combine header, data, and trailer into temporary blob */
    99  	blob_zero(&temp_blob);
   100  
   101  	/** Append gzip header to buffer **/
   102  	blob_append(&temp_blob, (char *) gzip_header, sizeof(gzip_header));
   103  
   104  	/** Append data to buffer **/
   105  	if (blob_size(pTar) >= 10) {
   106  		blob_append(&temp_blob, blob_buffer(pTar) + 6, blob_size(pTar) - 10);
   107  	}
   108  
   109  	/** Append trailer to buffer **/
   110  	blob_append(&temp_blob, (char *) gzip_trailer, sizeof(gzip_trailer));
   111  
   112  	/* Copy temporary blob to output blob */
   113  	blob_copy(pTar, &temp_blob);
   114  
   115  	/* Release temporary blob */
   116  	blob_zero(&temp_blob);
   117  
   118  	return;
   119  }
   120  
   121  /*
   122  ** Append a single file to a growing tar.gz archive.
   123  **
   124  ** pFile is the file to be appended.  zName is the name
   125  ** that the file should be saved as.
   126  */
   127  void tgz_add_file(Blob *pTar, const char *zName, const Blob *pFile, unsigned long file_mtime, int executable) {
   128  	char tar_header[512], tar_padding[512];
   129  	char *local_zName = NULL, *dirname, *filename;
   130  	unsigned long tar_checksum;
   131  	unsigned int tar_mode, tar_type;
   132  	int tar_padding_bytes;
   133  	int i;
   134  
   135  	if (strlen(zName) < 100) {
   136  		filename = (char *) zName;
   137  		dirname = "";
   138  	} else {
   139  		local_zName = strdup(zName);
   140  
   141  		/*
   142  		 * Split the filename into 2 parts, the directory name and
   143  		 * the file name.  USTAR allows us to fit longer file names
   144  		 * if we do this.
   145  		 */
   146  		filename = strrchr(local_zName, '/');
   147  		if (filename) {
   148  			*filename = '\0';
   149  			filename++;
   150  
   151  			dirname = local_zName;
   152  		} else {
   153  			filename = local_zName;
   154  			dirname = "";
   155  		}
   156  	}
   157  
   158  	/* Verify that the file attributes can fit into a USTAR header */
   159  	if (strlen(filename) >= 100) {
   160  		if (local_zName) {
   161  			free(local_zName);
   162  		}
   163  
   164  		return;
   165  	}
   166  
   167  	if (strlen(dirname) >= 155) {
   168  		if (local_zName) {
   169  			free(local_zName);
   170  		}
   171  
   172  		return;
   173  	}
   174  
   175  	if (blob_size(pFile) >= 0x200000000LLU) {
   176  		if (local_zName) {
   177  			free(local_zName);
   178  		}
   179  
   180  		return;
   181  	}
   182  
   183  	/* Determine file attributes */
   184  	/** Type **/
   185  	tar_type = 0; /* Regular file */
   186  
   187  	/** Permissions **/
   188  	if (executable) {
   189  		tar_mode = 0755;
   190  	} else {
   191  		tar_mode = 0644;
   192  	}
   193  
   194  	/* Create USTAR header */
   195  	/** Clear header **/
   196  	memset(tar_header, '\0', sizeof(tar_header));
   197  	/** File name **/
   198  	memcpy(tar_header, filename, strlen(filename));
   199  
   200  	/** Attributes **/
   201  	sprintf(tar_header + 100, "%07o", tar_mode);
   202  
   203  	/** User ID (Numeric) **/
   204  	sprintf(tar_header + 108, "%07o", 0);
   205  
   206  	/** Group ID (Numeric) **/
   207  	sprintf(tar_header + 116, "%07o", 0);
   208  
   209  	/** File Size **/
   210  	sprintf(tar_header + 124, "%011o", (unsigned int) blob_size(pFile));
   211  
   212  	/** Modification Time **/
   213  	sprintf(tar_header + 136, "%011lo", file_mtime);
   214  
   215  	/** Checksum dummy value (will be replaced later) **/
   216  	sprintf(tar_header + 148, "        ");
   217  
   218  	/** File Type **/
   219  	sprintf(tar_header + 156, "%1o", (unsigned int) tar_type);
   220  
   221  	/** US-TAR Header Marker **/
   222  	sprintf(tar_header + 257, "ustar");
   223  
   224  	/** ??? **/
   225  	sprintf(tar_header + 263, "  ");
   226  
   227  	/** User ID (Text) **/
   228  	sprintf(tar_header + 265, "%s", "root");
   229  
   230  	/** Group ID (Text) **/
   231  	sprintf(tar_header + 297, "%s", "root");
   232  
   233  	/** Directory Name **/
   234  	sprintf(tar_header + 345, "%s", dirname);
   235  
   236  	/* Compute header checksum */
   237  	tar_checksum = 0;
   238  	for (i = 0; i < sizeof(tar_header); i++) {
   239  		tar_checksum += (unsigned char) tar_header[i];
   240  	}
   241  
   242  	/** Checksum **/
   243  	sprintf(tar_header + 148, "%06lo", tar_checksum);
   244  
   245  	/* Clean up */
   246  	if (local_zName) {
   247  		free(local_zName);
   248  	}
   249  
   250  	/** Append header to buffer **/
   251  	blob_append(pTar, tar_header, sizeof(tar_header));
   252  
   253  	/* Append data */
   254  	/** Append contents **/
   255  	blob_append(pTar, blob_buffer(pFile), blob_size(pFile));
   256  
   257  	/** Append padding to align to 512 byte blocks **/
   258  	if ((blob_size(pFile) % sizeof(tar_padding)) != 0) {
   259  		memset(tar_padding, '\0', sizeof(tar_padding));
   260  
   261  		tar_padding_bytes = sizeof(tar_padding) - (blob_size(pFile) % sizeof(tar_padding));
   262  
   263  		blob_append(pTar, tar_padding, tar_padding_bytes);
   264  	}
   265  
   266  	return;
   267  }
   268  
   269  
   270  /*
   271  ** COMMAND: test-filetgz
   272  **
   273  ** Generate a tar.gz archive specified by the first argument that
   274  ** contains files given in the second and subsequent arguments.
   275  */
   276  void filetgz_cmd(void) {
   277  	int i;
   278  	Blob targz;
   279  	Blob file;
   280  
   281  	if (g.argc < 3) {
   282  		usage("ARCHIVE FILE....");
   283  	}
   284  
   285  	tgz_open(&targz);
   286  
   287  	for (i = 3; i < g.argc; i++) {
   288  		blob_zero(&file);
   289  
   290  		blob_read_from_file(&file, g.argv[i]);
   291  
   292  		tgz_add_file(&targz, g.argv[i], &file, 0, 0);
   293  
   294  		blob_reset(&file);
   295  	}
   296  
   297  	tgz_close(&targz);
   298  
   299  	blob_write_to_file(&targz, g.argv[2]);
   300  }
   301  
   302  /*
   303  ** Given the RID for a manifest, construct a tar.gz archive containing
   304  ** all files in the corresponding baseline.
   305  **
   306  ** If RID is for an object that is not a real manifest, then the
   307  ** resulting tar.gz archive contains a single file which is the RID
   308  ** object.
   309  **
   310  ** If the RID object does not exist in the repository, then
   311  ** pTar is zeroed.
   312  **
   313  ** zDir is a "synthetic" subdirectory which all targzped files get
   314  ** added to as part of the targz file. It may be 0 or an empty string,
   315  ** in which case it is ignored. The intention is to create a targz which
   316  ** politely expands into a subdir instead of filling your current dir
   317  ** with source files. For example, pass a UUID or "ProjectName".
   318  **
   319  */
   320  void tgz_of_baseline(int rid, Blob *pTar, const char *zDir) {
   321  	Blob mfile, hash, file;
   322  	Manifest *pManifest;
   323  	ManifestFile *pFile;
   324  	Blob filename;
   325  	unsigned long nFileTimeStamp;
   326  	int bExecutable;
   327  	int nPrefix;
   328    
   329  	content_get(rid, &mfile);
   330  	if (blob_size(&mfile) == 0) {
   331  		blob_zero(pTar);
   332  		return;
   333  	}
   334  
   335  	blob_zero(&hash);
   336  	blob_zero(&filename);
   337  
   338  	tgz_open(pTar);
   339  
   340  	if (zDir && zDir[0]) {
   341  		blob_appendf(&filename, "%s/", zDir);
   342  	}
   343  	nPrefix = blob_size(&filename);
   344  
   345  	pManifest = manifest_get(rid, CFTYPE_MANIFEST);
   346  	if (pManifest) {
   347  		char *zName;
   348  
   349  		nFileTimeStamp = get_unix_time_from_fossil(pManifest->rDate);
   350  
   351  		if (db_get_boolean("manifest", 0)) {
   352  			blob_append(&filename, "manifest", -1);
   353  			zName = blob_str(&filename);
   354  			tgz_add_file(pTar, zName, &mfile, nFileTimeStamp, 0);
   355  
   356  			sha1sum_blob(&mfile, &hash);
   357  			blob_reset(&mfile);
   358  			blob_append(&hash, "\n", 1);
   359  			blob_resize(&filename, nPrefix);
   360  			blob_append(&filename, "manifest.uuid", -1);
   361  			zName = blob_str(&filename);
   362  			tgz_add_file(pTar, zName, &hash, nFileTimeStamp, 0);
   363  			blob_reset(&hash);
   364  		}
   365  
   366  		manifest_file_rewind(pManifest);
   367  
   368  		while ((pFile = manifest_file_next(pManifest,0)) !=0) {
   369  			int fid;
   370  
   371  			fid = uuid_to_rid(pFile->zUuid, 0);
   372  
   373  			if (fid) {
   374  				if (pFile->zPerm && strchr(pFile->zPerm, 'x')) {
   375  					bExecutable = 1;
   376  				} else {
   377  					bExecutable = 0;
   378  				}
   379  
   380  				content_get(fid, &file);
   381  				blob_resize(&filename, nPrefix);
   382  				blob_append(&filename, pFile->zName, -1);
   383  				zName = blob_str(&filename);
   384  				tgz_add_file(pTar, zName, &file, nFileTimeStamp, bExecutable); 
   385  				blob_reset(&file);
   386  			}
   387  		}
   388  	} else {
   389  		blob_reset(&mfile);
   390  	}
   391  
   392  	manifest_destroy(pManifest);
   393  
   394  	blob_reset(&filename);
   395  
   396  	tgz_close(pTar);
   397  }
   398  
   399  /*
   400  ** COMMAND: tgz
   401  **
   402  ** Usage: %fossil tgz VERSION OUTPUTFILE [--name DIRECTORYNAME]
   403  **
   404  ** Generate a tar.gz archive for a specified version.  If the --name option is
   405  ** used, it argument becomes the name of the top-level directory in the
   406  ** resulting tar.gz archive.  If --name is omitted, the top-level directory
   407  ** named is derived from the project name, the check-in date and time, and
   408  ** the artifact ID of the check-in.
   409  */
   410  void baseline_tgz_cmd(void) {
   411  	int rid;
   412  	Blob targz;
   413  	const char *zName;
   414  
   415  	zName = find_option("name", 0, 1);
   416  
   417  	db_find_and_open_repository(0, 0);
   418  	if (g.argc != 4) {
   419  		usage("VERSION OUTPUTFILE");
   420  	}
   421  
   422  	rid = name_to_rid(g.argv[2]);
   423  	if (zName == 0) {
   424  		zName = db_text("default-name",
   425  		                "SELECT replace(%Q,' ','_') "
   426  		                " || strftime('_%%Y-%%m-%%d_%%H%%M%%S_', event.mtime) "
   427  		                " || substr(blob.uuid, 1, 10)"
   428  		                "  FROM event, blob"
   429  		                " WHERE event.objid=%d"
   430  		                "   AND blob.rid=%d",
   431  		                db_get("project-name", "unnamed"), rid, rid
   432  		               );
   433  	}
   434  
   435  	tgz_of_baseline(rid, &targz, zName);
   436  
   437  	blob_write_to_file(&targz, g.argv[3]);
   438  }
   439  
   440  /*
   441  ** WEBPAGE: tgz
   442  ** URL: /tgz/RID.tgz
   443  **
   444  ** Generate a tar.gz archive for the baseline.
   445  ** Return that tar.gz archive as the HTTP reply content.
   446  */
   447  void baseline_tgz_page(void) {
   448  	int rid;
   449  	char *zName, *zRid;
   450  	int nName, nRid;
   451  	Blob targz;
   452  
   453  	login_check_credentials();
   454  	if (!g.okZip) {
   455  		login_needed();
   456  
   457  		return;
   458  	}
   459  
   460  	zName = mprintf("%s", PD("name", ""));
   461  	nName = strlen(zName);
   462  
   463  	zRid = mprintf("%s", PD("uuid", ""));
   464  	nRid = strlen(zRid);
   465  
   466  	for (nName = strlen(zName) - 1; nName > 5; nName--) {
   467  		if (zName[nName] == '.') {
   468  			zName[nName] = 0;
   469  			break;
   470  		}
   471  	}
   472  
   473  	rid = name_to_rid(nRid ? zRid : zName);
   474  	if (rid == 0) {
   475  		@ Not found
   476  		return;
   477  	}
   478  
   479  	if (nRid == 0 && nName > 10) {
   480  		zName[10] = 0;
   481  	}
   482  
   483  	tgz_of_baseline(rid, &targz, zName);
   484  
   485  	free(zName);
   486  	free(zRid);
   487  
   488  	cgi_set_content(&targz);
   489  	cgi_set_content_type("application/x-compressed");
   490  }