-
Notifications
You must be signed in to change notification settings - Fork 25
/
DCTar.m
827 lines (747 loc) · 32.6 KB
/
DCTar.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// DCTar.m
//
// Created by Dalton Cherry on 5/21/14.
//
// Also created by Patrice Brend'amour of 2014-05-30
//
// Part of the code was inspired by libarchive
////////////////////////////////////////////////////////////////////////////////////////////////////
#import "DCTar.h"
#import <zlib.h>
// const definition
#define TAR_BLOCK_SIZE 512
#define TAR_TYPE_POSITION 156
#define TAR_NAME_POSITION 0
#define TAR_NAME_SIZE 100
#define TAR_SIZE_POSITION 124
#define TAR_SIZE_SIZE 12
#define TAR_MAX_BLOCK_LOAD_IN_MEMORY 100
/*
* Define structure of POSIX 'ustar' tar header.
+ Provided by libarchive.
*/
#define USTAR_name_offset 0
#define USTAR_name_size 100
#define USTAR_mode_offset 100
#define USTAR_mode_size 6
#define USTAR_mode_max_size 8
#define USTAR_uid_offset 108
#define USTAR_uid_size 6
#define USTAR_uid_max_size 8
#define USTAR_gid_offset 116
#define USTAR_gid_size 6
#define USTAR_gid_max_size 8
#define USTAR_size_offset 124
#define USTAR_size_size 11
#define USTAR_size_max_size 12
#define USTAR_mtime_offset 136
#define USTAR_mtime_size 11
#define USTAR_mtime_max_size 11
#define USTAR_checksum_offset 148
#define USTAR_checksum_size 8
#define USTAR_typeflag_offset 156
#define USTAR_typeflag_size 1
#define USTAR_linkname_offset 157
#define USTAR_linkname_size 100
#define USTAR_magic_offset 257
#define USTAR_magic_size 6
#define USTAR_version_offset 263
#define USTAR_version_size 2
#define USTAR_uname_offset 265
#define USTAR_uname_size 32
#define USTAR_gname_offset 297
#define USTAR_gname_size 32
#define USTAR_rdevmajor_offset 329
#define USTAR_rdevmajor_size 6
#define USTAR_rdevmajor_max_size 8
#define USTAR_rdevminor_offset 337
#define USTAR_rdevminor_size 6
#define USTAR_rdevminor_max_size 8
#define USTAR_prefix_offset 345
#define USTAR_prefix_size 155
#define USTAR_padding_offset 500
#define USTAR_padding_size 12
static const char template_header[] = {
/* name: 100 bytes */
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
0,0,0,0,
/* Mode, space-null termination: 8 bytes */
'0','0','0','0','0','0', ' ','\0',
/* uid, space-null termination: 8 bytes */
'0','0','0','0','0','0', ' ','\0',
/* gid, space-null termination: 8 bytes */
'0','0','0','0','0','0', ' ','\0',
/* size, space termation: 12 bytes */
'0','0','0','0','0','0','0','0','0','0','0', ' ',
/* mtime, space termation: 12 bytes */
'0','0','0','0','0','0','0','0','0','0','0', ' ',
/* Initial checksum value: 8 spaces */
' ',' ',' ',' ',' ',' ',' ',' ',
/* Typeflag: 1 byte */
'0', /* '0' = regular file */
/* Linkname: 100 bytes */
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
0,0,0,0,
/* Magic: 6 bytes, Version: 2 bytes */
'u','s','t','a','r','\0', '0','0',
/* Uname: 32 bytes */
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
/* Gname: 32 bytes */
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
/* rdevmajor + space/null padding: 8 bytes */
'0','0','0','0','0','0', ' ','\0',
/* rdevminor + space/null padding: 8 bytes */
'0','0','0','0','0','0', ' ','\0',
/* Prefix: 155 bytes */
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,
/* Padding: 12 bytes */
0,0,0,0,0,0,0,0, 0,0,0,0
};
@implementation DCTar
////////////////////////////////////////////////////////////////////////////////////////////////////
+(BOOL)compressFileAtPath:(NSString*)filePath toPath:(NSString*)path error:(NSError**)error
{
//does decompression as needed (based on if the file ends .gz)
NSFileManager *manager = [NSFileManager defaultManager];
if([manager fileExistsAtPath:filePath]) {
NSString *workPath = path;
BOOL doGzip = NO;
if([workPath hasSuffix:@".gz"]) {
NSRange range = [workPath rangeOfString:@".gz" options:NSBackwardsSearch];
NSString *tarPath = [workPath substringToIndex:range.location];
workPath = tarPath;
doGzip = YES;
}
BOOL status = YES;
if([workPath hasSuffix:@".tar"]) {
if(![self tarFileAtPath:filePath toPath:workPath error:error]) {
if(error)
*error = [self errorWithDetail:@"unable to tar the file" code:-3];
return status;
}
}
if(doGzip) {
NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:workPath];
status = [self fileDeflate:fileHandle isGzip:YES toPath:path];
[fileHandle closeFile];
if(status) {
[manager removeItemAtPath:workPath error:nil]; //remove our temp tar file
}
}
return status;
}
if(error)
*error = [self errorWithDetail:@"file not found" code:-2];
return NO;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//+(BOOL)compressData:(NSData*)data toPath:(NSString*)path error:(NSError**)error
//{
// //does both gzip and tar
// //TODO: make a way to tar and gzipped a data blob.
// return NO;
//}
////////////////////////////////////////////////////////////////////////////////////////////////////
+(BOOL)decompressFileAtPath:(NSString*)filePath toPath:(NSString*)path error:(NSError**)error
{
//does decompression as needed (based on if the file ends .gz)
NSFileManager *manager = [NSFileManager defaultManager];
if([manager fileExistsAtPath:filePath]) {
NSDictionary *attributes = [manager attributesOfItemAtPath:filePath error:nil];
NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
if([filePath hasSuffix:@".gz"]) {
NSRange range = [filePath rangeOfString:@".gz" options:NSBackwardsSearch];
NSString *tarPath = [filePath substringToIndex:range.location];
[manager removeItemAtPath:tarPath error:nil];
if([self fileInflate:fileHandle isGzip:YES toPath:tarPath]) {
[fileHandle closeFile];
attributes = [manager attributesOfItemAtPath:tarPath error:nil];
fileHandle = [NSFileHandle fileHandleForReadingAtPath:tarPath];
filePath = tarPath;
}
}
if([filePath hasSuffix:@".tar"]) {
BOOL status = [self untarFileAtPath:filePath toPath:path error:error];
[manager removeItemAtPath:filePath error:nil]; //remove our temp tar file
return status;
}
return YES;
}
if(error)
*error = [self errorWithDetail:@"file not found" code:-2];
return NO;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
+(BOOL)decompressData:(NSData*)data toPath:(NSString*)path error:(NSError**)error
{
return [self untarData:[self gzipDecompress:data] toPath:path error:error];
}
////////////////////////////////////////////////////////////////////////////////////////////////////
+(BOOL)tarFileAtPath:(NSString*)tarFilePath toPath:(NSString*)path error:(NSError**)error
{
NSFileManager *fileManager = [NSFileManager defaultManager];
if([fileManager fileExistsAtPath:tarFilePath]) {
[fileManager removeItemAtPath:path error:nil];
[@"" writeToFile:path atomically:NO encoding:NSUTF8StringEncoding error:nil];
NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:path];
for(NSString *file in [fileManager enumeratorAtPath:tarFilePath]) {
BOOL isDir = NO;
[fileManager fileExistsAtPath:[tarFilePath stringByAppendingPathComponent:file] isDirectory:&isDir];
[self chunkedWriteDataFromPath:file inDirectory:tarFilePath isDirectory:isDir toFileHandle: fileHandle];
}
//Append two empty blocks to indicate end
char block[TAR_BLOCK_SIZE*2];
memset(&block, '\0', TAR_BLOCK_SIZE*2);
[fileHandle writeData:[NSData dataWithBytes:block length:TAR_BLOCK_SIZE*2]];
[fileHandle closeFile];
free(block);
return YES;
}
if(error)
*error = [self errorWithDetail:@"tar file not found" code:-2];
return NO;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
+(BOOL)untarFileAtPath:(NSString*)tarFilePath toPath:(NSString*)path error:(NSError**)error
{
NSFileManager *manager = [NSFileManager defaultManager];
if([manager fileExistsAtPath:tarFilePath]) {
NSDictionary *attributes = [manager attributesOfItemAtPath:tarFilePath error:nil];
unsigned long long size = [attributes[NSFileSize] longLongValue];
NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:tarFilePath];
return [self untarObject:fileHandle size:size toPath:path error:error];
}
if(error)
*error = [self errorWithDetail:@"tar file not found" code:-2];
return NO;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//+(BOOL)tarData:(NSData*)tarData toPath:(NSString*)path error:(NSError**)error
//{
// //TODO: make a way to tar a data blob.
// return NO;
//}
////////////////////////////////////////////////////////////////////////////////////////////////////
+(BOOL)untarData:(NSData*)tarData toPath:(NSString*)path error:(NSError**)error
{
return [self untarObject:tarData size:[tarData length] toPath:path error:error];
}
////////////////////////////////////////////////////////////////////////////////////////////////////
+(NSData*)gzipCompress:(NSData*)data
{
return [self deflate:data isGzip:YES];
}
////////////////////////////////////////////////////////////////////////////////////////////////////
+(NSData*)gzipDecompress:(NSData*)data
{
return [self inflate:data isGzip:YES];
}
////////////////////////////////////////////////////////////////////////////////////////////////////
+(BOOL)gzipDecompress:(NSString*)filePath toPath:(NSString*)toPath error:(NSError**)error
{
NSFileManager *manager = [NSFileManager defaultManager];
if([manager fileExistsAtPath:filePath]) {
NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
BOOL status = [self fileInflate:fileHandle isGzip:YES toPath:toPath];
if(!status) {
if(error)
*error = [self errorWithDetail:@"gzip failed to decompress the file" code:-3];
}
return status;
}
if(error)
*error = [self errorWithDetail:@"tar file not found" code:-2];
return NO;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
+(NSData*)zlibDecompress:(NSData*)data
{
return [self inflate:data isGzip:NO];
}
////////////////////////////////////////////////////////////////////////////////////////////////////
+(NSData*)zlibCompress:(NSData*)data
{
return [self deflate:data isGzip:NO];
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//private methods
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
//untar methods
////////////////////////////////////////////////////////////////////////////////////////////////////
+(BOOL)untarObject:(id)tarObject size:(unsigned long long)size toPath:(NSString*)path error:(NSError**)error
{
//do untar awesomeness
NSFileManager *manager = [NSFileManager defaultManager];
[manager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
BOOL didError = NO;
unsigned long long location = 0; // Position in the file
while (location < size) {
unsigned long long blockCount = 1; // 1 block for the header
@autoreleasepool {
char type = [self typeForObject:tarObject atOffset:location];
if(type == '0' || type == '\0') {
NSString *name = [self nameForObject:tarObject atOffset:location];
NSString *filePath = [path stringByAppendingPathComponent:name]; // Create a full path from the name
unsigned long long size = [self sizeForObject:tarObject atOffset:location];
//NSLog(@"file created: %@",name);
if(size == 0) {
[@"" writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:error];
}
blockCount += (size - 1) / TAR_BLOCK_SIZE + 1; // size/TAR_BLOCK_SIZE rounded up
[self writeFileDataForObject:tarObject atLocation:(location + TAR_BLOCK_SIZE) withLength:size atPath:filePath];
} else if(type == '5') {
NSString *name = [self nameForObject:tarObject atOffset:location];
NSString *directoryPath = [path stringByAppendingPathComponent:name]; // Create a full path from the name
[manager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:nil]; //Write the directory on filesystem
//NSLog(@"directory created: %@",name);
} else if(type == 'g') {
} else if(type > '0' || type < 'Z') {
//NSLog(@"unknown type: %c",type);
//does nothing
//NSLog(@"nothing char: %c",type);
} else {
//Failure
//NSLog(@"failure: %c",type);
didError = YES;
break;
}
}
location += blockCount * TAR_BLOCK_SIZE;
}
if(didError) {
if(error)
*error = [self errorWithDetail:@"Unexpected type found in tar file" code:-1];
return NO;
}
return YES;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
+(NSError*)errorWithDetail:(NSString*)detail code:(NSInteger)code
{
NSMutableDictionary* details = [NSMutableDictionary dictionary];
[details setValue:detail forKey:NSLocalizedDescriptionKey];
return [[NSError alloc] initWithDomain:@"TarKit" code:code userInfo:details];
}
////////////////////////////////////////////////////////////////////////////////////////////////////
+ (char)typeForObject:(id)object atOffset:(unsigned long long)offset
{
char type;
memcpy(&type, [self dataForObject:object inRange:NSMakeRange((uInt)offset + TAR_TYPE_POSITION, 1)
orLocation:offset + TAR_TYPE_POSITION andLength:1].bytes, 1);
return type;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
+ (NSString *)nameForObject:(id)object atOffset:(unsigned long long)offset
{
char nameBytes[TAR_NAME_SIZE + 1]; // TAR_NAME_SIZE+1 for nul char at end
memset(&nameBytes, '\0', TAR_NAME_SIZE + 1); // Fill byte array with nul char
memcpy(&nameBytes, [self dataForObject:object inRange:NSMakeRange((uInt)offset + TAR_NAME_POSITION, TAR_NAME_SIZE)
orLocation:offset + TAR_NAME_POSITION andLength:TAR_NAME_SIZE].bytes, TAR_NAME_SIZE);
NSString *name = [NSString stringWithCString:nameBytes encoding:NSASCIIStringEncoding];
free(nameBytes);
return name;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
+ (unsigned long long)sizeForObject:(id)object atOffset:(unsigned long long)offset
{
char sizeBytes[TAR_SIZE_SIZE + 1]; // TAR_SIZE_SIZE+1 for nul char at end
memset(&sizeBytes, '\0', TAR_SIZE_SIZE + 1); // Fill byte array with nul char
memcpy(&sizeBytes, [self dataForObject:object inRange:NSMakeRange((uInt)offset + TAR_SIZE_POSITION, TAR_SIZE_SIZE)
orLocation:offset + TAR_SIZE_POSITION andLength:TAR_SIZE_SIZE].bytes, TAR_SIZE_SIZE);
unsigned long long size = strtol(sizeBytes, NULL, 8); // Size is an octal number, convert to decimal
free(sizeBytes);
return size;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
+(void)writeFileDataForObject:(id)object atLocation:(unsigned long long)location withLength:(unsigned long long)length atPath:(NSString *)path
{
if ([object isKindOfClass:[NSData class]]) {
[[NSFileManager defaultManager] createFileAtPath:path contents:[object subdataWithRange:NSMakeRange((uInt)location, (uInt)length)] attributes:nil]; //Write the file on filesystem
} else if ([object isKindOfClass:[NSFileHandle class]]){
if ([[NSData data] writeToFile:path atomically:NO]) {
NSFileHandle *destinationFile = [NSFileHandle fileHandleForWritingAtPath:path];
[object seekToFileOffset:location];
uInt maxSize = TAR_MAX_BLOCK_LOAD_IN_MEMORY * TAR_BLOCK_SIZE;
while (length > maxSize) {
@autoreleasepool {
[destinationFile writeData:[object readDataOfLength:maxSize]];
location += maxSize;
length -= maxSize;
}
}
[destinationFile writeData:[object readDataOfLength:(uInt)length]];
[destinationFile closeFile];
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
+(NSData *)dataForObject:(id)object inRange:(NSRange)range orLocation:(unsigned long long)location andLength:(unsigned long long)length
{
if ([object isKindOfClass:[NSData class]]) {
return [object subdataWithRange:range];
} else if ([object isKindOfClass:[NSFileHandle class]]) {
[object seekToFileOffset:location];
return [object readDataOfLength:(uInt)length];
}
return nil;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//tar stuff
////////////////////////////////////////////////////////////////////////////////////////////////////
+ (void)chunkedWriteDataFromPath:(NSString *) path inDirectory:(NSString *)basepath isDirectory:(BOOL) isDirectory toFileHandle:(NSFileHandle *)outputFileHandle {
if(isDirectory) {
path = [path stringByAppendingString:@"/"];
}
//write header
[self writeHeaderForPath:path withBasePath:basepath isDirectory:isDirectory toFileHandle:outputFileHandle];
//write data
if(!isDirectory) {
[self chunkedWriteDataFromPath: [basepath stringByAppendingPathComponent:path] toFileHandle: outputFileHandle];
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
+(void)writeHeaderForPath:(NSString*)path withBasePath:(NSString*)basePath isDirectory:(BOOL)isDirectory toFileHandle:(NSFileHandle *)outputFileHandle {
char buffer[TAR_BLOCK_SIZE];
memcpy(buffer,&template_header, TAR_BLOCK_SIZE);
NSError *error = nil;
NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[basePath stringByAppendingPathComponent:path] error:&error];
int permissions = [[attributes objectForKey:NSFilePosixPermissions] shortValue];
NSDate * modificationDate = [attributes objectForKey:NSFileModificationDate];
long ownerId = [[attributes objectForKey:NSFileOwnerAccountID] longValue];
long groupId = [[attributes objectForKey:NSFileGroupOwnerAccountID] longValue];
NSString *ownerName = [attributes objectForKey:NSFileOwnerAccountName];
NSString *groupName = [attributes objectForKey:NSFileGroupOwnerAccountName];
unsigned long long fileSize = [[attributes objectForKey:NSFileSize] longLongValue];
char nameChar[USTAR_name_size];
[self writeString:path toChar:nameChar withLength:USTAR_name_size];
char unameChar[USTAR_uname_size];
[self writeString:ownerName toChar:unameChar withLength:USTAR_uname_size];
char gnameChar[USTAR_gname_size];
[self writeString:groupName toChar:gnameChar withLength:USTAR_gname_size];
format_number(permissions & 07777, buffer+USTAR_mode_offset, USTAR_mode_size, USTAR_mode_max_size, 0);
format_number(ownerId,
buffer + USTAR_uid_offset, USTAR_uid_size, USTAR_uid_max_size, 0);
format_number(groupId,
buffer + USTAR_gid_offset, USTAR_gid_size, USTAR_gid_max_size, 0);
format_number(fileSize, buffer + USTAR_size_offset, USTAR_size_size, USTAR_size_max_size, 0);
format_number([modificationDate timeIntervalSince1970],
buffer + USTAR_mtime_offset, USTAR_mtime_size, USTAR_mtime_max_size, 0);
unsigned long nameLength = strlen(nameChar);
if (nameLength <= USTAR_name_size)
memcpy(buffer + USTAR_name_offset, nameChar, nameLength);
else {
/* Store in two pieces, splitting at a '/'. */
const char *p = strchr(nameChar + nameLength - USTAR_name_size - 1, '/');
/*
* Look for the next '/' if we chose the first character
* as the separator. (ustar format doesn't permit
* an empty prefix.)
*/
if (p == nameChar)
p = strchr(p + 1, '/');
memcpy(buffer + USTAR_prefix_offset, nameChar, p - nameChar);
memcpy(buffer + USTAR_name_offset, p + 1,
nameChar + nameLength - p - 1);
}
memcpy(buffer+USTAR_uname_offset,unameChar,USTAR_uname_size);
memcpy(buffer+USTAR_gname_offset,gnameChar,USTAR_gname_size);
if(isDirectory) {
format_number(0, buffer + USTAR_size_offset, USTAR_size_size, USTAR_size_max_size, 0);
memset(buffer+USTAR_typeflag_offset,'5',USTAR_typeflag_size);
}
//Checksum
int checksum = 0;
for (int i = 0; i < TAR_BLOCK_SIZE; i++)
checksum += 255 & (unsigned int)buffer[i];
buffer[USTAR_checksum_offset + 6] = '\0';
format_octal(checksum, buffer + USTAR_checksum_offset, 6);
[outputFileHandle writeData:[NSData dataWithBytes:buffer length:TAR_BLOCK_SIZE]];
free(buffer);
free(nameChar);
free(unameChar);
free(gnameChar);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
+(void)chunkedWriteDataFromPath:(NSString *)path toFileHandle:(NSFileHandle*) outputFileHandle {
NSFileHandle *sourceFile = [NSFileHandle fileHandleForReadingAtPath:path];
NSUInteger fileLength = [sourceFile seekToEndOfFile];
NSUInteger offset = 0;
[sourceFile seekToFileOffset:0];
while(offset < fileLength) {
NSData *chunk = [sourceFile readDataOfLength:TAR_BLOCK_SIZE];
NSUInteger chunkSize = [chunk length];
[outputFileHandle writeData:chunk];
unsigned long padding = (TAR_BLOCK_SIZE - (chunkSize % TAR_BLOCK_SIZE)) % TAR_BLOCK_SIZE;
if(padding > 0) {
char buffer[padding];
memset(&buffer, '\0', padding);
[outputFileHandle writeData:[NSData dataWithBytes:buffer length:TAR_BLOCK_SIZE*2]];
free(buffer);
}
offset += chunkSize;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
+ (void)writeString:(NSString*)string toChar:(char*)charArray withLength:(NSInteger)size
{
NSData *stringData = [string dataUsingEncoding:NSASCIIStringEncoding];
memset(charArray, '\0', size);
[stringData getBytes:charArray length:[stringData length]];
}
////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark - Formatting
//Thanks to libarchive
////////////////////////////////////////////////////////////////////////////////////////////////////
//Format a number into a field, with some intelligence.
static int format_number(int64_t v, char *p, int s, int maxsize, int strict)
{
int64_t limit;
limit = ((int64_t)1 << (s*3));
/* "Strict" only permits octal values with proper termination. */
if (strict)
return (format_octal(v, p, s));
/*
* In non-strict mode, we allow the number to overwrite one or
* more bytes of the field termination. Even old tar
* implementations should be able to handle this with no
* problem.
*/
if (v >= 0) {
while (s <= maxsize) {
if (v < limit)
return (format_octal(v, p, s));
s++;
limit <<= 3;
}
}
/* Base-256 can handle any number, positive or negative. */
return (format_256(v, p, maxsize));
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//Format a number into the specified field using base-256.
static int format_256(int64_t v, char *p, int s)
{
p += s;
while (s-- > 0) {
*--p = (char)(v & 0xff);
v >>= 8;
}
*p |= 0x80; /* Set the base-256 marker bit. */
return (0);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//Format a number into the specified field.
static int format_octal(int64_t v, char *p, int s)
{
int len;
len = s;
/* Octal values can't be negative, so use 0. */
if (v < 0) {
while (len-- > 0)
*p++ = '0';
return (-1);
}
p += s; /* Start at the end and work backwards. */
while (s-- > 0) {
*--p = (char)('0' + (v & 7));
v >>= 3;
}
if (v == 0)
return (0);
/* If it overflowed, fill field with max value. */
while (len-- > 0)
*p++ = '7';
return (-1);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//compression stuff
////////////////////////////////////////////////////////////////////////////////////////////////////
+(NSData*)deflate:(NSData*)data isGzip:(BOOL)isgzip
{
if ([data length] == 0)
return nil;
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.total_out = 0;
strm.next_in=(Bytef*)[data bytes];
strm.avail_in = (uInt)[data length];
if(isgzip){
if (deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (15+16), 8, Z_DEFAULT_STRATEGY) != Z_OK)
return nil;
} else {
if (deflateInit(&strm, Z_DEFAULT_COMPRESSION) != Z_OK)
return nil;
}
NSMutableData *compressed = [NSMutableData dataWithLength:16384]; // 16K chuncks for expansion
do {
if (strm.total_out >= [compressed length])
[compressed increaseLengthBy:16384];
strm.next_out = [compressed mutableBytes] + strm.total_out;
strm.avail_out = (uInt)([compressed length] - strm.total_out);
deflate(&strm, Z_FINISH);
} while (strm.avail_out == 0);
deflateEnd(&strm);
[compressed setLength:strm.total_out];
return [NSData dataWithData:compressed];
}
////////////////////////////////////////////////////////////////////////////////////////////////////
+(BOOL)fileDeflate:(NSFileHandle*)fileHandle isGzip:(BOOL)isgzip toPath:(NSString*)toPath
{
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.total_out = 0;
strm.avail_in = 0;
strm.next_in = Z_NULL;
if(isgzip){
if (deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (15+16), 8, Z_DEFAULT_STRATEGY) != Z_OK)
return NO;
} else {
if (deflateInit(&strm, Z_DEFAULT_COMPRESSION) != Z_OK)
return NO;
}
[@"" writeToFile:toPath atomically:NO encoding:NSUTF8StringEncoding error:nil];
NSFileHandle *writeHandle = [NSFileHandle fileHandleForWritingAtPath:toPath];
BOOL done = NO;
uInt chunkSize = 16384;
do {
NSData *chunk = [fileHandle readDataOfLength:chunkSize];
strm.avail_in = (uInt)[chunk length];
strm.next_in = (Bytef*)[chunk bytes];
int flush = Z_NO_FLUSH;
if(chunk.length == 0)
flush = Z_FINISH;
do {
NSMutableData *compressed = [NSMutableData dataWithLength:chunkSize];
strm.avail_out = chunkSize;
strm.next_out = (Bytef*)[compressed mutableBytes];
NSInteger status = deflate (&strm, flush);
if (status == Z_STREAM_END)
done = YES;
else if(status == Z_BUF_ERROR)
continue;
else if (status != Z_OK) {
done = YES;
return NO;
}
NSInteger have = chunkSize - strm.avail_out;
[compressed setLength:have];
[writeHandle writeData:compressed];
} while (strm.avail_out == 0);
} while (!done);
deflateEnd(&strm);
return YES;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
+(NSData*)inflate:(NSData*)data isGzip:(BOOL)isgzip
{
if ([data length] == 0)
return nil;
uInt full_length = (uInt)[data length];
uInt half_length = (uInt)[data length] / 2;
NSMutableData *decompressed = [NSMutableData dataWithLength: full_length + half_length];
BOOL done = NO;
int status;
z_stream strm;
strm.next_in = (Bytef*)[data bytes];
strm.avail_in = (uInt)[data length];
strm.total_out = 0;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
if(isgzip) {
if(inflateInit2(&strm, (15+32)) != Z_OK)
return nil;
} else {
if(inflateInit(&strm) != Z_OK)
return nil;
}
while (!done)
{
// Make sure we have enough room and reset the lengths.
if (strm.total_out >= [decompressed length])
[decompressed increaseLengthBy:half_length];
strm.next_out = [decompressed mutableBytes] + strm.total_out;
strm.avail_out = (uInt)([decompressed length] - strm.total_out);
// Inflate another chunk.
status = inflate (&strm, Z_SYNC_FLUSH);
if (status == Z_STREAM_END)
done = YES;
else if(status == Z_BUF_ERROR)
continue;
else if (status != Z_OK)
break;
}
if (inflateEnd (&strm) != Z_OK)
return nil;
// Set real length.
if (done)
{
[decompressed setLength: strm.total_out];
return [NSData dataWithData:decompressed];
}
return nil;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
+(BOOL)fileInflate:(NSFileHandle*)fileHandle isGzip:(BOOL)isgzip toPath:(NSString*)toPath
{
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.total_out = 0;
strm.avail_in = 0;
strm.next_in = Z_NULL;
if(isgzip) {
if(inflateInit2(&strm, (15+32)) != Z_OK)
return NO;
} else {
if(inflateInit(&strm) != Z_OK)
return NO;
}
[@"" writeToFile:toPath atomically:NO encoding:NSUTF8StringEncoding error:nil];
NSFileHandle *writeHandle = [NSFileHandle fileHandleForWritingAtPath:toPath];
BOOL done = NO;
uInt chunkSize = 16384;
do {
NSData *chunk = [fileHandle readDataOfLength:chunkSize];
strm.avail_in = (uInt)[chunk length];
strm.next_in = (Bytef*)[chunk bytes];
do {
NSMutableData *decompressed = [NSMutableData dataWithLength:chunkSize];
strm.avail_out = chunkSize;
strm.next_out = (Bytef*)[decompressed mutableBytes];
NSInteger status = inflate (&strm, Z_SYNC_FLUSH);
if (status == Z_STREAM_END)
done = YES;
else if(status == Z_BUF_ERROR)
continue;
else if (status != Z_OK) {
done = YES;
return NO;
}
NSInteger have = chunkSize - strm.avail_out;
[decompressed setLength:have];
[writeHandle writeData:decompressed];
} while (strm.avail_out == 0);
} while (!done);
[writeHandle closeFile];
if (inflateEnd (&strm) != Z_OK)
return NO;
return YES;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
@end