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 }