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 }