|
Michal Marek |
c56654 |
#!/usr/bin/perl
|
|
Michal Marek |
c56654 |
|
|
Michal Marek |
c56654 |
#############################################################################
|
|
Michal Marek |
c56654 |
# Copyright (c) 2012 Novell, Inc.
|
|
Michal Marek |
c56654 |
# All Rights Reserved.
|
|
Michal Marek |
c56654 |
#
|
|
Michal Marek |
c56654 |
# This program is free software; you can redistribute it and/or
|
|
Michal Marek |
c56654 |
# modify it under the terms of version 2 of the GNU General Public License as
|
|
Michal Marek |
c56654 |
# published by the Free Software Foundation.
|
|
Michal Marek |
c56654 |
#
|
|
Michal Marek |
c56654 |
# This program is distributed in the hope that it will be useful,
|
|
Michal Marek |
c56654 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
Michal Marek |
c56654 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
Michal Marek |
c56654 |
# GNU General Public License for more details.
|
|
Michal Marek |
c56654 |
#
|
|
Michal Marek |
c56654 |
# You should have received a copy of the GNU General Public License
|
|
Michal Marek |
c56654 |
# along with this program; if not, contact Novell, Inc.
|
|
Michal Marek |
c56654 |
#
|
|
Michal Marek |
c56654 |
# To contact Novell about this file by physical or electronic mail,
|
|
Michal Marek |
c56654 |
# you may find current contact information at www.novell.com
|
|
Michal Marek |
c56654 |
#############################################################################
|
|
Michal Marek |
c56654 |
|
|
Michal Marek |
c56654 |
# A very limited tar writer. Creates archives in the ustar format and
|
|
Michal Marek |
c56654 |
# makes sure that identical content results in identical archives.
|
|
Michal Marek |
c56654 |
|
|
Michal Marek |
c56654 |
use strict;
|
|
Michal Marek |
c56654 |
use warnings;
|
|
Michal Marek |
c56654 |
|
|
Michal Marek |
c56654 |
my $USAGE = "Usage: $0 --mtime=<decimal timestamp> [--exclude=<pattern] [-C directory] file|directory ... >archive.tar\n";
|
|
Michal Marek |
c56654 |
|
|
Michal Marek |
c56654 |
use Getopt::Long;
|
|
Michal Marek |
c56654 |
use File::Find;
|
|
Michal Marek |
c56654 |
use File::Copy;
|
|
Michal Marek |
c56654 |
use Fcntl ':mode';
|
|
Michal Marek |
c56654 |
|
|
Michal Marek |
f53c5d |
my $mtime;
|
|
Michal Marek |
df842b |
my $disable_paxheaders;
|
|
Michal Marek |
c56654 |
my $chdir;
|
|
Michal Marek |
8cb5b4 |
my $files_from;
|
|
Michal Marek |
c56654 |
my @exclude = ();
|
|
Michal Marek |
c56654 |
GetOptions(
|
|
Michal Marek |
c56654 |
"t|mtime=i" => \$mtime,
|
|
Michal Marek |
c56654 |
"exclude=s" => \@exclude,
|
|
Michal Marek |
df842b |
"no-paxheaders" => \$disable_paxheaders,
|
|
Michal Marek |
c56654 |
"C=s" => \$chdir,
|
|
Michal Marek |
8cb5b4 |
"T|files-from=s" => \$files_from,
|
|
Michal Marek |
c56654 |
) or die($USAGE);
|
|
Michal Marek |
c56654 |
|
|
Michal Marek |
f53c5d |
if (!defined($mtime)) {
|
|
Michal Marek |
f53c5d |
warn "$0: --mtime not specified, using 2000-01-01\n";
|
|
Michal Marek |
f53c5d |
$mtime = 946681200;
|
|
Michal Marek |
f53c5d |
}
|
|
Michal Marek |
8cb5b4 |
my @args;
|
|
Michal Marek |
8cb5b4 |
if ($files_from) {
|
|
Michal Marek |
8cb5b4 |
if ($files_from eq '-') {
|
|
Michal Marek |
8cb5b4 |
@args = <>;
|
|
Michal Marek |
8cb5b4 |
} else {
|
|
Michal Marek |
8cb5b4 |
open(my $fh, '<', $files_from) or die "$files_from: $!\n";
|
|
Michal Marek |
8cb5b4 |
@args = <$fh>;
|
|
Michal Marek |
8cb5b4 |
close($fh);
|
|
Michal Marek |
8cb5b4 |
}
|
|
Michal Marek |
8cb5b4 |
chomp(@args);
|
|
Michal Marek |
8cb5b4 |
} else {
|
|
Michal Marek |
8cb5b4 |
@args = @ARGV;
|
|
Michal Marek |
8cb5b4 |
}
|
|
Michal Marek |
8cb5b4 |
if (!@args) {
|
|
Michal Marek |
8cb5b4 |
print STDERR "No arguments given\n";
|
|
Michal Marek |
8cb5b4 |
die($USAGE);
|
|
Michal Marek |
8cb5b4 |
}
|
|
Michal Marek |
c56654 |
|
|
Michal Marek |
c56654 |
chdir($chdir) if $chdir;
|
|
Michal Marek |
c56654 |
my @files;
|
|
Michal Marek |
c56654 |
|
|
Michal Marek |
a5ef8b |
s/\./\\./g for @exclude;
|
|
Michal Marek |
c56654 |
s/\*/.*/g for @exclude;
|
|
Michal Marek |
c56654 |
s/\?/./g for @exclude;
|
|
Michal Marek |
a5ef8b |
s/.*/^$&\$/ for @exclude;
|
|
Michal Marek |
c56654 |
sub wanted {
|
|
Michal Marek |
c56654 |
for my $pattern (@exclude) {
|
|
Michal Marek |
c56654 |
return if $_ =~ $pattern;
|
|
Michal Marek |
c56654 |
}
|
|
Michal Marek |
c56654 |
push(@files, $File::Find::name);
|
|
Michal Marek |
c56654 |
}
|
|
Michal Marek |
c56654 |
|
|
Michal Marek |
8cb5b4 |
for my $file (@args) {
|
|
Michal Marek |
c56654 |
if (-d $file) {
|
|
Michal Marek |
c56654 |
find(\&wanted, $file);
|
|
Michal Marek |
c56654 |
} else {
|
|
Michal Marek |
c56654 |
push(@files, $file);
|
|
Michal Marek |
c56654 |
}
|
|
Michal Marek |
c56654 |
}
|
|
Michal Marek |
c56654 |
$| = 1;
|
|
Michal Marek |
c56654 |
my $total_size = 0;
|
|
Michal Marek |
7901ad |
|
|
Michal Marek |
7901ad |
# Tar header format description
|
|
Michal Marek |
7901ad |
# filed => <width><type>
|
|
Michal Marek |
7901ad |
# where type is either _s_string or _o_ctal
|
|
Michal Marek |
7901ad |
my @header_layout = (
|
|
Michal Marek |
7901ad |
name => "100s",
|
|
Michal Marek |
7901ad |
mode => "8o",
|
|
Michal Marek |
7901ad |
uid => "8o",
|
|
Michal Marek |
7901ad |
gid => "8o",
|
|
Michal Marek |
7901ad |
size => "12o",
|
|
Michal Marek |
7901ad |
mtime => "12o",
|
|
Michal Marek |
7901ad |
csum => "8s",
|
|
Michal Marek |
7901ad |
typeflag => "1s",
|
|
Michal Marek |
7901ad |
linktarget => "100s",
|
|
Michal Marek |
7901ad |
magic => "6s",
|
|
Michal Marek |
7901ad |
version => "2s",
|
|
Michal Marek |
7901ad |
user => "32s",
|
|
Michal Marek |
7901ad |
group => "32s",
|
|
Michal Marek |
7901ad |
devmajor => "8o",
|
|
Michal Marek |
7901ad |
devminor => "8o",
|
|
Michal Marek |
7901ad |
prefix => "155s"
|
|
Michal Marek |
7901ad |
);
|
|
Michal Marek |
7901ad |
|
|
Michal Marek |
c56654 |
for my $file (sort(@files)) {
|
|
Michal Marek |
7901ad |
my %header = ();
|
|
Michal Marek |
c56654 |
|
|
Michal Marek |
7901ad |
$header{name} = $file;
|
|
Michal Marek |
392bcc |
my $need_paxheader = 0;
|
|
Michal Marek |
c56654 |
if (length($file) > 100) {
|
|
Michal Marek |
7901ad |
($header{prefix} = $file) =~ s:/[^/]*$::;
|
|
Michal Marek |
7901ad |
($header{name} = $file) =~ s:^.*/::;
|
|
Michal Marek |
7901ad |
if (length($header{name}) > 100 ||
|
|
Michal Marek |
7901ad |
length($header{prefix}) > 155) {
|
|
Michal Marek |
df842b |
if ($disable_paxheaders) {
|
|
Michal Marek |
df842b |
die "Too long filenames are impossible with --no-paxheaders: $file\n";
|
|
Michal Marek |
df842b |
}
|
|
Michal Marek |
392bcc |
$header{name} = substr($header{name}, 0, 100);
|
|
Michal Marek |
392bcc |
$header{prefix} = substr($header{prefix}, 0, 155);
|
|
Michal Marek |
392bcc |
$need_paxheader = 1;
|
|
Michal Marek |
c56654 |
}
|
|
Michal Marek |
c56654 |
}
|
|
Michal Marek |
c56654 |
my @stat = lstat($file) or die "$file: $!\n";
|
|
Michal Marek |
c56654 |
my $mode = $stat[2];
|
|
Michal Marek |
7901ad |
$header{mode} = ($mode & 0111) ? 0755 : 0644;
|
|
Michal Marek |
392bcc |
# 65534:65534 is commonly used for nobody:nobody
|
|
Michal Marek |
392bcc |
$header{uid} = 65534;
|
|
Michal Marek |
392bcc |
$header{gid} = 65533;
|
|
Michal Marek |
392bcc |
$header{user} = "nobody";
|
|
Michal Marek |
392bcc |
$header{group} = "nobody";
|
|
Michal Marek |
392bcc |
|
|
Michal Marek |
392bcc |
$header{mtime} = $mtime;
|
|
Michal Marek |
392bcc |
|
|
Michal Marek |
392bcc |
if ($need_paxheader) {
|
|
Michal Marek |
392bcc |
my $record = "path=$file\n";
|
|
Michal Marek |
392bcc |
# length means length of the whole record, including the
|
|
Michal Marek |
392bcc |
# length number
|
|
Michal Marek |
392bcc |
my $length = length($record) + 2;
|
|
Michal Marek |
392bcc |
while ($length < length(sprintf("%d %s", $length, $record))) {
|
|
Michal Marek |
392bcc |
$length++;
|
|
Michal Marek |
392bcc |
}
|
|
Michal Marek |
392bcc |
$record = sprintf("%d %s", $length, $record);
|
|
Michal Marek |
392bcc |
$header{typeflag} = "x";
|
|
Michal Marek |
392bcc |
$header{size} = length($record);
|
|
Michal Marek |
392bcc |
print gen_header(\%header);
|
|
Michal Marek |
392bcc |
$total_size += 512;
|
|
Michal Marek |
392bcc |
print $record;
|
|
Michal Marek |
392bcc |
# padding to 512 byte boundary
|
|
Michal Marek |
392bcc |
my $pad = pad_tail($header{size}, 512);
|
|
Michal Marek |
392bcc |
print $pad;
|
|
Michal Marek |
392bcc |
$total_size += length($pad);
|
|
Michal Marek |
392bcc |
}
|
|
Michal Marek |
c56654 |
if (S_ISREG($mode)) {
|
|
Michal Marek |
7901ad |
$header{size} = $stat[7];
|
|
Michal Marek |
7901ad |
$header{typeflag} = "0";
|
|
Michal Marek |
c56654 |
} elsif (S_ISLNK($mode)) {
|
|
Michal Marek |
392bcc |
$header{size} = 0;
|
|
Michal Marek |
7901ad |
$header{linktarget} = readlink($file);
|
|
Michal Marek |
7901ad |
$header{typeflag} = "2";
|
|
Michal Marek |
c56654 |
} elsif (S_ISDIR($mode)) {
|
|
Michal Marek |
392bcc |
$header{size} = 0;
|
|
Michal Marek |
7901ad |
$header{typeflag} = "5";
|
|
Michal Marek |
c56654 |
} else {
|
|
Michal Marek |
c56654 |
die "Only regular files, symlinks and directories supported: $file\n";
|
|
Michal Marek |
c56654 |
}
|
|
Michal Marek |
7901ad |
print gen_header(\%header);
|
|
Michal Marek |
c56654 |
$total_size += 512;
|
|
Michal Marek |
c56654 |
next unless S_ISREG($mode);
|
|
Michal Marek |
c56654 |
|
|
Michal Marek |
c56654 |
# PAYLOAD
|
|
Michal Marek |
c56654 |
copy($file, \*STDOUT);
|
|
Michal Marek |
7901ad |
$total_size += $header{size};
|
|
Michal Marek |
c56654 |
# padding to 512 byte boundary
|
|
Michal Marek |
392bcc |
my $pad = pad_tail($header{size}, 512);
|
|
Michal Marek |
392bcc |
print $pad;
|
|
Michal Marek |
392bcc |
$total_size += length($pad);
|
|
Michal Marek |
c56654 |
}
|
|
Michal Marek |
c56654 |
# end of archive marker
|
|
Michal Marek |
c56654 |
print pad("", 1024);
|
|
Michal Marek |
c56654 |
$total_size += 1024;
|
|
Michal Marek |
c56654 |
# pad to 10240 boundary
|
|
Michal Marek |
392bcc |
print pad_tail($total_size, 10240);
|
|
Michal Marek |
c56654 |
exit;
|
|
Michal Marek |
c56654 |
|
|
Michal Marek |
7901ad |
sub gen_header {
|
|
Michal Marek |
7901ad |
my $header = shift;
|
|
Michal Marek |
7901ad |
|
|
Michal Marek |
7901ad |
$header->{magic} = "ustar";
|
|
Michal Marek |
7901ad |
$header->{version} = "00";
|
|
Michal Marek |
7901ad |
|
|
Michal Marek |
7901ad |
my $res = "";
|
|
Michal Marek |
7901ad |
my $csum_pos = 0;
|
|
Michal Marek |
7901ad |
for (my $i = 0; $i < scalar(@header_layout); $i += 2) {
|
|
Michal Marek |
7901ad |
my $field = $header_layout[$i];
|
|
Michal Marek |
7901ad |
my $fmt = $header_layout[$i + 1];
|
|
Michal Marek |
7901ad |
(my $length = $fmt) =~ s/.$//;
|
|
Michal Marek |
7901ad |
(my $type = $fmt) =~ s/^\d*//;
|
|
Michal Marek |
7901ad |
my $value = $header->{$field};
|
|
Michal Marek |
7901ad |
|
|
Michal Marek |
7901ad |
# special case
|
|
Michal Marek |
7901ad |
if ($field eq "csum") {
|
|
Michal Marek |
7901ad |
$csum_pos = length($res);
|
|
Michal Marek |
7901ad |
$res .= " " x 8;
|
|
Michal Marek |
7901ad |
next;
|
|
Michal Marek |
7901ad |
}
|
|
Michal Marek |
7901ad |
if ($type eq "s") {
|
|
Michal Marek |
7901ad |
$value = "" unless defined($value);
|
|
Michal Marek |
7901ad |
$res .= pad($value, $length);
|
|
Michal Marek |
7901ad |
} elsif ($type eq "o") {
|
|
Michal Marek |
7901ad |
$value = 0 unless defined($value);
|
|
Michal Marek |
7901ad |
$res .= pad_octal($value, $length);
|
|
Michal Marek |
7901ad |
} else {
|
|
Michal Marek |
7901ad |
die "Invalid format for $field: $fmt";
|
|
Michal Marek |
7901ad |
}
|
|
Michal Marek |
7901ad |
}
|
|
Michal Marek |
7901ad |
# add the checksum, using the "%06o\0 " format like GNU tar
|
|
Michal Marek |
7901ad |
my $csum = header_checksum($res);
|
|
Michal Marek |
7901ad |
substr($res, $csum_pos, 7) = pad_octal($csum, 7);
|
|
Michal Marek |
7901ad |
|
|
Michal Marek |
7901ad |
# padding to 512 byte boundary
|
|
Michal Marek |
7901ad |
$res .= pad("", 12);
|
|
Michal Marek |
7901ad |
die "error: header is not 512 bytes long" unless length($res) == 512;
|
|
Michal Marek |
7901ad |
|
|
Michal Marek |
7901ad |
return $res;
|
|
Michal Marek |
7901ad |
}
|
|
Michal Marek |
7901ad |
|
|
Michal Marek |
c56654 |
sub pad {
|
|
Michal Marek |
c56654 |
my ($string, $length) = @_;
|
|
Michal Marek |
c56654 |
|
|
Michal Marek |
c56654 |
my $pad = $length - length($string);
|
|
Michal Marek |
c56654 |
if ($pad < 0) {
|
|
Michal Marek |
c56654 |
die "Field over $length bytes: $string\n";
|
|
Michal Marek |
c56654 |
}
|
|
Michal Marek |
c56654 |
return $string . "\0" x $pad;
|
|
Michal Marek |
c56654 |
}
|
|
Michal Marek |
c56654 |
|
|
Michal Marek |
c56654 |
sub pad_octal {
|
|
Michal Marek |
c56654 |
my ($num, $length) = @_;
|
|
Michal Marek |
c56654 |
|
|
Michal Marek |
c56654 |
$length--;
|
|
Michal Marek |
c56654 |
return sprintf("%0${length}o\0", $num);
|
|
Michal Marek |
c56654 |
}
|
|
Michal Marek |
c56654 |
|
|
Michal Marek |
392bcc |
# after $size bytes written, padd to the neares $boundary
|
|
Michal Marek |
392bcc |
sub pad_tail {
|
|
Michal Marek |
392bcc |
my ($size, $boundary) = @_;
|
|
Michal Marek |
392bcc |
|
|
Michal Marek |
392bcc |
return "" unless $size % $boundary;
|
|
Michal Marek |
392bcc |
my $padding = $boundary - $size % $boundary;
|
|
Michal Marek |
392bcc |
return pad("", $padding);
|
|
Michal Marek |
392bcc |
}
|
|
Michal Marek |
392bcc |
|
|
Michal Marek |
c56654 |
sub header_checksum {
|
|
Michal Marek |
c56654 |
my $header = shift;
|
|
Michal Marek |
c56654 |
|
|
Michal Marek |
c56654 |
my $res = 0;
|
|
Michal Marek |
c56654 |
for (my $i = 0; $i < length($header); $i++) {
|
|
Michal Marek |
c56654 |
$res += ord(substr($header, $i, 1))
|
|
Michal Marek |
c56654 |
}
|
|
Michal Marek |
c56654 |
return $res;
|
|
Michal Marek |
c56654 |
}
|
|
Michal Marek |
c56654 |
|