pack.dat revision 3f54fd611f536639ec30dd53c48e5ec1897cc7d9
ce5ff829db5f0bb4f16ad4de150eed4401d6acd5Christian Maeder#pragma prototyped
ce5ff829db5f0bb4f16ad4de150eed4401d6acd5Christian Maeder/*
ce5ff829db5f0bb4f16ad4de150eed4401d6acd5Christian Maeder * pack [-] [-f] file ...
97018cf5fa25b494adffd7e9b4e87320dae6bf47Christian Maeder *
ce5ff829db5f0bb4f16ad4de150eed4401d6acd5Christian Maeder * pack files using Huffman coding
eca29a7be76eb73944ec19b06eda3d6a9e6e543dChristian Maeder *
ce5ff829db5f0bb4f16ad4de150eed4401d6acd5Christian Maeder * David Korn
ce5ff829db5f0bb4f16ad4de150eed4401d6acd5Christian Maeder * AT&T Research
ce5ff829db5f0bb4f16ad4de150eed4401d6acd5Christian Maeder *
f3a94a197960e548ecd6520bb768cb0d547457bbChristian Maeder */
3f63b98c111e5e2bb2cf13795cf6e084a78b0a8dChristian Maeder
3f63b98c111e5e2bb2cf13795cf6e084a78b0a8dChristian Maederstatic const char usage[] =
3f63b98c111e5e2bb2cf13795cf6e084a78b0a8dChristian Maeder"[-?\n@(#)$Id: pack (AT&T Research) 2003-04-28 $\n]"
3f63b98c111e5e2bb2cf13795cf6e084a78b0a8dChristian MaederUSAGE_LICENSE
3f63b98c111e5e2bb2cf13795cf6e084a78b0a8dChristian Maeder"[+NAME?pack - pack files using Huffman coding]"
04dada28736b4a237745e92063d8bdd49a362debChristian Maeder"[+DESCRIPTION?\bpack\b attempts to store the specified files in a compressed "
3f63b98c111e5e2bb2cf13795cf6e084a78b0a8dChristian Maeder "form using static Huffman coding. Wherever possible each \afile\a "
15c12a3ac049a4528da05b1017b78145f308aeb0Christian Maeder "is replaced by a packed file named \afile\a\b.z\b with the same "
3f63b98c111e5e2bb2cf13795cf6e084a78b0a8dChristian Maeder "access modes, access and modified dates, and owner as those of "
f353be6210f67ffd4a46967bba749afc968cee52Christian Maeder "\afile\a. The \b-f\b option forces packing of \afile\a even when "
15c12a3ac049a4528da05b1017b78145f308aeb0Christian Maeder "there is no space benefit for packing the file.]"
15c12a3ac049a4528da05b1017b78145f308aeb0Christian Maeder "[+?If \bpack\b is successful, \afile\a will be removed. Packed files "
15c12a3ac049a4528da05b1017b78145f308aeb0Christian Maeder "can be restored to their original form using \bunpack\b or \bpcat\b.]"
3f63b98c111e5e2bb2cf13795cf6e084a78b0a8dChristian Maeder"[+?\bpack\b uses Huffman (minimum redundancy) codes on a byte-by-byte basis. "
15c12a3ac049a4528da05b1017b78145f308aeb0Christian Maeder "Ordinarily, for each file that is packed, a line is written to "
fc7df539e6d41b050161ed8f9ae6e444b1b5ab14Christian Maeder "standard output containing \afile\a\b.z\b and the percent "
3f63b98c111e5e2bb2cf13795cf6e084a78b0a8dChristian Maeder "compression. If the \b-v\b options is specified, or if the \b-\b "
3f63b98c111e5e2bb2cf13795cf6e084a78b0a8dChristian Maeder "argument is specified, an internal flag is set that causes the "
2b9022bd5dfb351d1d80f61680336effeccfa23eChristian Maeder "number of times each byte is used, its relative frequency, and the "
2b9022bd5dfb351d1d80f61680336effeccfa23eChristian Maeder "code for the byte to be written to the standard output. Additional "
d3bca27d616c5741d0b18776c8a0848ec31c87f4Christian Maeder "occurrences of \b-\b in place of name cause the internal flag to be "
2b9022bd5dfb351d1d80f61680336effeccfa23eChristian Maeder "set and reset.]"
f9a73de15ef09dbd6b391c7b1f695c79b4446fe2Christian Maeder"[+?No packing occurs if:]{"
d3bca27d616c5741d0b18776c8a0848ec31c87f4Christian Maeder "[+-?\afile\a appears to be already packed.]"
f9a73de15ef09dbd6b391c7b1f695c79b4446fe2Christian Maeder "[+-?\afile\a has links.]"
0df692ce8b9293499b2e1768458613a63e7b5cd0Christian Maeder "[+-?\afile\a is a directory.]"
aff01ee50b66032469c232e00c945d1fd4f57d1bChristian Maeder "[+-?\afile\a cannot be opened.]"
aff01ee50b66032469c232e00c945d1fd4f57d1bChristian Maeder "[+-?No disk storage blocks will be saved by packing unless \b-f\b "
aff01ee50b66032469c232e00c945d1fd4f57d1bChristian Maeder "is specified.]"
aff01ee50b66032469c232e00c945d1fd4f57d1bChristian Maeder "[+-?A file called \afile\a\b.z\b already exists.]"
aff01ee50b66032469c232e00c945d1fd4f57d1bChristian Maeder "[+-?The \b.z\b file cannot be created.]"
aff01ee50b66032469c232e00c945d1fd4f57d1bChristian Maeder "[+-?An I/O error occurred during processing.]"
aff01ee50b66032469c232e00c945d1fd4f57d1bChristian Maeder "}"
aff01ee50b66032469c232e00c945d1fd4f57d1bChristian Maeder"[f:force?Pack the file even if the packed size is larger than the original.]"
aff01ee50b66032469c232e00c945d1fd4f57d1bChristian Maeder"[v:verbose?Causes additional information to be written to standard ouput.]"
aff01ee50b66032469c232e00c945d1fd4f57d1bChristian Maeder"\n"
0df692ce8b9293499b2e1768458613a63e7b5cd0Christian Maeder"\nfile ...\n"
cc8b603388a7deb7fb8045db0341f550f8be5844Christian Maeder"\n"
0df692ce8b9293499b2e1768458613a63e7b5cd0Christian Maeder"[+EXIT STATUS]{"
0df692ce8b9293499b2e1768458613a63e7b5cd0Christian Maeder "[+0?All files packed successfully.]"
0df692ce8b9293499b2e1768458613a63e7b5cd0Christian Maeder "[+\an\a?\an\a files failed to pack, where \an\a is less than 125.]"
ce5ff829db5f0bb4f16ad4de150eed4401d6acd5Christian Maeder "[+125?125 or more files failed to pack.]"
f4741f6b7da52b5417899c8fcbe4349b920b006eChristian Maeder"}"
04dada28736b4a237745e92063d8bdd49a362debChristian Maeder"[+SEE ALSO?\bunpack\b(1), \bpcat\b(1), \bcompress\b(1), \bgzip\b(1)]"
f4741f6b7da52b5417899c8fcbe4349b920b006eChristian Maeder;
f4741f6b7da52b5417899c8fcbe4349b920b006eChristian Maeder
f4741f6b7da52b5417899c8fcbe4349b920b006eChristian Maeder
f4741f6b7da52b5417899c8fcbe4349b920b006eChristian Maeder#include "huffman.h"
f4741f6b7da52b5417899c8fcbe4349b920b006eChristian Maeder#include <error.h>
04dada28736b4a237745e92063d8bdd49a362debChristian Maeder#include <ls.h>
ce5ff829db5f0bb4f16ad4de150eed4401d6acd5Christian Maeder
cf5149eb4d0faef6272231879c04aa740f5abc2bChristian Maeder#define BLKSIZE 512
cf5149eb4d0faef6272231879c04aa740f5abc2bChristian Maeder#define block(size) (((size) + BLKSIZE-1) & ~(BLKSIZE-1))
cf5149eb4d0faef6272231879c04aa740f5abc2bChristian Maeder#define PERM(m) ((m)&(S_IRWXU|S_IRWXG|S_IRWXO))
cf5149eb4d0faef6272231879c04aa740f5abc2bChristian Maeder
cf5149eb4d0faef6272231879c04aa740f5abc2bChristian Maederstatic void vprint(Huff_t*, int);
cf5149eb4d0faef6272231879c04aa740f5abc2bChristian Maederstatic char *outname(char*);
cf5149eb4d0faef6272231879c04aa740f5abc2bChristian Maederstatic const char suffix[] = ".z";
f4741f6b7da52b5417899c8fcbe4349b920b006eChristian Maeder
f4741f6b7da52b5417899c8fcbe4349b920b006eChristian Maederint
2b9022bd5dfb351d1d80f61680336effeccfa23eChristian Maedermain(int argc, register char *argv[])
2b9022bd5dfb351d1d80f61680336effeccfa23eChristian Maeder{
f4741f6b7da52b5417899c8fcbe4349b920b006eChristian Maeder static char command[] = "pack";
f4741f6b7da52b5417899c8fcbe4349b920b006eChristian Maeder register Huff_t *hp;
f4741f6b7da52b5417899c8fcbe4349b920b006eChristian Maeder register char *infile,*outfile;
f4741f6b7da52b5417899c8fcbe4349b920b006eChristian Maeder Sfio_t *fpin,*fpout;
04dada28736b4a237745e92063d8bdd49a362debChristian Maeder int nfile=0, npack=0, force=0, verbose=0;
ce5ff829db5f0bb4f16ad4de150eed4401d6acd5Christian Maeder int out, deleted, dsize, n;
7dec34aee2b609b9535c48d060e0f7baf3536457Christian Maeder struct stat statb;
d17834302eaa101395b4b806cd73670fd864445fChristian Maeder
ccf3de3d66b521a260e5c22d335c64a48e3f0195Christian Maeder NOT_USED(argc);
ccf3de3d66b521a260e5c22d335c64a48e3f0195Christian Maeder error_info.id = command;
8338fbf3cfb9cf981261d893286f070bd9fa17efChristian Maeder while(n = optget(argv,usage)) switch(n)
ccf3de3d66b521a260e5c22d335c64a48e3f0195Christian Maeder {
aff01ee50b66032469c232e00c945d1fd4f57d1bChristian Maeder case 'f':
2dba752dedea77b7664117a46312d4a5ced6c979Christian Maeder force++;
2b9022bd5dfb351d1d80f61680336effeccfa23eChristian Maeder break;
2dba752dedea77b7664117a46312d4a5ced6c979Christian Maeder case 'v':
2dba752dedea77b7664117a46312d4a5ced6c979Christian Maeder verbose = !verbose;
2dba752dedea77b7664117a46312d4a5ced6c979Christian Maeder break;
7dec34aee2b609b9535c48d060e0f7baf3536457Christian Maeder case ':':
b645cf3dc1e449038ed291bbd11fcc6e02b2fc7fChristian Maeder error(2, opt_info.arg);
fc8c6570c7b4ee13f375eb607bed2290438573bfChristian Maeder break;
04dada28736b4a237745e92063d8bdd49a362debChristian Maeder case '?':
bf8221af2a4e579e1a616e3d472e9e8533cd8f8cChristian Maeder error(ERROR_usage(2), "%s", opt_info.arg);
e76e6a43f51438215737d6fc176c89da05bb86daChristian Maeder break;
bf8221af2a4e579e1a616e3d472e9e8533cd8f8cChristian Maeder }
7dec34aee2b609b9535c48d060e0f7baf3536457Christian Maeder argv += opt_info.index;
bf8221af2a4e579e1a616e3d472e9e8533cd8f8cChristian Maeder if(error_info.errors || !*argv)
e76e6a43f51438215737d6fc176c89da05bb86daChristian Maeder error(ERROR_usage(2), "%s", optusage((char*)0));
7dec34aee2b609b9535c48d060e0f7baf3536457Christian Maeder
bf8221af2a4e579e1a616e3d472e9e8533cd8f8cChristian Maeder while (infile = *argv++)
7dec34aee2b609b9535c48d060e0f7baf3536457Christian Maeder {
7dec34aee2b609b9535c48d060e0f7baf3536457Christian Maeder if(*infile == '-')
aff01ee50b66032469c232e00c945d1fd4f57d1bChristian Maeder {
e76e6a43f51438215737d6fc176c89da05bb86daChristian Maeder /* awful way to handle options, but preserves SVID */
3f63b98c111e5e2bb2cf13795cf6e084a78b0a8dChristian Maeder switch(infile[1])
0df692ce8b9293499b2e1768458613a63e7b5cd0Christian Maeder {
0df692ce8b9293499b2e1768458613a63e7b5cd0Christian Maeder case 'f':
0df692ce8b9293499b2e1768458613a63e7b5cd0Christian Maeder force++;
f626b1acbe874a48143a6f8d6246bf9d7a055ffbChristian Maeder continue;
f626b1acbe874a48143a6f8d6246bf9d7a055ffbChristian Maeder case 0:
15c12a3ac049a4528da05b1017b78145f308aeb0Christian Maeder verbose = !verbose;
15c12a3ac049a4528da05b1017b78145f308aeb0Christian Maeder continue;
cc8b603388a7deb7fb8045db0341f550f8be5844Christian Maeder }
f4741f6b7da52b5417899c8fcbe4349b920b006eChristian Maeder }
f4741f6b7da52b5417899c8fcbe4349b920b006eChristian Maeder nfile++;
f4741f6b7da52b5417899c8fcbe4349b920b006eChristian Maeder fpin = fpout = (Sfio_t*)0;
1d589334ba6b4a4cbfb35307a7a732261e77b0cdChristian Maeder hp = (Huff_t*)0;
15c12a3ac049a4528da05b1017b78145f308aeb0Christian Maeder deleted = 0;
15c12a3ac049a4528da05b1017b78145f308aeb0Christian Maeder if(!(outfile = outname(infile)))
15c12a3ac049a4528da05b1017b78145f308aeb0Christian Maeder continue;
f4741f6b7da52b5417899c8fcbe4349b920b006eChristian Maeder if (!(fpin=sfopen((Sfio_t*)0,infile,"r")))
f4741f6b7da52b5417899c8fcbe4349b920b006eChristian Maeder error(ERROR_system(0), "%s: cannot open", infile);
f4741f6b7da52b5417899c8fcbe4349b920b006eChristian Maeder else if(fstat(sffileno(fpin),&statb) < 0)
cc8b603388a7deb7fb8045db0341f550f8be5844Christian Maeder error(ERROR_system(0), "%s: cannot stat", infile);
cc8b603388a7deb7fb8045db0341f550f8be5844Christian Maeder else if(S_ISDIR(statb.st_mode))
15c12a3ac049a4528da05b1017b78145f308aeb0Christian Maeder error(2, "%s: cannot pack a directory", infile);
1d589334ba6b4a4cbfb35307a7a732261e77b0cdChristian Maeder else if(statb.st_nlink > 1)
cc8b603388a7deb7fb8045db0341f550f8be5844Christian Maeder error(2, "%s: has links", infile);
cc8b603388a7deb7fb8045db0341f550f8be5844Christian Maeder else if(statb.st_size ==0)
cc8b603388a7deb7fb8045db0341f550f8be5844Christian Maeder error(2, "%s: cannot pack a zero length file", infile);
f626b1acbe874a48143a6f8d6246bf9d7a055ffbChristian Maeder else if(access(outfile,F_OK) ==0)
cdaff0507c1b7240e2660dbb311f9c4646a6d14aChristian Maeder error(ERROR_system(0), "%s: already exists", outfile);
cdaff0507c1b7240e2660dbb311f9c4646a6d14aChristian Maeder else if(((out=open(outfile,O_WRONLY|O_CREAT|O_TRUNC|O_BINARY,PERM(statb.st_mode))) < 0) ||
cdaff0507c1b7240e2660dbb311f9c4646a6d14aChristian Maeder !(fpout = sfnew((Sfio_t*)0,(char*)0,SF_UNBOUND,out,SF_WRITE)))
cdaff0507c1b7240e2660dbb311f9c4646a6d14aChristian Maeder error(ERROR_system(0), "%s: cannot create", outfile);
e92ae8b45c138b6cf7db8b69e2d099d7f62f24f0Christian Maeder else if((deleted++,chmod(outfile,statb.st_mode)) < 0)
d3bca27d616c5741d0b18776c8a0848ec31c87f4Christian Maeder error(ERROR_system(0), "%s: cannot change mode to %o",outfile,statb.st_mode);
f9a73de15ef09dbd6b391c7b1f695c79b4446fe2Christian Maeder else
ac19f8695aa1b2d2d1cd1319da2530edd8f46a96Christian Maeder {
ac19f8695aa1b2d2d1cd1319da2530edd8f46a96Christian Maeder chown(outfile,statb.st_uid,statb.st_gid);
aff01ee50b66032469c232e00c945d1fd4f57d1bChristian Maeder if(!(hp = huffinit(fpin,(Sfoff_t)-1)))
aff01ee50b66032469c232e00c945d1fd4f57d1bChristian Maeder error(2, "%s: read error", infile);
aff01ee50b66032469c232e00c945d1fd4f57d1bChristian Maeder else if(sfseek(fpin,(Sfoff_t)0,0) < 0)
15c12a3ac049a4528da05b1017b78145f308aeb0Christian Maeder error(ERROR_system(0),"%s: seek error", infile);
15c12a3ac049a4528da05b1017b78145f308aeb0Christian Maeder else if((dsize = huffputhdr(hp,fpout)) < 0)
15c12a3ac049a4528da05b1017b78145f308aeb0Christian Maeder error(2, "%s: write error", infile);
15c12a3ac049a4528da05b1017b78145f308aeb0Christian Maeder else if(!force && block(huffisize(hp)) <= block(huffosize(hp)+dsize))
15c12a3ac049a4528da05b1017b78145f308aeb0Christian Maeder error(2, "%s:no savings - file unchanged", infile);
15c12a3ac049a4528da05b1017b78145f308aeb0Christian Maeder else if(huffencode(hp,fpin,fpout,SF_UNBOUND)<0)
8338fbf3cfb9cf981261d893286f070bd9fa17efChristian Maeder error(2, "%s: read error", infile);
355a453397fa18360bbaeb0f1068ad6a299a1dffChristian Maeder else
0f67ca7b0c738a28f6688ba6e96d44d7c14af611Christian Maeder {
0f67ca7b0c738a28f6688ba6e96d44d7c14af611Christian Maeder double diff;
88ece6e49930670e8fd3ee79c89a2e918d2fbd0cChristian Maeder if(remove(infile) < 0)
0f67ca7b0c738a28f6688ba6e96d44d7c14af611Christian Maeder error(ERROR_system(0), "%s: cannot remove", infile);
88ece6e49930670e8fd3ee79c89a2e918d2fbd0cChristian Maeder diff = huffisize(hp) - (dsize+huffosize(hp));
8338fbf3cfb9cf981261d893286f070bd9fa17efChristian Maeder sfprintf(sfstdout,"%s: %s : %.1f%% Compression\n",command,
8338fbf3cfb9cf981261d893286f070bd9fa17efChristian Maeder infile,(100*diff)/((double)huffisize(hp)));
355a453397fa18360bbaeb0f1068ad6a299a1dffChristian Maeder if(verbose)
0f67ca7b0c738a28f6688ba6e96d44d7c14af611Christian Maeder vprint(hp,dsize);
355a453397fa18360bbaeb0f1068ad6a299a1dffChristian Maeder npack++;
88ece6e49930670e8fd3ee79c89a2e918d2fbd0cChristian Maeder deleted = 0;
355a453397fa18360bbaeb0f1068ad6a299a1dffChristian Maeder }
0f67ca7b0c738a28f6688ba6e96d44d7c14af611Christian Maeder }
0f67ca7b0c738a28f6688ba6e96d44d7c14af611Christian Maeder if(hp)
15c12a3ac049a4528da05b1017b78145f308aeb0Christian Maeder huffend(hp);
15c12a3ac049a4528da05b1017b78145f308aeb0Christian Maeder if(fpin)
15c12a3ac049a4528da05b1017b78145f308aeb0Christian Maeder sfclose(fpin);
15c12a3ac049a4528da05b1017b78145f308aeb0Christian Maeder if(fpout)
15c12a3ac049a4528da05b1017b78145f308aeb0Christian Maeder sfclose(fpout);
15c12a3ac049a4528da05b1017b78145f308aeb0Christian Maeder if(deleted)
15c12a3ac049a4528da05b1017b78145f308aeb0Christian Maeder remove(outfile);
15c12a3ac049a4528da05b1017b78145f308aeb0Christian Maeder if(outfile)
0f67ca7b0c738a28f6688ba6e96d44d7c14af611Christian Maeder free(outfile);
20fe556546c9277cf017931a07d90add61f199d9Christian Maeder }
20fe556546c9277cf017931a07d90add61f199d9Christian Maeder nfile -= npack;
b645cf3dc1e449038ed291bbd11fcc6e02b2fc7fChristian Maeder if(nfile > 125)
aff01ee50b66032469c232e00c945d1fd4f57d1bChristian Maeder nfile = 125;
b645cf3dc1e449038ed291bbd11fcc6e02b2fc7fChristian Maeder exit(nfile);
b645cf3dc1e449038ed291bbd11fcc6e02b2fc7fChristian Maeder}
20fe556546c9277cf017931a07d90add61f199d9Christian Maeder
20fe556546c9277cf017931a07d90add61f199d9Christian Maeder
aff01ee50b66032469c232e00c945d1fd4f57d1bChristian Maederstatic char *outname(char *infile)
20fe556546c9277cf017931a07d90add61f199d9Christian Maeder{
20fe556546c9277cf017931a07d90add61f199d9Christian Maeder register int n = strlen(infile);
aff01ee50b66032469c232e00c945d1fd4f57d1bChristian Maeder register int sufflen = strlen(suffix);
b645cf3dc1e449038ed291bbd11fcc6e02b2fc7fChristian Maeder register char *cp;
3f63b98c111e5e2bb2cf13795cf6e084a78b0a8dChristian Maeder if(streq(suffix,infile+n-sufflen))
f626b1acbe874a48143a6f8d6246bf9d7a055ffbChristian Maeder {
f626b1acbe874a48143a6f8d6246bf9d7a055ffbChristian Maeder error(ERROR_exit(1), "%s: already packed", infile);
15c12a3ac049a4528da05b1017b78145f308aeb0Christian Maeder return((char*)0);
aff01ee50b66032469c232e00c945d1fd4f57d1bChristian Maeder }
aff01ee50b66032469c232e00c945d1fd4f57d1bChristian Maeder if(cp = (char*)malloc(n+sufflen+1))
aff01ee50b66032469c232e00c945d1fd4f57d1bChristian Maeder {
aff01ee50b66032469c232e00c945d1fd4f57d1bChristian Maeder strcpy(cp,infile);
15c12a3ac049a4528da05b1017b78145f308aeb0Christian Maeder strcat(cp+n,suffix);
aff01ee50b66032469c232e00c945d1fd4f57d1bChristian Maeder }
aff01ee50b66032469c232e00c945d1fd4f57d1bChristian Maeder return(cp);
aff01ee50b66032469c232e00c945d1fd4f57d1bChristian Maeder}
aff01ee50b66032469c232e00c945d1fd4f57d1bChristian Maeder
3f63b98c111e5e2bb2cf13795cf6e084a78b0a8dChristian Maederstatic void vprint(Huff_t *hp,int dsize)
f626b1acbe874a48143a6f8d6246bf9d7a055ffbChristian Maeder{
f626b1acbe874a48143a6f8d6246bf9d7a055ffbChristian Maeder sfprintf(sfstdout," from %lld to %lld bytes\n", huffisize(hp), huffosize(hp));
15c12a3ac049a4528da05b1017b78145f308aeb0Christian Maeder sfprintf(sfstdout," Huffman tree has %d levels below root\n", hp->maxlev);
15c12a3ac049a4528da05b1017b78145f308aeb0Christian Maeder sfprintf(sfstdout," %d distinct bytes in input\n", hp->nchars);
15c12a3ac049a4528da05b1017b78145f308aeb0Christian Maeder sfprintf(sfstdout," dictionary overhead = %ld bytes\n", dsize);
15c12a3ac049a4528da05b1017b78145f308aeb0Christian Maeder sfprintf(sfstdout," effective entropy = %.2f bits/byte\n",
15c12a3ac049a4528da05b1017b78145f308aeb0Christian Maeder ((double)(huffosize(hp))/(double)huffisize(hp))*CHAR_BIT);
aff01ee50b66032469c232e00c945d1fd4f57d1bChristian Maeder sfprintf(sfstdout," asymptotic entropy = %.2f bits/byte\n",
aff01ee50b66032469c232e00c945d1fd4f57d1bChristian Maeder ((double)(huffosize(hp)-dsize)/(double)huffisize(hp))*CHAR_BIT);
20fe556546c9277cf017931a07d90add61f199d9Christian Maeder}
278de8173a1b7b7f6299f7c804135d14560176daChristian Maeder