mktar: Use `wc` instead of `du` in summary message
[sunny256-utils.git] / src / smsum / smsum
blob38aaf4cb142795c57a555e2a3073724899e2f425
1 #!/usr/bin/env perl
3 #==============================================================================
4 # smsum
5 # File ID: ac639b66-f942-11dd-8b88-0001805bf4b1
7 # Generates smsum hashes
9 # Character set: UTF-8
10 # ©opyleft 2008– Øyvind A. Holm <sunny@sunbase.org>
11 # License: GNU General Public License version 2 or later, see end of file for
12 # legal stuff.
13 #==============================================================================
15 use strict;
16 use warnings;
17 use Getopt::Long;
18 use Digest::MD5;
19 use Digest::SHA;
21 local $| = 1;
23 our %Opt = (
25 'help' => 0,
26 'quiet' => 0,
27 'verbose' => 0,
28 'version' => 0,
29 'with-mtime' => 0,
33 our $progname = $0;
34 $progname =~ s/^.*\/(.*?)$/$1/;
35 our $VERSION = '0.1.2';
37 Getopt::Long::Configure('bundling');
38 GetOptions(
40 'help|h' => \$Opt{'help'},
41 'quiet|q+' => \$Opt{'quiet'},
42 'verbose|v+' => \$Opt{'verbose'},
43 'version' => \$Opt{'version'},
44 'with-mtime|m' => \$Opt{'with-mtime'},
46 ) || die("$progname: Option error. Use -h for help.\n");
48 $Opt{'verbose'} -= $Opt{'quiet'};
49 $Opt{'help'} && usage(0);
50 if ($Opt{'version'}) {
51 print_version();
52 exit(0);
55 exit(main());
57 sub main {
58 my $Retval = 0;
59 my $result;
61 if ($#ARGV >= 0) {
62 for (@ARGV) {
63 $result = process_file($_);
64 if (defined($result)) {
65 print($result);
66 } else {
67 $Retval = 1;
70 } else {
71 $result = process_file("-");
72 if (defined($result)) {
73 print($result);
74 } else {
75 $Retval = 1;
79 return $Retval;
82 sub process_file {
83 my $Filename = shift;
84 my $Retval = "";
85 my %Sum = ();
86 msg(3, "process_file('$Filename')");
87 my $use_stdin = ($Filename eq "-") ? 1 : 0;
88 my @stat_array = ();
89 my $displayed_filename = $Filename;
90 my $tmpdir = (defined($ENV{'TMPDIR'}) && length($ENV{'TMPDIR'}))
91 ? $ENV{'TMPDIR'} : "/tmp";
92 my $tmpfile = "$tmpdir/$progname." . time . ".$$.tmp";
93 if ($use_stdin) {
94 open(my $tmpfp, '>', $tmpfile) or
95 die("$progname: $tmpfile: Cannot create tempfile: $!\n");
96 unless (print($tmpfp join('', <STDIN>))) {
97 my $errmsg = $!;
98 close($tmpfp);
99 unlink($tmpfile);
100 die("$progname: $tmpfile: Cannot write to tempfile: $errmsg\n");
102 close($tmpfp);
103 $Filename = $tmpfile;
105 if (@stat_array = stat($Filename)) {
106 my ($Dev, $Inode, $Mode, $Nlinks, $Uid, $Gid, $Rdev, $Size,
107 $Atime, $Mtime, $Ctime, $Blksize, $Blocks) = @stat_array;
108 if ($use_stdin || -f $Filename) {
109 local *FP;
110 if ($use_stdin || open(FP, "<", $Filename)) {
111 msg(2, sprintf("Reading %s...", safe_tab($Filename)));
112 $Sum{'sha1'} = sha1sum($Filename);
113 $Sum{'md5'} = md5sum($Filename);
114 $Retval =
115 $Sum{'sha1'} . "-" .
116 $Sum{'md5'} . "-" .
117 $Size . (
118 $use_stdin && !$Opt{'with-mtime'}
119 ? ""
120 : "\t" . safe_tab($displayed_filename) . (
121 $Opt{'with-mtime'}
122 ? "\t" . sec_to_string($Mtime)
123 : ""
126 . "\n";
127 } else {
128 warn("$progname: $Filename: Cannot read file\n");
129 $Retval = undef;
131 } else {
132 msg(1, "$Filename: Ignoring non-file");
134 } else {
135 warn("$progname: $Filename: Cannot read file status\n");
136 $Retval = undef;
138 unlink($tmpfile);
139 return($Retval);
142 sub sha1sum {
143 my $file = shift;
144 my $sha1 = Digest::SHA->new(1);
146 open(FP, "<", "$file") or return undef;
147 while (my $Curr = <FP>) {
148 $sha1->add($Curr);
150 return $sha1->hexdigest;
153 sub md5sum {
154 my $file = shift;
155 my $md5 = Digest::MD5->new;
157 open(FP, "<", "$file") or return undef;
158 while (my $Curr = <FP>) {
159 $md5->add($Curr);
161 return $md5->hexdigest;
164 sub safe_tab {
165 my $Str = shift;
166 $Str =~ s/\\/\\\\/gs;
167 $Str =~ s/\n/\\n/gs;
168 $Str =~ s/\r/\\r/gs;
169 $Str =~ s/\t/\\t/gs;
170 return($Str);
173 sub sec_to_string {
174 # Convert seconds since 1970 to "yyyy-mm-ddThh:mm:ssZ"
175 my ($Seconds) = shift;
177 my @TA = gmtime($Seconds);
178 my($DateString) = sprintf("%04u-%02u-%02uT%02u:%02u:%02uZ",
179 $TA[5]+1900, $TA[4]+1, $TA[3],
180 $TA[2], $TA[1], $TA[0]);
181 return($DateString);
184 sub print_version {
185 # Print program version
186 print("$progname $VERSION\n");
187 return;
190 sub usage {
191 # Send the help message to stdout
192 my $Retval = shift;
194 if ($Opt{'verbose'}) {
195 print("\n");
196 print_version();
198 print(<<"END");
200 Usage: $progname [options] [file [files [...]]]
202 The program is based on the same principle as md5sum(1) and sha1sum(1),
203 but combines the two hashes and also includes the file size:
205 [SHA1][-][MD5][-][SIZE][\\t][FILENAME][\\n]
207 or if the --with-mtime option is used:
209 [SHA1][-][MD5][-][SIZE][\\t][FILENAME][\\t][MTIME][\\n]
211 The reason for this approach, is that both hashing algoritms are well
212 known and widely used. Both algorithms are good enough for everyday
213 content verification, but at least the MD5 algorithm is vulnerable to
214 intentional collisions. Instead of inventing new algorithms which has to
215 earn trust over the years, combining the two well examined algorithms
216 and adding the size of the file will make a smsum hash collision much
217 harder.
219 If no filenames are specified on the command line, stdin is used.
221 Special characters in filenames are escaped this way:
223 Horizontal Tab (0x09): \\t
224 Line feed (0x0a): \\n
225 Carriage return (0x0d): \\r
226 Backslash ('\\', 0x5c): \\\\
228 Options:
230 -h, --help
231 Show this help.
232 -m, --with-mtime
233 Also include file modification time at the end of every line. The
234 date uses the UTC timezone and has the format
235 "yyyy-mm-ddThh:mm:ssZ". If stdin is read, the current time is used.
236 -q, --quiet
237 Be more quiet. Can be repeated to increase silence.
238 -v, --verbose
239 Increase level of verbosity. Can be repeated.
240 --version
241 Print version information.
244 exit($Retval);
247 sub msg {
248 # Print a status message to stderr based on verbosity level
249 my ($verbose_level, $Txt) = @_;
251 if ($Opt{'verbose'} >= $verbose_level) {
252 print(STDERR "$progname: $Txt\n");
254 return;
257 __END__
259 # This program is free software; you can redistribute it and/or modify it under
260 # the terms of the GNU General Public License as published by the Free Software
261 # Foundation; either version 2 of the License, or (at your option) any later
262 # version.
264 # This program is distributed in the hope that it will be useful, but WITHOUT
265 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
266 # FOR A PARTICULAR PURPOSE.
267 # See the GNU General Public License for more details.
269 # You should have received a copy of the GNU General Public License along with
270 # this program.
271 # If not, see L<http://www.gnu.org/licenses/>.
273 # vim: set ts=4 sw=4 sts=4 noet fo+=w tw=79 fenc=UTF-8 :