Subversion Repositories checksum-tools

Rev

Rev 5 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 daniel-mar 1
#!/usr/bin/php
2
<?php
3
 
4
/*
5
   Copyright 2020 Daniel Marschall, ViaThinkSoft
6
 
7
   Licensed under the Apache License, Version 2.0 (the "License");
8
   you may not use this file except in compliance with the License.
9
   You may obtain a copy of the License at
10
 
11
       http://www.apache.org/licenses/LICENSE-2.0
12
 
13
   Unless required by applicable law or agreed to in writing, software
14
   distributed under the License is distributed on an "AS IS" BASIS,
15
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
   See the License for the specific language governing permissions and
17
   limitations under the License.
18
*/
19
 
4 daniel-mar 20
// TODO: make use of STDERR and return different exit codes
5 daniel-mar 21
// TODO: On Windows file systems, accept file names case insensitively
4 daniel-mar 22
 
5 daniel-mar 23
function utf8_normalize($str) {
24
        // This helps to handle decomposite Unicode endpoints (E.g. German Umlauts have different representations)
25
        // Requires php-intl
26
        if (!class_exists('Normalizer')) return $str;
27
        return Normalizer::normalize($str);
28
}
29
 
30
function convertToUTF8($str) {
31
        $enc = mb_detect_encoding($str);
32
        if ($enc && $enc != 'UTF-8') {
33
                return iconv($enc, 'UTF-8', $str);
34
        } else {
35
                return $str;
36
        }
37
}
38
 
2 daniel-mar 39
function testmd5($file) {
40
        // TODO: warn if an entry is multiple times (with different checksums) in a single file
41
        if (!file_exists($file)) {
42
                echo "ERROR: File $file does not exist.\n";
43
                return;
44
        }
45
 
14 daniel-mar 46
        $files_checked = array();
47
 
2 daniel-mar 48
        $lines = file($file);
4 daniel-mar 49
        $is_first_line = true;
50
        $force_utf8 = false;
2 daniel-mar 51
        foreach ($lines as $line) {
4 daniel-mar 52
                if ($is_first_line) {
53
                        $tmp = 0;
54
                        $line = str_replace("\xEF\xBB\xBF",'',$line,$tmp);
55
                        if ($tmp > 0) $force_utf8 = true;
56
                        $is_first_line = false;
57
                }
5 daniel-mar 58
                if (!$force_utf8) $line = convertToUTF8($line);
4 daniel-mar 59
 
5 daniel-mar 60
                if (substr(trim($line),0,1) == ';') continue;
61
 
2 daniel-mar 62
                $line = trim($line);
63
                if ($line == '') continue;
64
                $line = str_replace('*', ' ', $line);
65
                $line = str_replace("\t", ' ', $line);
66
                list($checksum, $origname) = explode(' ', $line, 2);
67
                $origname = dirname($file) . '/' . trim($origname);
68
                $checksum = trim($checksum);
69
                if (!file_exists($origname)) {
70
                        echo "WARNING: File vanished : $origname\n";
71
                } else {
5 daniel-mar 72
                        if (is_file($origname)) {
73
                                $checksum2 = md5_file($origname);
74
                                if (strtolower($checksum) != strtolower($checksum2)) {
75
                                        echo "CHECKSUM FAIL: $origname (expected $checksum, but is $checksum2)\n";
76
                                } else {
77
                                        global $show_verbose;
78
                                        if ($show_verbose) echo "OK: $origname\n";
79
                                }
2 daniel-mar 80
                        } else {
5 daniel-mar 81
                                // For some reason, some files on a NTFS volume are "FIFO" pipe files?!
82
                                echo "Warning: $origname is not a regular file!\n";
2 daniel-mar 83
                        }
84
                }
5 daniel-mar 85
 
86
                $origname = utf8_normalize(basename($origname));
87
                $files_checked[] = dirname($file) . '/' . $origname;
2 daniel-mar 88
        }
5 daniel-mar 89
 
90
        // Now check if files have vanished!
91
        $directory = dirname($file);
92
        $sd = @scandir($directory);
93
        if ($sd === false) {
94
                echo "Error: Cannot scan directory $directory\n";
95
        } else {
96
                foreach ($sd as $file) {
97
                        if ($file === '.') continue;
98
                        if ($file === '..') continue;
99
                        if (strtolower($file) === 'thumbs.db') continue;
100
                        if (strtolower(substr($file, -4)) === '.md5') continue;
101
                        if (strtolower(substr($file, -4)) === '.sfv') continue;
102
                        $fullpath = $directory . '/' . $file;
103
                        if (!is_dir($fullpath)) {
104
                                $fullpath = utf8_normalize($fullpath);
105
                                if (!in_array($fullpath,$files_checked)) {
106
                                        echo "Warning: File not in SFV checksum file: $fullpath\n";
107
                                }
108
                        }
109
                }
110
        }
2 daniel-mar 111
}
112
 
113
function _rec($directory) {
4 daniel-mar 114
        $directory = rtrim($directory, '/\\');
115
 
2 daniel-mar 116
        if (!is_dir($directory)) {
117
                exit("Invalid directory path $directory\n");
118
        }
119
 
120
        if ($dont_add_files = count(glob("$directory/*.md5")) == 0) {
4 daniel-mar 121
                global $show_verbose;
122
                if ($show_verbose) echo "Directory $directory has no MD5 file. Skipping.\n";
2 daniel-mar 123
        } else {
124
                $out = array();
125
 
4 daniel-mar 126
                global $show_verbose;
127
                if ($show_verbose) echo "Check directory $directory\n";
128
                $md5files = glob($directory.'/*.md5');
2 daniel-mar 129
                foreach ($md5files as $md5file) {
130
                        testmd5($md5file);
131
                }
132
        }
133
 
4 daniel-mar 134
        $sd = @scandir($directory);
135
        if ($sd === false) {
136
                echo "Error: Cannot scan directory $directory\n";
137
                return;
138
        }
139
 
140
        foreach ($sd as $file) {
2 daniel-mar 141
                if ($file !== '.' && $file !== '..') {
142
                        $file = $directory . '/' . $file;
143
                        if (is_dir($file)) {
144
                                _rec($file);
145
                        }
146
                }
147
        }
148
}
149
 
150
 
151
# ---
152
 
4 daniel-mar 153
$show_verbose = false;
154
$dir = '';
155
 
156
for ($i=1; $i<$argc; $i++) {
157
        if ($argv[$i] == '-v') {
158
                $show_verbose = true;
159
        } else {
160
                $dir = $argv[$i];
161
        }
162
}
163
 
164
if (empty($dir)) {
165
        echo "Syntax: $argv[0] [-v] <directory>\n";
2 daniel-mar 166
        exit(2);
167
}
168
 
4 daniel-mar 169
if (!is_dir($dir)) {
2 daniel-mar 170
        echo "Directory not found\n";
171
        exit(1);
172
}
173
 
4 daniel-mar 174
_rec($dir);
2 daniel-mar 175
 
4 daniel-mar 176
if ($show_verbose) echo "Done.\n";