pastebin - collaborative debugging tool
kpaste.net RSS

Difference between
modified post c0da2f by Anonymous on Sun 23rd Sep 2012 14:42
original post b0a8951d by Anonymous on Sat 22nd Sep 2012 15:48
Download diff
Show old version | new version | both versions

    
11
#!/usr/bin/perl
22
33
use 5.12.3;
44
use strict;
55
use warnings;
66
use Cwd qw(abs_path cwd);
7-
use Digest::MD5 qw(md5_hex);
87
use IO::Handle qw(autoflush);
98
use File::Basename qw(basename dirname);
9+
use File::Path qw(make_path remove_tree);
1010
#use File::Slurp qw(read_file);
1111
use diagnostics;
1212
1313
use threads qw(yield);
1414
use threads::shared;
1515
use Thread::Queue;
1616
use Thread::Semaphore;
1717
#use Fcntl qw(:flock);
1818
use POSIX qw(SIGINT);
1919
use POSIX qw(ceil);
2020
2121
chomp(my $cores = `grep -c ^processor /proc/cpuinfo`);
2222
2323
my (@lib, $mode);
2424
2525
# Path to and name of log file to be used for logging.
2626
my $logf = "$ENV{HOME}/md5db.log";
2727
2828
# Delimiter used for database
2929
my $delim = "\t\*\t";
3030
3131
# Array for storing the actual arguments used by the script internally.
3232
# Might be useful for debugging.
3333
my @cmd = (basename($0));
3434
3535
# Name of database file.
3636
my $db = 'md5.db';
3737
3838
# Clear screen command.
3939
my $clear = `clear && echo`;
4040
4141
# Creating a hash that will store the names of files that are
4242
# too big to fit into RAM. We'll process them last.
4343
my %large :shared;
4444
4545
# Creating a few shared variables.
4646
# %err will be used for errors
4747
# $n will be used to count the number of files processed
4848
# %md5h is the database hash
4949
my %err :shared;
5050
my $n :shared = 0;
5151
my %md5h :shared;
5252
my %file_contents :shared;
5353
my $stopping :shared = 0;
5454
my $file_stack :shared = 0;
5555
my $busy :shared = 0;
5656
57-
my $disk_size = 1000000000;
57+
my $tempfs = '/dev/shm/md5sum' . '.' . int(rand(10000));
58+
my $disk_size = 500000000;
5859
5960
# This will be used to control access to the logger subroutine.
6061
my $semaphore = Thread::Semaphore->new();
6162
6263
POSIX::sigaction(SIGINT, POSIX::SigAction->new(\&handler))
6364
|| die "Error setting SIGINT handler: $!\n";
6465
6566
# Creating a custom POSIX signal handler.
6667
# First we create a shared variable that will work as a SIGINT switch.
6768
# Then we define the handler subroutine.
6869
# If ^C is pressed for the first time,
6970
# trip the switch and let the current threads finish.
7071
# If ^C is pressed a second time, quit directly.
7172
# Each subroutine to be used for starting threads will have to
7273
# take notice of the state of the $saw_sigint variable.
7374
my $saw_sigint :shared = 0;
7475
sub handler {
75-
	if ($saw_sigint == 1) {
76+
	$saw_sigint = 1;
8077
}
8178
8279
# Open file handle for the log file
8380
open(my $LOG, '>>', $logf) or die "Can't open '$logf': $!";
8481
8582
# Make the $LOG file handle unbuffered for instant logging.
8683
$LOG->autoflush(1);
8784
8885
# Duplicate STDERR as a regular file handle
8986
open(my $SE, ">&STDERR") or die "Can't duplicate STDERR: $!";
9087
9188
### Subroutine for printing usage instructions
9289
sub usage {
9390
9491
	my $s = basename($0);
9592
9693
	say <<"HELP"
9794
Usage: $s [options] [directory 1] .. [directory N]
9895
9996
	-help Print this help message.
10097
10198
	-double Check database for files that have identical
10299
	hashes.
103100
104101
	-import Import MD5 sums to the database from already existing
105102
	\*.MD5 files in each directory.
106103
107104
	-index Index new files in each directory.
108105
109106
	-test Test the MD5 sums of the files in the database to see if
110107
	they've changed.
111108
112109
HELP
113110
}
114111
115112
# This loop goes through the argument list as passed to the script
116113
# by the user when ran.
117114
foreach my $arg (@ARGV) {
118115
119116
	# If argument starts with a dash '-', interprete it as an option
120117
	if ($arg =~ /^-/) {
121118
122119
		given ($arg) {
123120
			# When '-double', set script mode to 'double', and call
124121
			# the md5double subroutine later.
125122
			when (/^-double$/) {
126123
				if (!$mode) { push(@cmd, $arg); $mode = 'double'; }
127124
			}
128125
129126
			# When '-import', set script mode to 'import', and call
130127
			# the md5import subroutine later.
131128
			when (/^-import$/) {
132129
				if (!$mode) { push(@cmd, $arg); $mode = 'import'; }
133130
			}
134131
135132
			# When '-help', set script mode to 'help', and print
136133
			# usage instructions later.
137134
			when (/^-help$/) {
138135
				if (!$mode) { push(@cmd, $arg); $mode = 'help'; }
139136
			}
140137
141138
			# When '-index', set script mode to 'index', and call
142139
			# the md5index subroutine later.
143140
			when (/^-index$/) {
144141
				if (!$mode) { push(@cmd, $arg); $mode = 'index'; }
145142
			}
146143
147144
			# When '-test', set the script mode to 'test', and call
148145
			# the md5test subroutine later.
149146
			when (/^-test$/) {
150147
				if (!$mode) { push(@cmd, $arg); $mode = 'test'; }
151148
			}
152149
		}
153150
	# If argument is a directory, include it in the @lib array
154151
	} elsif (-d $arg) { push(@lib, $arg); push(@cmd, $arg); }
155152
}
156153
157154
# If no switches were used, print usage instructions
158155
if (!@lib || !$mode || $mode eq 'help')
159156
	{ usage; exit; }
160157
161158
#say "@cmd\n";
162159
163160
# Subroutine for controlling the log file
164161
# Applying a semaphore so multiple threads won't try to
165162
# access it at once, just in case ;-)
166163
sub logger {
167164
168165
	$semaphore->down();
169166
170167
	my($arg, $sw, @fn, $n);
171168
172169
	# Creating a variable to hold the current time.
173170
	my $now = localtime(time);
174171
175172
	# Array of accepted switches to this subroutine
176173
	my @larg = qw{start int gone corr diff end};
177174
178175
	# Loop through all the arguments passed to this subroutine
179176
	# Perform checks that decide which variable the arguments are to
180177
	# be assigned to.
181178
	CHECK: while (@_) {
182179
			$arg = shift(@_);
183180
184181
			# If $arg is a switch, set the $sw variable and start
185182
			# the next iteration of the CHECK loop.
186183
			foreach (@larg) {
187184
				if ($_ eq $arg) { $sw = $arg; next CHECK; }
188185
			}
189186
190187
			# If $arg is a number assign it to $n, if it's a file
191188
			# add it to @fn.
192189
			if ($arg =~ /^[0-9]*$/) { $n = $arg; }
193190
			else { push(@fn, $arg); }
194191
	}
195192
	given ($sw) {
196193
		# Starts writing the log.
197194
		when ('start') {
198195
			say $LOG "\n**** Logging started on $now ****\n";
199196
			say $LOG "Running script in '$mode' mode.\n";
200197
		}
201198
		# When the script is interrupted by user pressing ^C,
202199
		# say so in STDOUT, close the log.
203200
		when ('int') {
204201
			say "\nInterrupted by user!\n";
205202
			say $LOG $n . " file(s) were tested.";
206203
			say $LOG "\n**** Logging ended on $now ****\n";
207204
			close $LOG or die "Can't close '$LOG': $!";
208205
		}
209206
		# Called when file has been deleted or moved.
210207
		when ('gone') {
211208
			say $LOG $fn[0] . "\n\t" . "has been (re)moved.\n";
212209
			$err{$fn[0]} = "has been (re)moved.\n";
213210
		}
214211
		# Called when file has been corrupted.
215212
		when ('corr') {
216213
			say $LOG $fn[0] . "\n\t" .
217214
			"has been corrupted.\n";
218215
			$err{$fn[0]} = "has been corrupted.\n";
219216
		}
220217
		when ('diff') {
221218
			say $LOG $fn[0] . "\n\t" .
222219
				"doesn't match the hash in database.\n";
223220
			$err{$fn[0]} = "doesn't match the hash in database.\n";
224221
		}
225222
		# Called when done, and to close the log.
226223
		# If no errors occurred write "Everything is OK!" to the log.
227224
		# If errors occurred print the %err hash.
228225
		# Either way, print number of files processed.
229226
		when ('end') {
230227
			if (!%err) {
231228
				say $LOG "\nEverything is OK!\n";
232229
			} else {
233230
				say "\n**** Errors Occurred ****\n";
234231
				foreach my $fn (sort keys %err) {
235232
					say $SE $fn . "\n\t" . $err{$fn};
236233
				}
237234
			}
238235
239236
			say $LOG $n . " file(s) were tested.\n" if ($n);
240237
			say $LOG "\n**** Logging ended on $now ****\n";
241238
			close $LOG or die "Can't close '$LOG': $!";
242239
		}
243240
	}
244241
	$semaphore->up();
245242
}
246243
247244
248245
# Subroutine for reading a database file into the database hash.
249246
# This is the first subroutine that will be executed and all others
250247
# depend upon it, cause without it we don't have a
251248
# database hash to work with.
252249
sub file2hash {
253250
254251
	# The format string which is used for parsing the database file
255252
	my $format = qr/^.*\t\*\t[[:alnum:]]{32}$/;
256253
	my (@dbfile, @gone);
257254
258255
	# Open the database file and read it into the @dbfile variable
259256
	open(MD5DB, '<', $db) or die "Can't open '$db': $!";
260257
	chomp (@dbfile = (<MD5DB>));
261258
	close(MD5DB) or die "Can't close '$db': $!";
262259
263260
	# Loop through all the lines in the database file and split
264261
	# them before storing in the database hash.
265262
	# Also, print each line to STDOUT for debug purposes
266263
	foreach my $line (@dbfile) {
267264
268265
		# If current line matches the proper database file format,
269266
		# continue.
270267
		if ($line =~ /$format/) {
271268
272269
			my ($fn, $hash) = (split(/\Q$delim/, $line));
273270
274271
			# If $fn is a real file and not already in the hash,
275272
			# continue.
276273
			if (-f $fn && ! $md5h{$fn}) {
277274
					$md5h{$fn} = $hash;
278275
					say "$fn". "$delim" . "$hash";
279276
280277
			# Saves the names of deleted or moved files in '@gone'
281278
			# for printing at the end of this subroutine.
282279
			} else { push(@gone, $fn); }
283280
		}
284281
	}
285282
286283
	# Clears the screen, thereby scrolling past the database file print
287284
	print $clear;
288285
289286
	# Loops through the @gone array and logs every file name
290287
	# that's been deleted or moved.
291288
	foreach my $fn (@gone) { logger('gone', $fn); }
292289
}
293290
294291
# Subroutine for printing the database hash to the database file
295292
sub hash2file {
296293
297294
	open(MD5DB, '>', "$db") or die "Can't open '$db': $!";
298295
	# Loops through all the keys in the database hash and prints
299296
	# the entries (divided by the $delim variable) to the database file.
300297
	foreach my $k (sort keys %md5h) {
301298
		say MD5DB $k . $delim . $md5h{$k};
302299
	}
303300
	close(MD5DB) or die "Can't close '$db': $!";
304301
}
305302
306303
# Subroutine for finding files
307304
# Finds all the files inside the directory name passed to it,
308305
# and sorts the output before storing it in the @files array.
309306
sub getfiles {
310307
311308
	my $dn = shift;
312309
	my @files;
313310
314-
	open(FIND, '-|', qq(find -L "$dn" -type f -name "*"))
311+
	open(FIND, '-|', qq(find "$dn" -type f -name "*" -nowarn))
315312
	or die "Can\'t run 'find': $!";
316313
	while (my $fn = (<FIND>)) {
317314
			chomp($fn);
318315
			$fn =~ s($dn/)();
319316
			push(@files, $fn) if (-f $fn && $fn ne basename($db));
320317
	}
321318
	close(FIND) or die "Can't close 'find': $!";
322319
	return @files;
323320
}
324321
325322
sub md5double {
326323
327324
	if (!keys(%md5h)) {
328325
		say "No database file. Run the script in 'index' mode first\n" .
329326
		"to index the files.";
330327
		exit;
331328
	}
332329
333330
	# Loop through the %md5h hash and save the checksums as keys in a
334331
	# new hash called %exists. Each of those keys will hold an
335332
	# anonymous array with the matching file names.
336333
	my %exists;
337334
	foreach my $fn (keys(%md5h)) {
338335
		my $hash = $md5h{$fn};
339336
		if (!$exists{"$hash"}) {
340337
			$exists{"$hash"}->[0] = $fn;
341338
		} else {
342339
			push(@{$exists{"$hash"}}, $fn);
343340
		}
344341
	}
345342
346343
	# Loop through the %exists hash and print files that are identical,
347344
	# if any.
348345
	foreach my $hash (keys(%exists)) {
349346
		if (scalar(@{$exists{"$hash"}}) > 1) {
350347
			say "These files have the same hash (${hash}):";
351348
			foreach my $fn (@{$exists{"$hash"}}) {
352349
				say $fn;
353350
			}
354351
			say "";
355352
		}
356353
	}
357354
}
358355
359356
# Subroutine for finding and parsing *.MD5 files, adding the hashes
360357
# to the database hash and thereby also to the file.
361358
# It takes 1 argument:
362359
# (1) the thread queue
363360
sub md5import {
364361
365362
	my $md5fn = shift;
366363
367364
	my ($fn, $hash, @fields, @lines);
368365
369366
	# The format string which is used for parsing the *.MD5 files.
370367
	my $format = qr/^[[:alnum:]]{32}\s\*.*/;
371368
372369
	# If the file extension is *.MD5 in either upper- or
373370
	# lowercase, continue.
374371
	if ($md5fn =~ /.md5$/i) {
375372
376373
		# Open the *.MD5 file and read its contents to the
377374
		# @lines array.
378375
		open(MD5, '<', $md5fn) or die "Can't open '$md5fn': $!";
379376
		chomp(@lines = (<MD5>));
380377
		close(MD5) or die "Can't close '$md5fn': $!";
381378
382379
		# Loop to check that the format of the *.MD5 file really
383380
		# is correct before proceeding.
384381
		foreach my $line (@lines) {
385382
386383
			# If format string matches the line(s) in the *.MD5
387384
			# file, continue.
388385
			if ($line =~ /$format/) {
389386
390387
				# Split the line so that the hash and file name go
391388
				# into @fields array.
392389
				# After that strip the path (if any) of the file
393390
				# name, and prepend the path of the *.MD5 file to
394391
				# it instead.
395392
				# Store hash and file name in the $hash and $fn
396393
				# variables for readability.
397394
				@fields = split(/\s\Q*/, $line, 2);
398395
				my $path = dirname($md5fn);
399396
				$hash = $fields[0];
400397
401398
				if ($path eq '.') { $fn = basename($fields[1]); }
402399
				else { $fn = dirname($md5fn)
403400
				. '/' . basename($fields[1]); }
404401
405402
				# Convert CR+LF newlines to proper LF to avoid
406403
				# identical file names from being interpreted as
407404
				# different.
408405
				$fn =~ s/\r//;
409406
410407
				# Unless file name already is in the database hash,
411408
				# print a message, add it to the hash.
412409
				if (! $md5h{$fn} && -f $fn) {
413410
414411
					say "$fn" . "\n\t" .
415412
					"Imported MD5 sum from '" .
416413
					basename($md5fn) .
417414
					"'.\n";
418415
419416
					$md5h{$fn} = $hash;
420417
421418
					# If file name is not a real file, write to
422419
					# the log.
423420
					# If file name is in database hash but the
424421
					# MD5 sum from the MD5 file doesn't match,
425422
					# print to the log.
426423
				} elsif (! -f $fn) { logger('gone', $fn); }
427424
				elsif ($md5h{$fn} ne $hash)
428425
				{ logger('diff', $md5fn); }
429426
			}
430427
		}
431428
	}
432429
}
433430
431+
sub iquit {
434440
435441
sub md5sum {
436442
	my $fn = shift;
437443
438444
	while ($busy) { yield(); }
439445
440446
	if ($large{$fn}) {
441447
		lock($busy);
442448
		$busy = 1;
443-
		open(FILE, '<:raw', $fn) or die "Can't open '$fn': $!";
449+
		chomp(my $hash = (split(/\Q */, qx/md5sum -b "$fn"/, 2))[0]);
446450
		$busy = 0;
447451
		return $hash;
448452
	} else {
453-
		delete($file_contents{$fn});
454-
		return $hash;
455-
	}
456-
}
457453
458-
# Subroutine to index the files
454+
		my $tempfn = $tempfs . '/' . quotemeta(basename($fn)) . '.' .
459-
# i.e calculate and store the MD5 sums in the database hash/file.
455+
		int(rand(10000));
460-
# It takes 1 argument:
456+
		#say "tempfs: $tempfs";
461-
# (1) the thread queue
457+
		#say "tempfn: $tempfn";
462-
sub md5index {
458+
		if (! -d $tempfs) {
463-
	my $q = shift;
459+
			make_path($tempfs);
464-
	my $tid = threads->tid();
460+
			if (! -d $tempfs) {
461+
				die "Couldnt make_path $tempfs: $!"; }
465463
466-
	# Loop through the thread que.
464+
		my($hash, $md5sum_in);
476465
477-
			# If the $saw_sigint variable has been tripped.
466+
		my $pid = open($md5sum_in, '|-:raw',
478-
			# Quit this 'while' loop, thereby closing the thread.
467+
		qq{md5sum -b >${tempfn}})
479-
			if ($saw_sigint == 1) {
468+
		or die "Can't open md5sum: $!";
480-
				say "Closing thread: " . $tid;
469+
		print $md5sum_in $file_contents{$fn};
481-
				last;
470+
		close($md5sum_in);
482-
			}
471+
		waitpid( $pid, 0 );
483-
	}
472+
		if ($? == 2) { iquit(); }
484473
485-
	while (!$q->pending() && !$stopping) {
474+
		chomp($hash = (split(/\Q */, qx/cat $tempfn/, 2))[0]);
486-
		yield();
475+
		say "$fn : $hash";
487-
		goto(LOOP2);
476+
		#system(qq{rm -rf $tempfn});
477+
		#if ($? != 0) { die "Couldn't rm '$tempfn': $!"; }
490478
491-
# Subroutine for testing to see if the MD5 sums
479+
		lock($file_stack);
492-
# in the database file are correct (i.e. have changed or not).
480+
		$file_stack -= length($file_contents{$fn});
481+
		lock(%file_contents);
493489
# It takes 1 argument:
494490
# (1) the thread queue
495-
sub md5test {
491+
sub md5index {
496492
	my $q = shift;
497493
	my $tid = threads->tid();
499494
500-
	# Loop through the thread queue.
495+
	# Loop through the thread que.
501-
	LOOP: while ((my $fn = $q->dequeue_nb()) || !$stopping) {
496+
	LOOP2: while ((my $fn = $q->dequeue_nb()) || !$stopping) {
502497
				if ($fn) {
503-
					$newmd5 = md5sum($fn);
498+
				$md5h{$fn} = md5sum($fn);
504-
					say "$tid $fn: done testing (${file_stack})";
499+
				#say "$tid $fn: done indexing (${file_stack})";
505-
					$oldmd5 = $md5h{$fn};
500+
				lock($n);
501+
				$n++;
506505
507-
					# If the new MD5 sum doesn't match the one in the hash,
506+
			# If the $saw_sigint variable has been tripped.
508-
					# and file doesn't already exist in the %err hash,
507+
			# Quit this 'while' loop, thereby closing the thread.
509-
					# log it and replace the old MD5 sum in the hash with
508+
			if ($saw_sigint == 1) {
510-
					# the new one.
509+
				iquit();
514510
					}
519511
				}
520-
				# If the $saw_sigint variable has been tripped.
521-
				# Quit this 'while' loop, thereby closing the thread.
522-
				if ($saw_sigint == 1) {
523-
					say "Closing thread: " . $tid;
524-
					last;
525-
				}
526-
	}
512+

Submit a correction or amendment below (click here to make a fresh posting)
After submitting an amendment, you'll be able to view the differences between the old and new posts easily.

Syntax highlighting:

To highlight particular lines, prefix each line with {%HIGHLIGHT}




All content is user-submitted.
The administrators of this site (kpaste.net) are not responsible for their content.
Abuse reports should be emailed to us at