pastebin - collaborative debugging tool
kpaste.net RSS

Difference between
modified post b0a8951d by Anonymous on Sat 22nd Sep 2012 15:48
original post f0b64 by Anonymous on Sat 22nd Sep 2012 13:05
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);
77
use Digest::MD5 qw(md5_hex);
88
use IO::Handle qw(autoflush);
99
use File::Basename qw(basename dirname);
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
5757
my $disk_size = 1000000000;
5858
5959
# This will be used to control access to the logger subroutine.
6060
my $semaphore = Thread::Semaphore->new();
6161
6262
POSIX::sigaction(SIGINT, POSIX::SigAction->new(\&handler))
6363
|| die "Error setting SIGINT handler: $!\n";
6464
6565
# Creating a custom POSIX signal handler.
6666
# First we create a shared variable that will work as a SIGINT switch.
6767
# Then we define the handler subroutine.
6868
# If ^C is pressed for the first time,
6969
# trip the switch and let the current threads finish.
7070
# If ^C is pressed a second time, quit directly.
7171
# Each subroutine to be used for starting threads will have to
7272
# take notice of the state of the $saw_sigint variable.
7373
my $saw_sigint :shared = 0;
7474
sub handler {
7575
	if ($saw_sigint == 1) {
7676
		logger('int', $n);
7777
		hash2file();
7878
		exit;
7979
	} else { $saw_sigint = 1; }
8080
}
8181
8282
# Open file handle for the log file
8383
open(my $LOG, '>>', $logf) or die "Can't open '$logf': $!";
8484
8585
# Make the $LOG file handle unbuffered for instant logging.
8686
$LOG->autoflush(1);
8787
8888
# Duplicate STDERR as a regular file handle
8989
open(my $SE, ">&STDERR") or die "Can't duplicate STDERR: $!";
9090
9191
### Subroutine for printing usage instructions
9292
sub usage {
9393
9494
	my $s = basename($0);
9595
9696
	say <<"HELP"
9797
Usage: $s [options] [directory 1] .. [directory N]
9898
9999
	-help Print this help message.
100100
101101
	-double Check database for files that have identical
102102
	hashes.
103103
104104
	-import Import MD5 sums to the database from already existing
105105
	\*.MD5 files in each directory.
106106
107107
	-index Index new files in each directory.
108108
109109
	-test Test the MD5 sums of the files in the database to see if
110110
	they've changed.
111111
112112
HELP
113113
}
114114
115115
# This loop goes through the argument list as passed to the script
116116
# by the user when ran.
117117
foreach my $arg (@ARGV) {
118118
119119
	# If argument starts with a dash '-', interprete it as an option
120120
	if ($arg =~ /^-/) {
121121
122122
		given ($arg) {
123123
			# When '-double', set script mode to 'double', and call
124124
			# the md5double subroutine later.
125125
			when (/^-double$/) {
126126
				if (!$mode) { push(@cmd, $arg); $mode = 'double'; }
127127
			}
128128
129129
			# When '-import', set script mode to 'import', and call
130130
			# the md5import subroutine later.
131131
			when (/^-import$/) {
132132
				if (!$mode) { push(@cmd, $arg); $mode = 'import'; }
133133
			}
134134
135135
			# When '-help', set script mode to 'help', and print
136136
			# usage instructions later.
137137
			when (/^-help$/) {
138138
				if (!$mode) { push(@cmd, $arg); $mode = 'help'; }
139139
			}
140140
141141
			# When '-index', set script mode to 'index', and call
142142
			# the md5index subroutine later.
143143
			when (/^-index$/) {
144144
				if (!$mode) { push(@cmd, $arg); $mode = 'index'; }
145145
			}
146146
147147
			# When '-test', set the script mode to 'test', and call
148148
			# the md5test subroutine later.
149149
			when (/^-test$/) {
150150
				if (!$mode) { push(@cmd, $arg); $mode = 'test'; }
151151
			}
152152
		}
153153
	# If argument is a directory, include it in the @lib array
154154
	} elsif (-d $arg) { push(@lib, $arg); push(@cmd, $arg); }
155155
}
156156
157157
# If no switches were used, print usage instructions
158158
if (!@lib || !$mode || $mode eq 'help')
159159
	{ usage; exit; }
160160
161161
#say "@cmd\n";
162162
163163
# Subroutine for controlling the log file
164164
# Applying a semaphore so multiple threads won't try to
165165
# access it at once, just in case ;-)
166166
sub logger {
167167
168168
	$semaphore->down();
169169
170170
	my($arg, $sw, @fn, $n);
171171
172172
	# Creating a variable to hold the current time.
173173
	my $now = localtime(time);
174174
175175
	# Array of accepted switches to this subroutine
176176
	my @larg = qw{start int gone corr diff end};
177177
178178
	# Loop through all the arguments passed to this subroutine
179179
	# Perform checks that decide which variable the arguments are to
180180
	# be assigned to.
181181
	CHECK: while (@_) {
182182
			$arg = shift(@_);
183183
184184
			# If $arg is a switch, set the $sw variable and start
185185
			# the next iteration of the CHECK loop.
186186
			foreach (@larg) {
187187
				if ($_ eq $arg) { $sw = $arg; next CHECK; }
188188
			}
189189
190190
			# If $arg is a number assign it to $n, if it's a file
191191
			# add it to @fn.
192192
			if ($arg =~ /^[0-9]*$/) { $n = $arg; }
193193
			else { push(@fn, $arg); }
194194
	}
195195
	given ($sw) {
196196
		# Starts writing the log.
197197
		when ('start') {
198198
			say $LOG "\n**** Logging started on $now ****\n";
199199
			say $LOG "Running script in '$mode' mode.\n";
200200
		}
201201
		# When the script is interrupted by user pressing ^C,
202202
		# say so in STDOUT, close the log.
203203
		when ('int') {
204204
			say "\nInterrupted by user!\n";
205205
			say $LOG $n . " file(s) were tested.";
206206
			say $LOG "\n**** Logging ended on $now ****\n";
207207
			close $LOG or die "Can't close '$LOG': $!";
208208
		}
209209
		# Called when file has been deleted or moved.
210210
		when ('gone') {
211211
			say $LOG $fn[0] . "\n\t" . "has been (re)moved.\n";
212212
			$err{$fn[0]} = "has been (re)moved.\n";
213213
		}
214214
		# Called when file has been corrupted.
215215
		when ('corr') {
216216
			say $LOG $fn[0] . "\n\t" .
217217
			"has been corrupted.\n";
218218
			$err{$fn[0]} = "has been corrupted.\n";
219219
		}
220220
		when ('diff') {
221221
			say $LOG $fn[0] . "\n\t" .
222222
				"doesn't match the hash in database.\n";
223223
			$err{$fn[0]} = "doesn't match the hash in database.\n";
224224
		}
225225
		# Called when done, and to close the log.
226226
		# If no errors occurred write "Everything is OK!" to the log.
227227
		# If errors occurred print the %err hash.
228228
		# Either way, print number of files processed.
229229
		when ('end') {
230230
			if (!%err) {
231231
				say $LOG "\nEverything is OK!\n";
232232
			} else {
233233
				say "\n**** Errors Occurred ****\n";
234234
				foreach my $fn (sort keys %err) {
235235
					say $SE $fn . "\n\t" . $err{$fn};
236236
				}
237237
			}
238238
239239
			say $LOG $n . " file(s) were tested.\n" if ($n);
240240
			say $LOG "\n**** Logging ended on $now ****\n";
241241
			close $LOG or die "Can't close '$LOG': $!";
242242
		}
243243
	}
244244
	$semaphore->up();
245245
}
246246
247247
248248
# Subroutine for reading a database file into the database hash.
249249
# This is the first subroutine that will be executed and all others
250250
# depend upon it, cause without it we don't have a
251251
# database hash to work with.
252252
sub file2hash {
253253
254254
	# The format string which is used for parsing the database file
255255
	my $format = qr/^.*\t\*\t[[:alnum:]]{32}$/;
256256
	my (@dbfile, @gone);
257257
258258
	# Open the database file and read it into the @dbfile variable
259259
	open(MD5DB, '<', $db) or die "Can't open '$db': $!";
260260
	chomp (@dbfile = (<MD5DB>));
261261
	close(MD5DB) or die "Can't close '$db': $!";
262262
263263
	# Loop through all the lines in the database file and split
264264
	# them before storing in the database hash.
265265
	# Also, print each line to STDOUT for debug purposes
266266
	foreach my $line (@dbfile) {
267267
268268
		# If current line matches the proper database file format,
269269
		# continue.
270270
		if ($line =~ /$format/) {
271271
272272
			my ($fn, $hash) = (split(/\Q$delim/, $line));
273273
274274
			# If $fn is a real file and not already in the hash,
275275
			# continue.
276276
			if (-f $fn && ! $md5h{$fn}) {
277277
					$md5h{$fn} = $hash;
278278
					say "$fn". "$delim" . "$hash";
279279
280280
			# Saves the names of deleted or moved files in '@gone'
281281
			# for printing at the end of this subroutine.
282282
			} else { push(@gone, $fn); }
283283
		}
284284
	}
285285
286286
	# Clears the screen, thereby scrolling past the database file print
287287
	print $clear;
288288
289289
	# Loops through the @gone array and logs every file name
290290
	# that's been deleted or moved.
291291
	foreach my $fn (@gone) { logger('gone', $fn); }
292292
}
293293
294294
# Subroutine for printing the database hash to the database file
295295
sub hash2file {
296296
297297
	open(MD5DB, '>', "$db") or die "Can't open '$db': $!";
298298
	# Loops through all the keys in the database hash and prints
299299
	# the entries (divided by the $delim variable) to the database file.
300300
	foreach my $k (sort keys %md5h) {
301301
		say MD5DB $k . $delim . $md5h{$k};
302302
	}
303303
	close(MD5DB) or die "Can't close '$db': $!";
304304
}
305305
306306
# Subroutine for finding files
307307
# Finds all the files inside the directory name passed to it,
308308
# and sorts the output before storing it in the @files array.
309309
sub getfiles {
310310
311311
	my $dn = shift;
312312
	my @files;
313313
314314
	open(FIND, '-|', qq(find -L "$dn" -type f -name "*"))
315315
	or die "Can\'t run 'find': $!";
316316
	while (my $fn = (<FIND>)) {
317317
			chomp($fn);
318318
			$fn =~ s($dn/)();
319319
			push(@files, $fn) if (-f $fn && $fn ne basename($db));
320320
	}
321321
	close(FIND) or die "Can't close 'find': $!";
322322
	return @files;
323323
}
324324
325325
sub md5double {
326326
327327
	if (!keys(%md5h)) {
328328
		say "No database file. Run the script in 'index' mode first\n" .
329329
		"to index the files.";
330330
		exit;
331331
	}
332332
333333
	# Loop through the %md5h hash and save the checksums as keys in a
334334
	# new hash called %exists. Each of those keys will hold an
335335
	# anonymous array with the matching file names.
336336
	my %exists;
337337
	foreach my $fn (keys(%md5h)) {
338338
		my $hash = $md5h{$fn};
339339
		if (!$exists{"$hash"}) {
340340
			$exists{"$hash"}->[0] = $fn;
341341
		} else {
342342
			push(@{$exists{"$hash"}}, $fn);
343343
		}
344344
	}
345345
346346
	# Loop through the %exists hash and print files that are identical,
347347
	# if any.
348348
	foreach my $hash (keys(%exists)) {
349349
		if (scalar(@{$exists{"$hash"}}) > 1) {
350350
			say "These files have the same hash (${hash}):";
351351
			foreach my $fn (@{$exists{"$hash"}}) {
352352
				say $fn;
353353
			}
354354
			say "";
355355
		}
356356
	}
357357
}
358358
359359
# Subroutine for finding and parsing *.MD5 files, adding the hashes
360360
# to the database hash and thereby also to the file.
361361
# It takes 1 argument:
362362
# (1) the thread queue
363363
sub md5import {
364364
365365
	my $md5fn = shift;
366366
367367
	my ($fn, $hash, @fields, @lines);
368368
369369
	# The format string which is used for parsing the *.MD5 files.
370370
	my $format = qr/^[[:alnum:]]{32}\s\*.*/;
371371
372372
	# If the file extension is *.MD5 in either upper- or
373373
	# lowercase, continue.
374374
	if ($md5fn =~ /.md5$/i) {
375375
376376
		# Open the *.MD5 file and read its contents to the
377377
		# @lines array.
378378
		open(MD5, '<', $md5fn) or die "Can't open '$md5fn': $!";
379379
		chomp(@lines = (<MD5>));
380380
		close(MD5) or die "Can't close '$md5fn': $!";
381381
382382
		# Loop to check that the format of the *.MD5 file really
383383
		# is correct before proceeding.
384384
		foreach my $line (@lines) {
385385
386386
			# If format string matches the line(s) in the *.MD5
387387
			# file, continue.
388388
			if ($line =~ /$format/) {
389389
390390
				# Split the line so that the hash and file name go
391391
				# into @fields array.
392392
				# After that strip the path (if any) of the file
393393
				# name, and prepend the path of the *.MD5 file to
394394
				# it instead.
395395
				# Store hash and file name in the $hash and $fn
396396
				# variables for readability.
397397
				@fields = split(/\s\Q*/, $line, 2);
398398
				my $path = dirname($md5fn);
399399
				$hash = $fields[0];
400400
401401
				if ($path eq '.') { $fn = basename($fields[1]); }
402402
				else { $fn = dirname($md5fn)
403403
				. '/' . basename($fields[1]); }
404404
405405
				# Convert CR+LF newlines to proper LF to avoid
406406
				# identical file names from being interpreted as
407407
				# different.
408408
				$fn =~ s/\r//;
409409
410410
				# Unless file name already is in the database hash,
411411
				# print a message, add it to the hash.
412412
				if (! $md5h{$fn} && -f $fn) {
413413
414414
					say "$fn" . "\n\t" .
415415
					"Imported MD5 sum from '" .
416416
					basename($md5fn) .
417417
					"'.\n";
418418
419419
					$md5h{$fn} = $hash;
420420
421421
					# If file name is not a real file, write to
422422
					# the log.
423423
					# If file name is in database hash but the
424424
					# MD5 sum from the MD5 file doesn't match,
425425
					# print to the log.
426426
				} elsif (! -f $fn) { logger('gone', $fn); }
427427
				elsif ($md5h{$fn} ne $hash)
428428
				{ logger('diff', $md5fn); }
429429
			}
430430
		}
431431
	}
432432
}
433433
434434
435435
sub md5sum {
436436
	my $fn = shift;
437437
438438
	while ($busy) { yield(); }
439439
440440
	if ($large{$fn}) {
441441
		lock($busy);
442442
		$busy = 1;
443443
		open(FILE, '<:raw', $fn) or die "Can't open '$fn': $!";
444444
		my $hash = Digest::MD5->new->addfile("FILE")->hexdigest;
445445
		close(FILE) or die "Can't close '$fn': $!";
446446
		$busy = 0;
447447
		return $hash;
448448
	} else {
449449
		my $hash = md5_hex($file_contents{$fn});
450450
		lock($file_stack);
451451
		$file_stack -= length($file_contents{$fn});
452452
		lock(%file_contents);
453453
		delete($file_contents{$fn});
454454
		return $hash;
455455
	}
456456
}
457457
458458
# Subroutine to index the files
459459
# i.e calculate and store the MD5 sums in the database hash/file.
460460
# It takes 1 argument:
461461
# (1) the thread queue
462462
sub md5index {
463463
	my $q = shift;
464464
	my $tid = threads->tid();
465465
466466
	# Loop through the thread que.
467467
	LOOP2: while ((my $fn = $q->dequeue_nb()) || !$stopping) {
468468
			if ($fn) {
469469
				$md5h{$fn} = md5sum($fn);
470470
				say "$tid $fn: done indexing (${file_stack})";
471471
				lock($n);
472472
				$n++;
473473
			} else {
474474
				yield();
475475
			}
476476
477477
			# If the $saw_sigint variable has been tripped.
478478
			# Quit this 'while' loop, thereby closing the thread.
479479
			if ($saw_sigint == 1) {
480480
				say "Closing thread: " . $tid;
481481
				last;
482482
			}
483483
	}
484484
485485
	while (!$q->pending() && !$stopping) {
486486
		yield();
487487
		goto(LOOP2);
488488
	}
489489
}
490490
491491
# Subroutine for testing to see if the MD5 sums
492492
# in the database file are correct (i.e. have changed or not).
493493
# It takes 1 argument:
494494
# (1) the thread queue
495495
sub md5test {
496496
	my $q = shift;
497497
	my $tid = threads->tid();
498498
	my ($oldmd5, $newmd5);
499499
500500
	# Loop through the thread queue.
501501
	LOOP: while ((my $fn = $q->dequeue_nb()) || !$stopping) {
502502
				if ($fn) {
503503
					$newmd5 = md5sum($fn);
504504
					say "$tid $fn: done testing (${file_stack})";
505505
					$oldmd5 = $md5h{$fn};
506506
507507
					# If the new MD5 sum doesn't match the one in the hash,
508508
					# and file doesn't already exist in the %err hash,
509509
					# log it and replace the old MD5 sum in the hash with
510510
					# the new one.
511511
					if ($newmd5 ne $oldmd5 && ! $err{$fn}) {
512512
						logger('diff', $fn);
513513
						$md5h{$fn} = $newmd5;
514514
					}
515515
					lock($n);
516516
					$n++;
517517
				} else {
518518
					yield();
519519
				}
520520
				# If the $saw_sigint variable has been tripped.
521521
				# Quit this 'while' loop, thereby closing the thread.
522522
				if ($saw_sigint == 1) {
523523
					say "Closing thread: " . $tid;
524524
					last;
525525
				}
526526
	}
527527
528528
	while (!$q->pending() && !$stopping) {
529529
		yield();
530530
		goto(LOOP);
531531
	}
532532
}
533533
534534
sub md5flac {
535535
	my $fn = shift;
536536
	my (@req, $hash);
537537
538538
	if ($fn =~ /.flac$/i) {
539539
540540
		if (! @req) {
541541
			chomp(@req = ( `which flac metaflac 2>&-` ));
542542
543543
			if (! $req[0] || ! $req[1]) {
544544
				say "You need both 'flac' and 'metaflac' to test FLAC files!\n" .
545545
				"Using normal test method...\n";
546546
				@req = '0';
547547
				return;
548548
			}
549549
		}
550550
551551
		unless ($req[0] = '0') {
552552
			chomp($hash = `metaflac --show-md5sum "$fn" 2>&-`);
553553
			if ($? != 0 && $? != 2) { logger('corr', $fn); return; }
554554
555555
			system('flac', '--totally-silent', '--test', "$fn");
556556
			if ($? != 0 && $? != 2) { logger('corr', $fn); return; }
557557
558558
			return $hash;
559559
		}
560560
	}
561561
}
562562
563563
# Create the thread queue.
564564
my $q = Thread::Queue->new();
565565
566566
# Depending on which script mode is active,
567567
# set the @run array to the correct arguments.
568568
# This will be used to start the threads later.
569569
my @run;
570570
given ($mode) {
571571
	when ('index') {
572572
		@run = (\&md5index, $q);
573573
	}
574574
	when ('test') {
575575
		@run = (\&md5test, $q);
576576
	}
577577
}
578578
579579
# If script mode is either 'import' or 'double' we'll start only
580580
# one thread, else we'll start as many as the available number of CPUs.
581581
my @threads;
582582
if ($mode ne 'import' && $mode ne 'double') {
583583
	foreach (1 .. $cores) {
584584
		push(@threads, threads->create(@run));
585585
	}
586586
}
587587
588588
# This loop is where the actual action takes place
589589
# (i.e. where all the subroutines get called from)
590590
foreach my $dn (@lib) {
591591
	if (-d $dn) {
592592
		# Changing $dn to the absolute path.
593593
		$dn = abs_path($dn);
594594
595595
		# Adding the current PATH to the $db variable.
596596
		$db = "$dn/$db";
597597
598598
		# Change directory to $dn.
599599
		chdir($dn)
600600
			or die "Can't change directory to '$dn': $!";
601601
602602
		# Start logging.
603603
		logger('start');
604604
605605
		# If the database file is a real file,
606606
		# store it in the database hash.
607607
		file2hash() if (-f $db);
608608
609609
		given ($mode) {
610610
			when ('double') {
611611
				# Find identical files in database.
612612
				md5double();
613613
			}
614614
			when ('import') {
615615
				# For all the files in $dn, run md5import.
616616
				foreach my $fn (getfiles($dn)) { md5import($fn); }
617617
			}
618618
			when ('index') {
619619
				#say("Enqueueing files");
620620
				# Get all the file names and put them in the queue.
621621
				foreach my $fn (getfiles($dn)) {
622622
					while ($file_stack >= $disk_size) {
623623
						my $active = threads->running();
624624
						say("${active}: $file_stack > $disk_size");
625625
						yield();
626626
					}
627627
					# Unless file name exists in the database hash,
628628
					# continue.
629629
					unless ($md5h{$fn}) {
630630
						my $size = (stat($fn))[7];
631631
						if ($size < $disk_size) {
632632
633633
							#say("${fn}: reading");
634634
							open(FILE, '<:raw', $fn) or die "Can't open '$fn': $!";
635635
							sysread(FILE, $file_contents{$fn}, $size);
636636
							close(FILE) or die "Can't close '$fn': $!";
637637
638638
							#say("${fn}: queueing");
639639
							lock($file_stack);
640640
							$file_stack += length($file_contents{$fn});
641641
							$q->enqueue($fn);
642642
						} else {
643643
							$large{$fn} = 1;
644644
						}
645645
					}
646646
				}
647647
			}
648648
			when ('test') {
649649
				#say("Enqueueing files");
650650
				# Fetch all the keys for the database hash and put
651651
				# them in the queue.
652652
				foreach my $fn (sort(keys(%md5h))) {
653653
					while ($file_stack >= $disk_size) {
654654
						say("$file_stack > $disk_size");
655655
						yield();
656656
					}
657657
658658
					my $size = (stat($fn))[7];
659659
					if ($size < $disk_size) {
660660
661661
						#say("${fn}: reading");
662662
						open(FILE, '<:raw', $fn) or die "Can't open '$fn': $!";
663663
						sysread(FILE, $file_contents{$fn}, $size);
664664
						close(FILE) or die "Can't close '$fn': $!";
665665
666666
						#say("${fn}: queueing");
667667
						lock($file_stack);
668668
						$file_stack += length($file_contents{$fn});
669669
						$q->enqueue($fn);
670670
					} else {
671671
						$large{$fn} = 1;
672672
					}
673673
				}
674674
			}
675675
		}
676676
677677
		if (%large) {
678678
			while ($file_stack > 0) {
679-
				say("$file_stack > $disk_size");
679+
				say("$file_stack > 0");
680680
				yield();
681681
			}
682682
			foreach my $fn (sort(keys(%large))) {
683683
				#say("${fn}: queueing");
684684
				$q->enqueue($fn);
685685
			}
686686
		}
687687
688688
#     use Digest::MD5;
689689
#    $md5 = Digest::MD5->new;
690690
#    $md5->add('foo', 'bar');
691691
#    $md5->add('baz');
692692
#    $digest = $md5->hexdigest;
693693
#    print "Digest is $digest\n";
694694
695695
696696
		$stopping = 1;
697697
		foreach my $t (threads->list()) { $t->join(); }
698698
		#say("All threads joined");
699699
700700
		# Print the hash to the database file and close the log
701701
		hash2file();
702702
		logger('end', $n);
703703
	}
704704
}
705705

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