1 /* Copyright (C) 2000-2005 Thomas Bopp, Thorsten Hampel, Ludger Merkens
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
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. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 * $Id: tar.pike,v 1.1 2008/03/31 13:39:57 exodusd Exp $
19 inherit "/kernel/module.pike";
20 inherit Filesystem.Base;
21 inherit "/kernel/DocFile";
22 inherit Filesystem.Stat;
23 inherit Filesystem.System;
28 //! This is the tar module. It is able to create a string-archive of
30 class tar : public module.pike,DocFile{
41 #define LOG_TAR(s, args...) werror("tar: "+s+"\n", args)
43 #define LOG_TAR(s, args...)
49 * Convert an integer value to oct.
51 * @param int val - the value to convert
52 * @param int size - the size of the resulting buffer
53 * @return the resulting string buffer
56 string to_oct(int val, int size)
58 string v = (string) val;
62 # define MAX_OCTAL_VAL_WITH_DIGITS(digits) (1 << ((digits) * 3) - 1)
64 for ( i = 0; i < size; i++ ) oct += " ";
66 if ( val <= MAX_OCTAL_VAL_WITH_DIGITS(size-1) )
69 while ( i >= 0 && val != 0 ) {
70 oct[--i] = '0' + (int)(val&7);
81 private string header;
84 * Copy a source string to the header at position 'pos'.
86 * @param string source - source string to copy.
87 * @param int pos - the position to copy it in 'header'.
90 void buffer_copy(string source, int pos)
92 for ( int i = 0; i < strlen(source); i++ )
93 header[pos+i] = source[i];
100 * Create a header in the tarfile with name 'fname' and content 'content'.
102 * @param fname - the filename to store.
103 * @param content - the content of the file.
104 * @return the tar header for the filename.
107 string tar_header(string fname, string content)
114 ({ "ä", "ö", "ü", "Ä", "Ö", "Ü", "ß", "<", ">", "?", " ", "'" }),
115 ({ "\344", "\366", "\374", "\304", "\326", "\334", "\337",
116 "\74", "\76", "\77", "\40", "\47" }));
118 if ( !stringp(content) )
124 if ( strlen(fname) > 99 ) {
125 name = basename(fname);
126 prefix = dirname(fname);
132 if ( strlen(name) > 99 )
133 error("Cannot store names with more than 99 bytes: "+name+"\n");
134 if ( strlen(prefix) > 154 )
135 error("Cannot store files with prefix more than 154 chars.");
137 header = "\0" * BLOCKSIZE;
139 buffer_copy(name, 0);
140 buffer_copy("0100664", 100);
141 buffer_copy("0000767", 108);
142 buffer_copy("0000767", 116);
143 buffer_copy(to_oct(l, 12), 124);
144 buffer_copy(to_oct(time(), 12), 136);
145 int chksum = 7*32; // the checksum field is counted as ' '
146 buffer_copy("ustar ", 257);
147 buffer_copy("steam", 265);
148 buffer_copy("steam", 297);
149 buffer_copy(" 0", 155);
151 buffer_copy(prefix, 345);
153 for ( i = 0; i < BLOCKSIZE; i++ )
154 chksum += (0xff & header[i]);
156 buffer_copy(to_oct(chksum, 7), 148);
164 * Tar the content of the file 'fname'. Tars both header and content.
166 * @param string fname - the filename to tar.
167 * @param string content - the content of the file.
168 * @return the tared string.
170 string tar_content(string fname, string content)
173 if ( !stringp(fname) || fname== "" ) {
174 FATAL("Empty file name !");
177 LOG("tar_content("+fname+", "+strlen(content)+" bytes)\n");
178 if ( !stringp(content) || strlen(content) == 0 )
179 return tar_header(fname, content);
181 buf = tar_header(fname, content);
183 int rest = (strlen(content) % BLOCKSIZE);
184 if ( rest > 0 ) { // does not fit into a single buffer
185 rest = BLOCKSIZE - rest;
186 string b = "\0" * rest;
193 * Create an end header for the tarfile.
195 * @return the end header.
199 return "\0" * BLOCKSIZE;
203 * Create an empty tarfile header.
205 * @return an empty tarfile header.
208 string empty_header()
210 return tar_header("", "");
214 * Create a tarfile with an array of given steam objects. This
215 * tars there identifiers and call the content functions.
217 * @param array arr - array of documents to be tared.
218 * @return the tarfile.
220 string tar_objects(array arr)
223 foreach(arr, object obj) {
224 tar += tar_content(obj->get_identifier(), obj->get_content());
226 tar += end_header(); // empty header at the end
230 object open_file(string fname, mixed mode)
232 return ((program)"/kernel/DocFile")(OBJ(fname), mode);
237 object open(string fname, string mode)
239 LOG_TAR("steamfs:open(%s)", fname);
240 object file = find_object(fname);
241 //return Stdio.FakeFile(file->get_content(), mode);
242 return open_file(fname, mode);
244 array get_dir(string dirname) {
247 object cont = find_object(dirname);
248 array dir = cont->get_inventory();
249 foreach(dir, object o) {
250 if ( o->get_object_class() & CLASS_CONTAINER )
251 files += ({ o->get_identifier() + "/" });
253 files += ({ o->get_identifier() });
257 array get_files(string dirname) {
258 object cont = find_object(dirname);
259 return cont->get_inventory();
264 array unpack_directory(string fs, string dirname, void|object contEnv)
267 array created = ({ });
268 object fsys = TarFile(fs);
270 LOG_TAR("tar: unpack_directory(%s)", dirname);
271 files = fsys->get_dir(dirname);
272 foreach(files, string fname) {
273 //object file = TarFile(fs)->open(fname, "r");
274 object file = fsys->open(fname, "r");
275 if ( !objectp(file) )
276 steam_error("Failed to open: " + dirname + fname);
281 if ( file->isdir() ) // container ?
283 cont = get_factory(CLASS_CONTAINER)->execute( ([ "name":
284 basename(fname), ]) );
285 contfiles = unpack_directory(fs, fname, cont);
286 foreach(contfiles, object dirfile)
288 created += ({ cont });
290 else // ansonsten: File
292 fname = basename(fname);
293 LOG_TAR("tar: file(%s)", fname);
294 doc = get_factory(CLASS_DOCUMENT)->execute( (["name":fname, ]) );
295 if ( objectp(contEnv) )
296 doc->move(contEnv); // move before setting ocntent
297 doc->set_content( file->read() );
298 created += ({ doc });
299 LOG_TAR("tar: file(%s) has %d bytes", fname, doc->get_content_size());
309 array unpack(string|object fname)
311 if ( objectp(fname) )
312 fname = get_module("filepath:tree")->object_to_filename(fname);
313 LOG_TAR("tar: unpack(%s)", fname);
314 array result = unpack_directory(fname, "");
318 class _Tar // filesystem
327 private int start, pos, len;
331 string _sprintf(int t)
333 return t=='O' && sprintf("Filesystem.Tar.ReadFile(%d, %d /* pos = %d */)",
348 return ::seek((pos = p)+start);
351 string read(int|void n)
353 if(!query_num_arg() || n>len-pos)
359 void create(int p, int l, string type)
361 ::create(fd->get_document(), "r");
369 return !stringp(_type) || _type == "dir";
376 constant RECORDSIZE = 512;
377 constant NAMSIZ = 100;
378 constant TUNMLEN = 32;
379 constant TGNMLEN = 32;
380 constant SPARSE_EXT_HDR = 21;
381 constant SPARSE_IN_HDR = 4;
386 string arch_linkname;
396 // Header description:
398 // Fieldno Offset len Description
401 // 1 100 8 Mode (octal)
402 // 2 108 8 uid (octal)
403 // 3 116 8 gid (octal)
404 // 4 124 12 size (octal)
405 // 5 136 12 mtime (octal)
406 // 6 148 8 chksum (octal)
408 // 8 157 100 linkname
410 // 10 265 32 (USTAR) uname
411 // 11 297 32 (USTAR) gname
412 // 12 329 8 devmajor (octal)
413 // 13 337 8 devminor (octal)
414 // 14 345 167 (USTAR) Long path
416 // magic can be any of:
417 // "ustar\0""00" POSIX ustar (Version 0?).
418 // "ustar \0" GNU tar (POSIX draft)
420 void create(void|string s, void|int _pos)
429 array a = array_sscanf(s,
430 "%"+((string)NAMSIZ)+"s%8s%8s%8s%12s%12s%8s"
431 "%c%"+((string)NAMSIZ)+"s%8s"
432 "%"+((string)TUNMLEN)+"s"
433 "%"+((string)TGNMLEN)+"s%8s%8s%167s");
434 sscanf(a[0], "%s%*[\0]", arch_name);
435 sscanf(a[1], "%o", mode);
436 sscanf(a[2], "%o", uid);
437 sscanf(a[3], "%o", gid);
438 sscanf(a[4], "%o", size);
439 sscanf(a[5], "%o", mtime);
440 sscanf(a[6], "%o", chksum);
442 sscanf(a[8], "%s%*[\0]", arch_linkname);
443 sscanf(a[9], "%s%*[\0]", magic);
445 if((magic=="ustar ") || (magic == "ustar"))
447 // GNU ustar or POSIX ustar
448 sscanf(a[10], "%s\0", uname);
449 sscanf(a[11], "%s\0", gname);
450 if (a[9] == "ustar\0""00") {
451 // POSIX ustar (Version 0?)
452 string long_path = "";
453 sscanf(a[14], "%s\0", long_path);
454 if (sizeof(long_path)) {
455 arch_name = long_path + "/" + arch_name;
457 } else if (arch_name == "././@LongLink") {
459 // FIXME: Data contains full filename of next record.
465 sscanf(a[12], "%o", devmajor);
466 sscanf(a[13], "%o", devminor);
468 fullpath = combine_path_unix("/", arch_name);
469 name = (fullpath/"/")[-1];
470 atime = ctime = mtime;
482 ])[linkflag] || "reg";
486 object open(string mode)
489 error("Can only read right now.\n");
490 return ReadFile(pos, size, type);
494 array(Record) entries = ({});
496 mapping filename_to_entry;
498 void mkdirnode(string what, Record from, object parent)
502 if(what=="") what = "/";
505 r->name = (what/"/")[-1];
507 r->mode = 0755|((from->mode&020)?020:0)|((from->mode&02)?02:0);
512 r->atime = r->ctime = r->mtime = from->mtime;
513 r->filesystem = parent;
515 filename_to_entry[what] = r;
518 void create(Stdio.File fd, string filename, object parent)
520 this_program::filename = filename;
523 this_program::fd = fd;
524 int pos = 0; // fd is at position 0 here
528 string s = this_program::fd->read(512);
530 if(s=="" || strlen(s)<512 || sscanf(s, "%*[\0]%*2s")==1)
533 r = Record(s, pos+512);
534 r->filesystem = parent;
536 if(r->arch_name!="") // valid file?
539 pos += 512 + r->size;
541 pos += 512 - (pos%512);
542 this_program::fd->seek(pos);
545 filename_to_entry = mkmapping(entries->fullpath, entries);
547 // create missing dirnodes
550 foreach(entries, Record r)
552 array path = r->fullpath/"/";
553 if(path[..sizeof(path)-2]==last) continue; // same dir
554 last = path[..sizeof(path)-2];
556 for(int i = 0; i<sizeof(last); i++)
557 if(!filename_to_entry[last[..i]*"/"])
558 mkdirnode(last[..i]*"/", r, parent);
561 filenames = indices(filename_to_entry);
564 string _sprintf(int t)
566 return t=='O' && sprintf("_Tar(/* filename=%O */)", filename);
576 Stdio.File fd; // tar file object
577 //not used; it's present in tar->filename, though /jhs 2001-01-20
578 // string filename; // tar filename in parent filesystem
580 void create(void|_Tar _tar,
581 void|string _wd, void|string _root,
582 void|Filesystem.Base _parent)
586 sscanf(reverse(_wd), "%*[\\/]%s", wd);
591 sscanf(_root, "%*[/]%s", root);
595 string _sprintf(int t)
597 return t=='O' && sprintf("_TarFS(/* root=%O, wd=%O */)", root, wd);
600 Filesystem.Stat stat(string file, void|int lstat)
602 file = combine_path_unix(wd, file);
603 return tar->filename_to_entry[root+file];
606 array get_dir(void|string directory, void|string|array globs)
608 directory = combine_path_unix(wd, (directory||""), "");
610 array f = glob(root+directory+"?*", tar->filenames);
611 f -= glob(root+directory+"*/*", f); // stay here
616 Filesystem.Base cd(string directory)
618 Filesystem.Stat st = stat(directory);
620 if(st->isdir()) // stay in this filesystem
622 object new = _TarFS(tar, st->fullpath, root, parent);
625 return st->cd(); // try something else
628 Stdio.File open(string filename, string mode)
630 LOG_TAR("fS:open(%s)",filename);
631 filename = combine_path_unix(wd, filename);
632 return tar->filename_to_entry[root+filename] &&
633 tar->filename_to_entry[root+filename]->open(mode);
636 int access(string filename, string mode)
641 int rm(string filename)
645 void chmod(string filename, int|string mode)
649 void chown(string filename, int|object owner, int|object group)
657 void create(string filename)
659 object parent = steamfs();
660 object fd = parent->open(filename, "r");
661 _Tar tar = _Tar(fd, filename, this_object());
662 _TarFS::create(tar, "/", "", parent);
664 string _sprintf(int t) {
665 return t=='O' && sprintf("TarFile(/* root=%O, wd=%O */)", root, wd);
669 string get_identifier() { return "tar"; }