diff --git a/defragfs b/defragfs index fb670da..274a563 100755 --- a/defragfs +++ b/defragfs @@ -7,11 +7,17 @@ # Using: $ sudo ./defragfs / -af # Help: $ ./defragfs / -h +use strict; +use warnings; + print ("defragfs 1.1.1, Released under GPLv3 by John Robson , March 2011 (help: \$ defragfs / -h)\n\n"); -($DIR, $ARG) = @ARGV; +my $NEEDHELP = 0; +my ($DIR, $ARG); +$DIR = shift(@ARGV); +$ARG = join(" ", @ARGV); -if($ARG =~ m/h/) { die("GNU/Linux file systems rarely fragmented files. The file system always allocates more space to write a file, but sometimes the file size grows so that space becomes insufficient and the file is fragmented, but even so the file system fragments the file efficiently. +if($ARG =~ m/(^|\s)-\S*h/) { die("GNU/Linux file systems rarely fragment files. The file system always allocates more space to write a file, but sometimes the file size grows so that space becomes insufficient and the file is fragmented, but even so the file system fragments the file efficiently. Using defragfs: @@ -66,37 +72,49 @@ if (!(-e $DIR) || !(-d $DIR)) { die "You must specify a correct directory name! my $AUTO = 0; my $FORCE = 0; -if($ARG =~ m/a/) { $AUTO = 1; } -if($ARG =~ m/f/) { $FORCE = 1; } +if($ARG =~ m/(^|\s)-\S*a/) { $AUTO = 1; } +if($ARG =~ m/(^|\s)-\S*f/) { $FORCE = 1; } +if($ARG =~ m/-\S*([^afh ])\S*/) { + print "FATAL: unknown flag '$1' was used.\n"; + $NEEDHELP = 1; +} -if (($DIR eq "") || ($DIR =~ m/-/)) { die "Usage: defragfs.pl DIRECTORY [-af], -af means force automatic defragmentation +if (($DIR eq "") || ($DIR =~ m/^-/) || $NEEDHELP) { die "Usage: defragfs.pl DIRECTORY [-af], -af means force automatic defragmentation "; } start: -print ("Analysis in progress...\n\n"); +print ("Analysis in progress...\n"); my $files = 0; # number of files my $fragments = 0; # number of fragment before defrag my $fragfiles = 0; # number of fragmented files before defrag -my $TMP_filefrag_1 = "/tmp/frags-result-tmp"; -my $TMP_filefrag_2 = "/tmp/frags-result"; -my $TMP_defrag_filelist_1 = "/tmp/defrag-filelist-tmp"; -my $TMP_defrag_filelist_2 = "/tmp/defrag-filelist"; +my @hash_chars = ("0".."9", "A".."Z", "a".."z"); +my $random_chars = randomChars(4); +my $TMP_filefrag_1 = "/tmp/frags-result-tmp$random_chars"; +my $TMP_filefrag_2 = "/tmp/frags-result$random_chars"; +my $TMP_defrag_filelist_1 = "/tmp/defrag-filelist-tmp$random_chars"; +my $TMP_defrag_filelist_2 = "/tmp/defrag-filelist$random_chars"; my $max_fragrate = 1; # max "File Fragmentation Rate" used to determine whether worth defrag. my $max_avgfrags = 1.1; # max "Avg File Fragments" used to determine whether worth defrag. my $default_defrag_ratio; # default defragmentation ratio in percentage my $max_display_num = 10; # display how much files in report my $total_defrag_files = 0; # which files to be defrag, determined after user input the ratio my $max_tries = 1; # max "Max Tries" used to determine max attempts to defrag a file after first attempt. +# variables used during defrag process +my $defrag_ratio; +my $confirm; -system("rm -f $TMP_filefrag_1"); -system("rm -f $TMP_filefrag_2"); -system("rm -f $TMP_defrag_filelist_1"); -system("rm -f $TMP_defrag_filelist_2"); +unlink($TMP_filefrag_1, $TMP_filefrag_2, $TMP_defrag_filelist_1, $TMP_defrag_filelist_2); + +# cleanup on aborts +$SIG{'INT'} = $SIG{'QUIT'} = $SIG{'ABRT'} = $SIG{'TERM'} = \&abort; my $progress = 0; +my $starttime = time(); +my $lasttime = $starttime; open (FILES, "find \"" . $DIR . "\" -xdev -type f |"); while (defined (my $file = )) { + chomp($file); $file =~ s/!/\\!/g; $file =~ s/#/\\#/g; $file =~ s/&/\\&/g; @@ -105,27 +123,44 @@ while (defined (my $file = )) { $file =~ s/\$/\\\$/g; $file =~ s/\(/\\\(/g; $file =~ s/\)/\\\)/g; + $file =~ s/\;/\\\;/g; $file =~ s/\|/\\\|/g; $file =~ s/'/\\'/g; + $file =~ s/"/\\"/g; $file =~ s/ /\\ /g; open (FRAG, "filefrag $file |"); my $res = ; if ($res =~ m/.*:\s+(\d+) extents? found/) { my $fragment = $1; - if ($fragment eq 0) { $fragment = 1; } + if ($fragment eq 0) { + my $size = (stat($file))[7]; + if ($size > 0) { + printf("\rSystem reports zero fragments for '$file' with size $size bytes. Perhaps file is locked, in use, or missing permissions?\n"); + } + $fragment = 1; + } $fragments+=$fragment; if ($fragment > 1) { system("echo -n \"$res\" >> $TMP_filefrag_1"); $fragfiles++; } $files++; + } else { + printf("\rFailed to parse fragmentation for: $file\n"); } close (FRAG); - if (($progress++ % 1000) eq 0) { print "."; } + my $now = time(); + if ($now != $lasttime) { + my $et = $now - $starttime; + my $rate = int($progress / $et); + print "\r $progress files analyzed in $et seconds @ $rate files/sec"; + $lasttime = $now; + } + if (($progress++ % 100) == 0) { print ". \b"; } } close (FILES); -if ($files eq 0) { +if ($files == 0) { print ("The selected directory contains no file!\n"); exit; } @@ -148,10 +183,10 @@ if ($fragfiles > 0) { exit; } -$default_defrag_ratio = ($files eq 1) ? 100 : ($fragfiles / $files) * 100; +$default_defrag_ratio = ($files == 1) ? 100 : ($fragfiles / $files) * 100; if (((($fragfiles / $files) * 100) > $max_fragrate) || (($fragments / $files) > $max_avgfrags) || ($FORCE)) { - print ("\nYou need a defragmentation or Your are using -f parameter!\n"); + print ("\nYou need a defragmentation or you are using -f parameter!\n"); } else { print ("\nYou do not need a defragmentation!\n"); exit; @@ -183,14 +218,16 @@ while () { my $filename = $1; system("echo \"$1\" >> $TMP_defrag_filelist_2"); } +close TMPFRAGLIST; open (GETSIZE, "$TMP_defrag_filelist_2"); my $max = 0; while () { s/(.*)\n/$1/; - $size = -s "$_"; + my $size = -s "$_"; if ($size > $max) { $max = $size; } } +close GETSIZE; print ("You need AT LEAST " . sprintf("%.3f", $max / 1048576) . " Megabytes temporarily used for defragmentation (at the directory where you specified), continue (Y/N)? [Y] "); if ($AUTO) { $confirm = "" } @@ -201,13 +238,16 @@ if (($confirm eq "y") || ($confirm eq "Y") || ($confirm eq "")) { print ("\nOK, please drink a cup of tea and wait...\n"); print ("\nFile Number - File Name (Size Mb) [actual extents] - extents after defrag attempt\n"); my $actual_file = $total_defrag_files; + my $total_fragments_eliminated = 0; open (DEFRAG, "$TMP_defrag_filelist_2"); while () { s/(.*)\n/$1/; - $from = $_; - s/(.*)/$1.ft/; - $to = $_; + my $from = $_; + my $to = $1 . ".defrag_tmp$random_chars"; + while (-e $to) { + $to .= randomChars(1); + } my $i_file = $actual_file; my $i_tries = 0; @@ -218,6 +258,7 @@ if (($confirm eq "y") || ($confirm eq "Y") || ($confirm eq "")) { open (FRAG, "-|", "filefrag", $from); $res = ; + close FRAG; if ($res =~ m/.*:\s+(\d+) extents? found/) { $fragment_from = $1; } if ($i_file eq $actual_file) { @@ -239,11 +280,13 @@ if (($confirm eq "y") || ($confirm eq "Y") || ($confirm eq "")) { } else { open (FRAG, "-|", "filefrag", $to); $res = ; + close FRAG; if ($res =~ m/.*:\s+(\d+) extents? found/) { $fragment_to = $1; } if ($fragment_to <= $fragment_from) { # <= not just < system("mv -f \"$to\" \"$from\" 2>/dev/null"); print "$fragment_to "; + $total_fragments_eliminated += ($fragment_from - $fragment_to); if ($fragment_to eq 1) { last; } if ($fragment_to eq $fragment_from) { $i_tries++; } if ($fragment_to < $fragment_from) { $i_tries--; } @@ -255,9 +298,12 @@ if (($confirm eq "y") || ($confirm eq "Y") || ($confirm eq "")) { system("sync"); } } + close DEFRAG; + + unlink($TMP_filefrag_1, $TMP_filefrag_2, $TMP_defrag_filelist_1, $TMP_defrag_filelist_2); system("sync"); - print ("\n\nDone!\n\n"); + print ("\n\nDone - Eliminated $total_fragments_eliminated excess fragments this pass.\n\n"); if ($AUTO) { exit; } else { goto start; } @@ -265,3 +311,18 @@ if (($confirm eq "y") || ($confirm eq "Y") || ($confirm eq "")) { exit; } +sub randomChars { + my $cnt = shift; + my $str = ""; + for(;$cnt>0;$cnt--) { + $str .= $hash_chars[rand @hash_chars]; + } + return $str; +} + +sub abort { + print "interrupt signaled, cleaning up...\n"; + unlink($TMP_filefrag_1, $TMP_filefrag_2, $TMP_defrag_filelist_1, $TMP_defrag_filelist_2); + exit(1); +} +