File Coverage

ext/xxHash/xxhsum.c
Criterion Covered Total %
statement 0 427 0.0
branch 0 240 0.0
condition n/a
subroutine n/a
pod n/a
total 0 667 0.0


line stmt bran cond sub pod time code
1             /*
2             * xxhsum - Command line interface for xxhash algorithms
3             * Copyright (C) Yann Collet 2012-2016
4             *
5             * GPL v2 License
6             *
7             * This program is free software; you can redistribute it and/or modify
8             * it under the terms of the GNU General Public License as published by
9             * the Free Software Foundation; either version 2 of the License, or
10             * (at your option) any later version.
11             *
12             * This program is distributed in the hope that it will be useful,
13             * but WITHOUT ANY WARRANTY; without even the implied warranty of
14             * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15             * GNU General Public License for more details.
16             *
17             * You should have received a copy of the GNU General Public License along
18             * with this program; if not, write to the Free Software Foundation, Inc.,
19             * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20             *
21             * You can contact the author at :
22             * - xxHash homepage : http://www.xxhash.com
23             * - xxHash source repository : https://github.com/Cyan4973/xxHash
24             */
25              
26             /* xxhsum :
27             * Provides hash value of a file content, or a list of files, or stdin
28             * Display convention is Big Endian, for both 32 and 64 bits algorithms
29             */
30              
31              
32             /* ************************************
33             * Compiler Options
34             **************************************/
35             /* MS Visual */
36             #if defined(_MSC_VER) || defined(_WIN32)
37             # define _CRT_SECURE_NO_WARNINGS /* removes visual warnings */
38             #endif
39              
40             /* Under Linux at least, pull in the *64 commands */
41             #ifndef _LARGEFILE64_SOURCE
42             # define _LARGEFILE64_SOURCE
43             #endif
44              
45              
46             /* ************************************
47             * Includes
48             **************************************/
49             #include /* malloc */
50             #include /* fprintf, fopen, ftello64, fread, stdin, stdout; when present : _fileno */
51             #include /* strcmp */
52             #include /* stat64 */
53             #include /* stat64 */
54             #include /* clock_t, clock, CLOCKS_PER_SEC */
55              
56             #define XXH_STATIC_LINKING_ONLY
57             #if defined(XXHSUM_INCLUDE_XXHC) /* compile xxhsum with xxhash as private (no public symbol) */
58             # define XXH_PRIVATE_API
59             # include "xxhash.c"
60             #else
61             # include "xxhash.h"
62             #endif
63              
64              
65             /*-************************************
66             * OS-Specific Includes
67             **************************************/
68             #if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(_WIN32) || defined(__CYGWIN__)
69             # include /* _O_BINARY */
70             # include /* _setmode, _isatty */
71             # ifdef __MINGW32__
72             int _fileno(FILE *stream); /* MINGW somehow forgets to include this windows declaration into */
73             # endif
74             # define SET_BINARY_MODE(file) _setmode(_fileno(file), _O_BINARY)
75             # define IS_CONSOLE(stdStream) _isatty(_fileno(stdStream))
76             #else
77             # include /* isatty, STDIN_FILENO */
78             # define SET_BINARY_MODE(file)
79             # define IS_CONSOLE(stdStream) isatty(STDIN_FILENO)
80             #endif
81              
82             #if !defined(S_ISREG)
83             # define S_ISREG(x) (((x) & S_IFMT) == S_IFREG)
84             #endif
85              
86              
87             /* ************************************
88             * Basic Types
89             **************************************/
90             #ifndef MEM_MODULE
91             # define MEM_MODULE
92             # if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */
93             # include
94             typedef uint8_t BYTE;
95             typedef uint16_t U16;
96             typedef uint32_t U32;
97             typedef int32_t S32;
98             typedef uint64_t U64;
99             # else
100             typedef unsigned char BYTE;
101             typedef unsigned short U16;
102             typedef unsigned int U32;
103             typedef signed int S32;
104             typedef unsigned long long U64;
105             # endif
106             #endif
107              
108             static unsigned BMK_isLittleEndian(void)
109             {
110             const union { U32 u; BYTE c[4]; } one = { 1 }; /* don't use static : performance detrimental */
111             return one.c[0];
112             }
113              
114              
115             /* *************************************
116             * Constants
117             ***************************************/
118             #define LIB_VERSION XXH_VERSION_MAJOR.XXH_VERSION_MINOR.XXH_VERSION_RELEASE
119             #define QUOTE(str) #str
120             #define EXPAND_AND_QUOTE(str) QUOTE(str)
121             #define PROGRAM_VERSION EXPAND_AND_QUOTE(LIB_VERSION)
122             static const int g_nbBits = (int)(sizeof(void*)*8);
123             static const char g_lename[] = "little endian";
124             static const char g_bename[] = "big endian";
125             #define ENDIAN_NAME (BMK_isLittleEndian() ? g_lename : g_bename)
126             #define COMPILED __DATE__
127             static const char author[] = "Yann Collet";
128             #define WELCOME_MESSAGE(exename) "%s %s (%i-bits %s), by %s (%s) \n", exename, PROGRAM_VERSION, g_nbBits, ENDIAN_NAME, author, COMPILED
129              
130             #define NBLOOPS 3 /* Default number of benchmark iterations */
131             #define TIMELOOP_S 1
132             #define TIMELOOP (TIMELOOP_S * CLOCKS_PER_SEC) /* Minimum timing per iteration */
133             #define XXHSUM32_DEFAULT_SEED 0 /* Default seed for algo_xxh32 */
134             #define XXHSUM64_DEFAULT_SEED 0 /* Default seed for algo_xxh64 */
135              
136             #define KB *( 1<<10)
137             #define MB *( 1<<20)
138             #define GB *(1U<<30)
139              
140             #define MAX_MEM (2 GB - 64 MB)
141              
142             static const char stdinName[] = "-";
143             typedef enum { algo_xxh32, algo_xxh64 } algoType;
144             static const algoType g_defaultAlgo = algo_xxh64; /* required within main() & usage() */
145              
146             /* <16 hex char> <'\0'>
147             * '4096' is typical Linux PATH_MAX configuration. */
148             #define DEFAULT_LINE_LENGTH (sizeof(XXH64_hash_t) * 2 + 2 + 4096 + 1)
149              
150             /* Maximum acceptable line length. */
151             #define MAX_LINE_LENGTH (32 KB)
152              
153              
154             /* ************************************
155             * Display macros
156             **************************************/
157             #define DISPLAY(...) fprintf(stderr, __VA_ARGS__)
158             #define DISPLAYRESULT(...) fprintf(stdout, __VA_ARGS__)
159             #define DISPLAYLEVEL(l, ...) if (g_displayLevel>=l) DISPLAY(__VA_ARGS__);
160             static U32 g_displayLevel = 1;
161              
162              
163             /* ************************************
164             * Local variables
165             **************************************/
166             static size_t g_sampleSize = 100 KB;
167             static U32 g_nbIterations = NBLOOPS;
168              
169              
170             /* ************************************
171             * Benchmark Functions
172             **************************************/
173             static clock_t BMK_clockSpan( clock_t start )
174             {
175 0           return clock() - start; /* works even if overflow; Typical max span ~ 30 mn */
176             }
177              
178              
179 0           static size_t BMK_findMaxMem(U64 requiredMem)
180             {
181             size_t const step = 64 MB;
182             BYTE* testmem = NULL;
183              
184 0           requiredMem = (((requiredMem >> 26) + 1) << 26);
185 0           requiredMem += 2*step;
186 0 0         if (requiredMem > MAX_MEM) requiredMem = MAX_MEM;
187              
188 0 0         while (!testmem) {
189 0 0         if (requiredMem > step) requiredMem -= step;
190 0           else requiredMem >>= 1;
191 0           testmem = (BYTE*) malloc ((size_t)requiredMem);
192             }
193 0           free (testmem);
194              
195             /* keep some space available */
196 0 0         if (requiredMem > step) requiredMem -= step;
197 0           else requiredMem >>= 1;
198              
199 0           return (size_t)requiredMem;
200             }
201              
202              
203 0           static U64 BMK_GetFileSize(const char* infilename)
204             {
205             int r;
206             #if defined(_MSC_VER)
207             struct _stat64 statbuf;
208             r = _stat64(infilename, &statbuf);
209             #else
210             struct stat statbuf;
211             r = stat(infilename, &statbuf);
212             #endif
213 0 0         if (r || !S_ISREG(statbuf.st_mode)) return 0; /* No good... */
    0          
214 0           return (U64)statbuf.st_size;
215             }
216              
217             typedef void (*hashFunction)(const void* buffer, size_t bufferSize);
218              
219 0           static void localXXH32(const void* buffer, size_t bufferSize) { XXH32(buffer, bufferSize, 0); }
220              
221 0           static void localXXH64(const void* buffer, size_t bufferSize) { XXH64(buffer, bufferSize, 0); }
222              
223 0           static void BMK_benchHash(hashFunction h, const char* hName, const void* buffer, size_t bufferSize)
224             {
225             static const U32 nbh_perloop = 100;
226             U32 iterationNb;
227             double fastestH = 100000000.;
228              
229 0           DISPLAY("\r%79s\r", ""); /* Clean display line */
230 0 0         if (g_nbIterations<1) g_nbIterations=1;
231 0 0         for (iterationNb = 1; iterationNb <= g_nbIterations; iterationNb++) {
232             U32 nbHashes = 0;
233             clock_t cStart;
234              
235 0           DISPLAY("%1i-%-17.17s : %10u ->\r", iterationNb, hName, (U32)bufferSize);
236 0           cStart = clock();
237 0 0         while (clock() == cStart); /* starts clock() at its exact beginning */
238 0           cStart = clock();
239              
240 0 0         while (BMK_clockSpan(cStart) < TIMELOOP) {
241             U32 i;
242 0 0         for (i=0; i
243 0           h(buffer, bufferSize);
244 0           nbHashes += nbh_perloop;
245             }
246 0           { double const timeS = ((double)BMK_clockSpan(cStart) / CLOCKS_PER_SEC) / nbHashes;
247 0 0         if (timeS < fastestH) fastestH = timeS;
248 0           DISPLAY("%1i-%-17.17s : %10u -> %7.1f MB/s\r", iterationNb, hName, (U32)bufferSize, ((double)bufferSize / (1<<20)) / fastestH );
249             }
250             }
251 0           DISPLAY("%-19.19s : %10u -> %7.1f MB/s \n", hName, (U32)bufferSize, ((double)bufferSize / (1<<20)) / fastestH);
252 0           }
253              
254              
255             /* Note : buffer is supposed malloc'ed, hence aligned */
256 0           static void BMK_benchMem(const void* buffer, size_t bufferSize)
257             {
258             /* XXH32 bench */
259 0           BMK_benchHash(localXXH32, "XXH32", buffer, bufferSize);
260              
261             /* Bench XXH32 on Unaligned input */
262 0 0         if (bufferSize>1)
263 0           BMK_benchHash(localXXH32, "XXH32 unaligned", ((const char*)buffer)+1, bufferSize-1);
264              
265             /* Bench XXH64 */
266 0           BMK_benchHash(localXXH64, "XXH64", buffer, bufferSize);
267              
268             /* Bench XXH64 on Unaligned input */
269 0 0         if (bufferSize>1)
270 0           BMK_benchHash(localXXH64, "XXH64 unaligned", ((const char*)buffer)+1, bufferSize-1);
271 0           }
272              
273              
274 0           static int BMK_benchFiles(const char** fileNamesTable, int nbFiles)
275             {
276             int fileIdx=0;
277              
278 0 0         while (fileIdx
279             FILE* inFile;
280             const char* inFileName;
281             size_t benchedSize;
282             char* buffer;
283             char* alignedBuffer;
284              
285             /* Check file existence */
286 0           inFileName = fileNamesTable[fileIdx++];
287 0           inFile = fopen( inFileName, "rb" );
288 0 0         if ((inFile==NULL) || (inFileName==NULL)) {
289 0           DISPLAY( "Pb opening %s\n", inFileName);
290 0           return 11;
291             }
292              
293             /* Memory allocation & restrictions */
294 0           { U64 const inFileSize = BMK_GetFileSize(inFileName);
295 0           benchedSize = (size_t) BMK_findMaxMem(inFileSize);
296 0 0         if ((U64)benchedSize > inFileSize) benchedSize = (size_t)inFileSize;
297 0 0         if (benchedSize < inFileSize) {
298 0           DISPLAY("Not enough memory for '%s' full size; testing %i MB only...\n", inFileName, (int)(benchedSize>>20));
299             } }
300              
301 0           buffer = (char*)malloc((size_t )benchedSize+16);
302 0 0         if(!buffer) {
303 0           DISPLAY("\nError: not enough memory!\n");
304 0           fclose(inFile);
305 0           return 12;
306             }
307 0           alignedBuffer = (buffer+15) - (((size_t)(buffer+15)) & 0xF); /* align on next 16 bytes boundaries */
308              
309             /* Fill input buffer */
310 0           DISPLAY("\rLoading %s... \n", inFileName);
311             { size_t const readSize = fread(alignedBuffer, 1, benchedSize, inFile);
312 0           fclose(inFile);
313 0 0         if(readSize != benchedSize) {
314 0           DISPLAY("\nError: problem reading file '%s' !! \n", inFileName);
315 0           free(buffer);
316 0           return 13;
317             } }
318              
319             /* bench */
320 0           BMK_benchMem(alignedBuffer, benchedSize);
321              
322 0           free(buffer);
323             }
324              
325             return 0;
326             }
327              
328              
329              
330 0           static int BMK_benchInternal(void)
331             {
332 0           size_t const benchedSize = g_sampleSize;
333 0           void* buffer = malloc(benchedSize);
334 0 0         if(!buffer) {
335 0           DISPLAY("\nError: not enough memory!\n");
336 0           return 12;
337             }
338              
339             /* bench */
340 0           DISPLAY("\rSample of %u KB... \n", (U32)(benchedSize >> 10));
341 0           BMK_benchMem(buffer, benchedSize);
342              
343 0           free(buffer);
344 0           return 0;
345             }
346              
347              
348 0           static void BMK_checkResult(U32 r1, U32 r2)
349             {
350             static int nbTests = 1;
351 0 0         if (r1==r2) DISPLAY("\rTest%3i : %08X == %08X ok ", nbTests, r1, r2);
352             else {
353 0           DISPLAY("\rERROR : Test%3i : %08X <> %08X !!!!! \n", nbTests, r1, r2);
354 0           exit(1);
355             }
356 0           nbTests++;
357 0           }
358              
359              
360 0           static void BMK_checkResult64(U64 r1, U64 r2)
361             {
362             static int nbTests = 1;
363 0 0         if (r1!=r2) {
364 0           DISPLAY("\rERROR : Test%3i : 64-bits values non equals !!!!! \n", nbTests);
365 0           DISPLAY("\r %08X%08X != %08X%08X \n", (U32)(r1>>32), (U32)r1, (U32)(r2>>32), (U32)r2);
366 0           exit(1);
367             }
368 0           nbTests++;
369 0           }
370              
371              
372 0           static void BMK_testSequence64(void* sentence, int len, U64 seed, U64 Nresult)
373             {
374             XXH64_state_t state;
375             U64 Dresult;
376             int index;
377              
378 0           Dresult = XXH64(sentence, len, seed);
379 0           BMK_checkResult64(Dresult, Nresult);
380              
381 0           XXH64_reset(&state, seed);
382 0           XXH64_update(&state, sentence, len);
383 0           Dresult = XXH64_digest(&state);
384 0           BMK_checkResult64(Dresult, Nresult);
385              
386 0           XXH64_reset(&state, seed);
387 0 0         for (index=0; index
388 0           Dresult = XXH64_digest(&state);
389 0           BMK_checkResult64(Dresult, Nresult);
390 0           }
391              
392              
393 0           static void BMK_testSequence(const void* sequence, size_t len, U32 seed, U32 Nresult)
394             {
395             XXH32_state_t state;
396             U32 Dresult;
397             size_t index;
398              
399 0           Dresult = XXH32(sequence, len, seed);
400 0           BMK_checkResult(Dresult, Nresult);
401              
402 0           XXH32_reset(&state, seed);
403 0           XXH32_update(&state, sequence, len);
404 0           Dresult = XXH32_digest(&state);
405 0           BMK_checkResult(Dresult, Nresult);
406              
407 0           XXH32_reset(&state, seed);
408 0 0         for (index=0; index
409 0           Dresult = XXH32_digest(&state);
410 0           BMK_checkResult(Dresult, Nresult);
411 0           }
412              
413              
414             #define SANITY_BUFFER_SIZE 101
415 0           static void BMK_sanityCheck(void)
416             {
417             static const U32 prime = 2654435761U;
418             BYTE sanityBuffer[SANITY_BUFFER_SIZE];
419             U32 byteGen = prime;
420              
421             int i;
422 0 0         for (i=0; i
423 0           sanityBuffer[i] = (BYTE)(byteGen>>24);
424 0           byteGen *= byteGen;
425             }
426              
427 0           BMK_testSequence(NULL, 0, 0, 0x02CC5D05);
428 0           BMK_testSequence(NULL, 0, prime, 0x36B78AE7);
429 0           BMK_testSequence(sanityBuffer, 1, 0, 0xB85CBEE5);
430 0           BMK_testSequence(sanityBuffer, 1, prime, 0xD5845D64);
431 0           BMK_testSequence(sanityBuffer, 14, 0, 0xE5AA0AB4);
432 0           BMK_testSequence(sanityBuffer, 14, prime, 0x4481951D);
433 0           BMK_testSequence(sanityBuffer, SANITY_BUFFER_SIZE, 0, 0x1F1AA412);
434 0           BMK_testSequence(sanityBuffer, SANITY_BUFFER_SIZE, prime, 0x498EC8E2);
435              
436 0           BMK_testSequence64(NULL , 0, 0, 0xEF46DB3751D8E999ULL);
437 0           BMK_testSequence64(NULL , 0, prime, 0xAC75FDA2929B17EFULL);
438 0           BMK_testSequence64(sanityBuffer, 1, 0, 0x4FCE394CC88952D8ULL);
439 0           BMK_testSequence64(sanityBuffer, 1, prime, 0x739840CB819FA723ULL);
440 0           BMK_testSequence64(sanityBuffer, 14, 0, 0xCFFA8DB881BC3A3DULL);
441 0           BMK_testSequence64(sanityBuffer, 14, prime, 0x5B9611585EFCC9CBULL);
442 0           BMK_testSequence64(sanityBuffer, SANITY_BUFFER_SIZE, 0, 0x0EAB543384F878ADULL);
443 0           BMK_testSequence64(sanityBuffer, SANITY_BUFFER_SIZE, prime, 0xCAA65939306F1E21ULL);
444              
445 0           DISPLAY("\r%79s\r", ""); /* Clean display line */
446 0 0         DISPLAYLEVEL(2, "Sanity check -- all tests ok\n");
447 0           }
448              
449              
450             /* ********************************************************
451             * File Hashing
452             **********************************************************/
453              
454 0           static void BMK_display_LittleEndian(const void* ptr, size_t length)
455             {
456             const BYTE* p = (const BYTE*)ptr;
457             size_t index;
458 0 0         for (index=length-1; index
459 0           DISPLAYRESULT("%02x", p[index]);
460 0           }
461              
462 0           static void BMK_display_BigEndian(const void* ptr, size_t length)
463             {
464             const BYTE* p = (const BYTE*)ptr;
465             size_t index;
466 0 0         for (index=0; index
467 0           DISPLAYRESULT("%02x", p[index]);
468 0           }
469              
470 0           static void BMK_hashStream(void* xxhHashValue, const algoType hashType, FILE* inFile, void* buffer, size_t blockSize)
471             {
472             XXH64_state_t state64;
473             XXH32_state_t state32;
474             size_t readSize;
475              
476             /* Init */
477 0           XXH32_reset(&state32, XXHSUM32_DEFAULT_SEED);
478 0           XXH64_reset(&state64, XXHSUM64_DEFAULT_SEED);
479              
480             /* Load file & update hash */
481             readSize = 1;
482 0 0         while (readSize) {
483             readSize = fread(buffer, 1, blockSize, inFile);
484 0           switch(hashType)
485             {
486             case algo_xxh32:
487 0           XXH32_update(&state32, buffer, readSize);
488 0           break;
489             case algo_xxh64:
490 0           XXH64_update(&state64, buffer, readSize);
491 0           break;
492             default:
493             break;
494             }
495             }
496              
497 0           switch(hashType)
498             {
499             case algo_xxh32:
500 0           { U32 const h32 = XXH32_digest(&state32);
501             memcpy(xxhHashValue, &h32, sizeof(h32));
502             break;
503             }
504             case algo_xxh64:
505 0           { U64 const h64 = XXH64_digest(&state64);
506             memcpy(xxhHashValue, &h64, sizeof(h64));
507             break;
508             }
509             default:
510             break;
511             }
512 0           }
513              
514              
515             typedef enum { big_endian, little_endian} endianess;
516              
517 0           static int BMK_hash(const char* fileName,
518             const algoType hashType,
519             const endianess displayEndianess)
520             {
521             FILE* inFile;
522             size_t const blockSize = 64 KB;
523             void* buffer;
524 0           U32 h32 = 0;
525 0           U64 h64 = 0;
526              
527             /* Check file existence */
528 0 0         if (fileName == stdinName) {
529 0           inFile = stdin;
530             SET_BINARY_MODE(stdin);
531             }
532             else
533 0           inFile = fopen( fileName, "rb" );
534 0 0         if (inFile==NULL) {
535 0           DISPLAY( "Pb opening %s\n", fileName);
536 0           return 1;
537             }
538              
539             /* Memory allocation & restrictions */
540 0           buffer = malloc(blockSize);
541 0 0         if(!buffer) {
542 0           DISPLAY("\nError: not enough memory!\n");
543 0           fclose(inFile);
544 0           return 1;
545             }
546              
547             /* loading notification */
548 0           { const size_t fileNameSize = strlen(fileName);
549 0           const char* const fileNameEnd = fileName + fileNameSize;
550 0           const size_t maxInfoFilenameSize = fileNameSize > 30 ? 30 : fileNameSize;
551             size_t infoFilenameSize = 1;
552 0 0         while ( (infoFilenameSize < maxInfoFilenameSize)
553 0 0         &&(fileNameEnd[-1-infoFilenameSize] != '/')
554 0 0         &&(fileNameEnd[-1-infoFilenameSize] != '\\') )
555 0           infoFilenameSize++;
556 0           DISPLAY("\rLoading %s... \r", fileNameEnd - infoFilenameSize);
557             }
558              
559             /* Load file & update hash */
560 0           switch(hashType)
561             {
562             case algo_xxh32:
563 0           BMK_hashStream(&h32, algo_xxh32, inFile, buffer, blockSize);
564 0           break;
565             case algo_xxh64:
566 0           BMK_hashStream(&h64, algo_xxh64, inFile, buffer, blockSize);
567 0           break;
568             default:
569             break;
570             }
571              
572 0           fclose(inFile);
573 0           free(buffer);
574              
575             /* display Hash */
576 0           switch(hashType)
577             {
578             case algo_xxh32:
579             { XXH32_canonical_t hcbe32;
580 0           XXH32_canonicalFromHash(&hcbe32, h32);
581             displayEndianess==big_endian ?
582 0 0         BMK_display_BigEndian(&hcbe32, sizeof(hcbe32)) : BMK_display_LittleEndian(&hcbe32, sizeof(hcbe32));
583 0           DISPLAYRESULT(" %s\n", fileName);
584             break;
585             }
586             case algo_xxh64:
587             { XXH64_canonical_t hcbe64;
588 0           XXH64_canonicalFromHash(&hcbe64, h64);
589             displayEndianess==big_endian ?
590 0 0         BMK_display_BigEndian(&hcbe64, sizeof(hcbe64)) : BMK_display_LittleEndian(&hcbe64, sizeof(hcbe64));
591 0           DISPLAYRESULT(" %s\n", fileName);
592             break;
593             }
594             default:
595             break;
596             }
597              
598             return 0;
599             }
600              
601              
602 0           static int BMK_hashFiles(const char** fnList, int fnTotal,
603             algoType hashType, endianess displayEndianess)
604             {
605             int fnNb;
606             int result = 0;
607              
608 0 0         if (fnTotal==0)
609 0           return BMK_hash(stdinName, hashType, displayEndianess);
610              
611 0 0         for (fnNb=0; fnNb
612 0           result += BMK_hash(fnList[fnNb], hashType, displayEndianess);
613 0           DISPLAY("\r%70s\r", "");
614 0           return result;
615             }
616              
617              
618             typedef enum {
619             GetLine_ok,
620             GetLine_eof,
621             GetLine_exceedMaxLineLength,
622             GetLine_outOfMemory,
623             } GetLineResult;
624              
625             typedef enum {
626             CanonicalFromString_ok,
627             CanonicalFromString_invalidFormat,
628             } CanonicalFromStringResult;
629              
630             typedef enum {
631             ParseLine_ok,
632             ParseLine_invalidFormat,
633             } ParseLineResult;
634              
635             typedef enum {
636             LineStatus_hashOk,
637             LineStatus_hashFailed,
638             LineStatus_failedToOpen,
639             } LineStatus;
640              
641             typedef union {
642             XXH32_canonical_t xxh32;
643             XXH64_canonical_t xxh64;
644             } Canonical;
645              
646             typedef struct {
647             Canonical canonical;
648             const char* filename;
649             int xxhBits; /* canonical type : 32:xxh32, 64:xxh64 */
650             } ParsedLine;
651              
652             typedef struct {
653             unsigned long nProperlyFormattedLines;
654             unsigned long nImproperlyFormattedLines;
655             unsigned long nMismatchedChecksums;
656             unsigned long nOpenOrReadFailures;
657             unsigned long nMixedFormatLines;
658             int xxhBits;
659             int quit;
660             } ParseFileReport;
661              
662             typedef struct {
663             const char* inFileName;
664             FILE* inFile;
665             int lineMax;
666             char* lineBuf;
667             size_t blockSize;
668             char* blockBuf;
669             int strictMode;
670             int statusOnly;
671             int warn;
672             int quiet;
673             ParseFileReport report;
674             } ParseFileArg;
675              
676              
677             /* Read line from stream.
678             Returns GetLine_ok, if it reads line successfully.
679             Returns GetLine_eof, if stream reaches EOF.
680             Returns GetLine_exceedMaxLineLength, if line length is longer than MAX_LINE_LENGTH.
681             Returns GetLine_outOfMemory, if line buffer memory allocation failed.
682             */
683 0           static GetLineResult getLine(char** lineBuf, int* lineMax, FILE* inFile)
684             {
685             GetLineResult result = GetLine_ok;
686             int len = 0;
687              
688 0 0         if (*lineBuf == NULL || *lineMax < 1) {
    0          
689 0           *lineMax = DEFAULT_LINE_LENGTH;
690 0           *lineBuf = (char*) realloc(*lineBuf, *lineMax);
691 0 0         if(*lineBuf == NULL) return GetLine_outOfMemory;
692             }
693              
694             for (;;) {
695 0           const int c = fgetc(inFile);
696 0 0         if (c == EOF) {
697             /* If we meet EOF before first character, returns GetLine_eof,
698             * otherwise GetLine_ok.
699             */
700 0 0         if (len == 0) result = GetLine_eof;
701             break;
702             }
703              
704             /* Make enough space for len+1 (for final NUL) bytes. */
705 0 0         if (len+1 >= *lineMax) {
706             char* newLineBuf = NULL;
707             int newBufSize = *lineMax;
708              
709 0           newBufSize += (newBufSize/2) + 1; /* x 1.5 */
710 0 0         if (newBufSize > MAX_LINE_LENGTH) newBufSize = MAX_LINE_LENGTH;
711 0 0         if (len+1 >= newBufSize) return GetLine_exceedMaxLineLength;
712              
713 0           newLineBuf = (char*) realloc(*lineBuf, newBufSize);
714 0 0         if (newLineBuf == NULL) return GetLine_outOfMemory;
715              
716 0           *lineBuf = newLineBuf;
717 0           *lineMax = newBufSize;
718             }
719              
720 0 0         if (c == '\n') break;
721 0           (*lineBuf)[len++] = (char) c;
722 0           }
723              
724 0           (*lineBuf)[len] = '\0';
725 0           return result;
726             }
727              
728              
729             /* Converts one hexadecimal character to integer.
730             * Returns -1, if given character is not hexadecimal.
731             */
732             static int charToHex(char c)
733             {
734             int result = -1;
735 0 0         if (c >= '0' && c <= '9') {
    0          
736 0           result = (int) (c - '0');
737 0 0         } else if (c >= 'A' && c <= 'F') {
    0          
738 0           result = (int) (c - 'A') + 0x0a;
739 0 0         } else if (c >= 'a' && c <= 'f') {
    0          
740 0           result = (int) (c - 'a') + 0x0a;
741             }
742             return result;
743             }
744              
745              
746             /* Converts XXH32 canonical hexadecimal string hashStr to big endian unsigned char array dst.
747             * Returns CANONICAL_FROM_STRING_INVALID_FORMAT, if hashStr is not well formatted.
748             * Returns CANONICAL_FROM_STRING_OK, if hashStr is parsed successfully.
749             */
750 0           static CanonicalFromStringResult canonicalFromString(unsigned char* dst,
751             size_t dstSize,
752             const char* hashStr)
753             {
754             size_t i;
755 0 0         for (i = 0; i < dstSize; ++i) {
756             int h0, h1;
757              
758 0           h0 = charToHex(hashStr[i*2 + 0]);
759 0 0         if (h0 < 0) return CanonicalFromString_invalidFormat;
760              
761 0           h1 = charToHex(hashStr[i*2 + 1]);
762 0 0         if (h1 < 0) return CanonicalFromString_invalidFormat;
763              
764 0           dst[i] = (unsigned char) ((h0 << 4) | h1);
765             }
766             return CanonicalFromString_ok;
767             }
768              
769              
770             /* Parse single line of xxHash checksum file.
771             * Returns PARSE_LINE_ERROR_INVALID_FORMAT, if line is not well formatted.
772             * Returns PARSE_LINE_OK if line is parsed successfully.
773             * And members of parseLine will be filled by parsed values.
774             *
775             * - line must be ended with '\0'.
776             * - Since parsedLine.filename will point within given argument `line`,
777             * users must keep `line`s content during they are using parsedLine.
778             *
779             * Given xxHash checksum line should have the following format:
780             *
781             * <8 or 16 hexadecimal char> <'\0'>
782             */
783 0           static ParseLineResult parseLine(ParsedLine* parsedLine, const char* line)
784             {
785 0           const char* const firstSpace = strchr(line, ' ');
786             const char* const secondSpace = firstSpace + 1;
787              
788 0           parsedLine->filename = NULL;
789 0           parsedLine->xxhBits = 0;
790              
791 0 0         if (firstSpace == NULL || *secondSpace != ' ') return ParseLine_invalidFormat;
    0          
792              
793 0           switch (firstSpace - line)
794             {
795             case 8:
796             { XXH32_canonical_t* xxh32c = &parsedLine->canonical.xxh32;
797 0 0         if (canonicalFromString(xxh32c->digest, sizeof(xxh32c->digest), line)
798             != CanonicalFromString_ok) {
799             return ParseLine_invalidFormat;
800             }
801 0           parsedLine->xxhBits = 32;
802 0           break;
803             }
804              
805             case 16:
806             { XXH64_canonical_t* xxh64c = &parsedLine->canonical.xxh64;
807 0 0         if (canonicalFromString(xxh64c->digest, sizeof(xxh64c->digest), line)
808             != CanonicalFromString_ok) {
809             return ParseLine_invalidFormat;
810             }
811 0           parsedLine->xxhBits = 64;
812 0           break;
813             }
814              
815             default:
816             return ParseLine_invalidFormat;
817             break;
818             }
819              
820 0           parsedLine->filename = secondSpace + 1;
821 0           return ParseLine_ok;
822             }
823              
824              
825             /*! Parse xxHash checksum file.
826             */
827 0           static void parseFile1(ParseFileArg* parseFileArg)
828             {
829 0           const char* const inFileName = parseFileArg->inFileName;
830 0           ParseFileReport* const report = &parseFileArg->report;
831              
832             unsigned long lineNumber = 0;
833             memset(report, 0, sizeof(*report));
834              
835 0 0         while (!report->quit) {
836             FILE* fp = NULL;
837             LineStatus lineStatus = LineStatus_hashFailed;
838             GetLineResult getLineResult;
839             ParsedLine parsedLine;
840             memset(&parsedLine, 0, sizeof(parsedLine));
841              
842 0           lineNumber++;
843 0 0         if (lineNumber == 0) {
844             /* This is unlikely happen, but md5sum.c has this
845             * error check. */
846 0           DISPLAY("%s : too many checksum lines\n", inFileName);
847 0           report->quit = 1;
848 0           break;
849             }
850              
851 0           getLineResult = getLine(&parseFileArg->lineBuf, &parseFileArg->lineMax,
852             parseFileArg->inFile);
853 0 0         if (getLineResult != GetLine_ok) {
854 0 0         if (getLineResult == GetLine_eof) break;
855              
856 0           switch (getLineResult)
857             {
858             case GetLine_ok:
859             case GetLine_eof:
860             /* These cases never happen. See above getLineResult related "if"s.
861             They exist just for make gcc's -Wswitch-enum happy. */
862             break;
863              
864             default:
865 0           DISPLAY("%s : %lu: unknown error\n", inFileName, lineNumber);
866             break;
867              
868             case GetLine_exceedMaxLineLength:
869 0           DISPLAY("%s : %lu: too long line\n", inFileName, lineNumber);
870             break;
871              
872             case GetLine_outOfMemory:
873 0           DISPLAY("%s : %lu: out of memory\n", inFileName, lineNumber);
874             break;
875             }
876 0           report->quit = 1;
877 0           break;
878             }
879              
880 0 0         if (parseLine(&parsedLine, parseFileArg->lineBuf) != ParseLine_ok) {
881 0           report->nImproperlyFormattedLines++;
882 0 0         if (parseFileArg->warn) {
883 0           DISPLAY("%s : %lu: improperly formatted XXHASH checksum line\n"
884             , inFileName, lineNumber);
885             }
886 0           continue;
887             }
888              
889 0 0         if (report->xxhBits != 0 && report->xxhBits != parsedLine.xxhBits) {
    0          
890             /* Don't accept xxh32/xxh64 mixed file */
891 0           report->nImproperlyFormattedLines++;
892 0           report->nMixedFormatLines++;
893 0 0         if (parseFileArg->warn) {
894 0           DISPLAY("%s : %lu: improperly formatted XXHASH checksum line (XXH32/64)\n"
895             , inFileName, lineNumber);
896             }
897 0           continue;
898             }
899              
900 0           report->nProperlyFormattedLines++;
901 0 0         if (report->xxhBits == 0) {
902 0           report->xxhBits = parsedLine.xxhBits;
903             }
904              
905 0           fp = fopen(parsedLine.filename, "rb");
906 0 0         if (fp == NULL) {
907             lineStatus = LineStatus_failedToOpen;
908             } else {
909             lineStatus = LineStatus_hashFailed;
910 0           switch (parsedLine.xxhBits)
911             {
912             case 32:
913             { XXH32_hash_t xxh;
914 0           BMK_hashStream(&xxh, algo_xxh32, fp, parseFileArg->blockBuf, parseFileArg->blockSize);
915 0 0         if (xxh == XXH32_hashFromCanonical(&parsedLine.canonical.xxh32)) {
916             lineStatus = LineStatus_hashOk;
917             } }
918 0           break;
919              
920             case 64:
921             { XXH64_hash_t xxh;
922 0           BMK_hashStream(&xxh, algo_xxh64, fp, parseFileArg->blockBuf, parseFileArg->blockSize);
923 0 0         if (xxh == XXH64_hashFromCanonical(&parsedLine.canonical.xxh64)) {
924             lineStatus = LineStatus_hashOk;
925             } }
926 0           break;
927              
928             default:
929             break;
930             }
931 0           fclose(fp);
932             }
933              
934 0           switch (lineStatus)
935             {
936             default:
937 0           DISPLAY("%s : unknown error\n", inFileName);
938 0           report->quit = 1;
939 0           break;
940              
941             case LineStatus_failedToOpen:
942 0           report->nOpenOrReadFailures++;
943 0 0         if (!parseFileArg->statusOnly) {
944 0           DISPLAYRESULT("%s : %lu: FAILED open or read %s\n"
945             , inFileName, lineNumber, parsedLine.filename);
946             }
947             break;
948              
949             case LineStatus_hashOk:
950             case LineStatus_hashFailed:
951             { int b = 1;
952 0 0         if (lineStatus == LineStatus_hashOk) {
953             /* If --quiet is specified, don't display "OK" */
954 0 0         if (parseFileArg->quiet) b = 0;
955             } else {
956 0           report->nMismatchedChecksums++;
957             }
958              
959 0 0         if (b && !parseFileArg->statusOnly) {
    0          
960 0 0         DISPLAYRESULT("%s: %s\n", parsedLine.filename
961             , lineStatus == LineStatus_hashOk ? "OK" : "FAILED");
962             } }
963             break;
964             }
965             } /* while (!report->quit) */
966 0           }
967              
968              
969             /* Parse xxHash checksum file.
970             * Returns 1, if all procedures were succeeded.
971             * Returns 0, if any procedures was failed.
972             *
973             * If strictMode != 0, return error code if any line is invalid.
974             * If statusOnly != 0, don't generate any output.
975             * If warn != 0, print a warning message to stderr.
976             * If quiet != 0, suppress "OK" line.
977             *
978             * "All procedures are succeeded" means:
979             * - Checksum file contains at least one line and less than SIZE_T_MAX lines.
980             * - All files are properly opened and read.
981             * - All hash values match with its content.
982             * - (strict mode) All lines in checksum file are consistent and well formatted.
983             *
984             */
985 0           static int checkFile(const char* inFileName,
986             const endianess displayEndianess,
987             U32 strictMode,
988             U32 statusOnly,
989             U32 warn,
990             U32 quiet)
991             {
992             int result = 0;
993             FILE* inFile = NULL;
994             ParseFileArg parseFileArgBody;
995             ParseFileArg* const parseFileArg = &parseFileArgBody;
996             ParseFileReport* const report = &parseFileArg->report;
997              
998 0 0         if (displayEndianess != big_endian) {
999             /* Don't accept little endian */
1000 0           DISPLAY( "Check file mode doesn't support little endian\n" );
1001 0           return 0;
1002             }
1003              
1004             /* note : stdinName is special constant pointer. It is not a string. */
1005 0 0         if (inFileName == stdinName) {
1006             /* note : Since we expect text input for xxhash -c mode,
1007             * Don't set binary mode for stdin */
1008 0           inFile = stdin;
1009             } else {
1010 0           inFile = fopen( inFileName, "rt" );
1011             }
1012              
1013 0 0         if (inFile == NULL) {
1014 0           DISPLAY( "Pb opening %s\n", inFileName);
1015 0           return 0;
1016             }
1017              
1018 0           parseFileArg->inFileName = inFileName;
1019 0           parseFileArg->inFile = inFile;
1020 0           parseFileArg->lineMax = DEFAULT_LINE_LENGTH;
1021 0           parseFileArg->lineBuf = (char*) malloc((size_t) parseFileArg->lineMax);
1022 0           parseFileArg->blockSize = 64 * 1024;
1023 0           parseFileArg->blockBuf = (char*) malloc(parseFileArg->blockSize);
1024 0           parseFileArg->strictMode = strictMode;
1025 0           parseFileArg->statusOnly = statusOnly;
1026 0           parseFileArg->warn = warn;
1027 0           parseFileArg->quiet = quiet;
1028              
1029 0           parseFile1(parseFileArg);
1030              
1031 0           free(parseFileArg->blockBuf);
1032 0           free(parseFileArg->lineBuf);
1033              
1034 0 0         if (inFile != stdin) fclose(inFile);
1035              
1036             /* Show error/warning messages. All messages are copied from md5sum.c
1037             */
1038 0 0         if (report->nProperlyFormattedLines == 0) {
1039 0           DISPLAY("%s: no properly formatted XXHASH checksum lines found\n", inFileName);
1040 0 0         } else if (!statusOnly) {
1041 0 0         if (report->nImproperlyFormattedLines) {
1042 0           DISPLAYRESULT("%lu lines are improperly formatted\n"
1043             , report->nImproperlyFormattedLines);
1044             }
1045 0 0         if (report->nOpenOrReadFailures) {
1046 0           DISPLAYRESULT("%lu listed files could not be read\n"
1047             , report->nOpenOrReadFailures);
1048             }
1049 0 0         if (report->nMismatchedChecksums) {
1050 0           DISPLAYRESULT("%lu computed checksums did NOT match\n"
1051             , report->nMismatchedChecksums);
1052             } }
1053              
1054             /* Result (exit) code logic is copied from
1055             * gnu coreutils/src/md5sum.c digest_check() */
1056 0           result = report->nProperlyFormattedLines != 0
1057 0 0         && report->nMismatchedChecksums == 0
1058 0 0         && report->nOpenOrReadFailures == 0
1059 0 0         && (!strictMode || report->nImproperlyFormattedLines == 0)
    0          
1060 0 0         && report->quit == 0;
    0          
1061 0           return result;
1062             }
1063              
1064              
1065 0           static int checkFiles(const char** fnList, int fnTotal,
1066             const endianess displayEndianess,
1067             U32 strictMode,
1068             U32 statusOnly,
1069             U32 warn,
1070             U32 quiet)
1071             {
1072             int ok = 1;
1073              
1074             /* Special case for stdinName "-",
1075             * note: stdinName is not a string. It's special pointer. */
1076 0 0         if (fnTotal==0) {
1077 0           ok &= checkFile(stdinName, displayEndianess, strictMode, statusOnly, warn, quiet);
1078             } else {
1079             int fnNb;
1080 0 0         for (fnNb=0; fnNb
1081 0           ok &= checkFile(fnList[fnNb], displayEndianess, strictMode, statusOnly, warn, quiet);
1082             }
1083 0           return ok ? 0 : 1;
1084             }
1085              
1086              
1087             /* ********************************************************
1088             * Main
1089             **********************************************************/
1090              
1091 0           static int usage(const char* exename)
1092             {
1093 0           DISPLAY( WELCOME_MESSAGE(exename) );
1094 0           DISPLAY( "Usage :\n");
1095 0           DISPLAY( " %s [arg] [filenames]\n", exename);
1096 0           DISPLAY( "When no filename provided, or - provided : use stdin as input\n");
1097 0           DISPLAY( "Arguments :\n");
1098 0           DISPLAY( " -H# : hash selection : 0=32bits, 1=64bits (default: %i)\n", (int)g_defaultAlgo);
1099 0           DISPLAY( " -c : read xxHash sums from the [filenames] and check them\n");
1100 0           DISPLAY( " -h : help \n");
1101 0           return 0;
1102             }
1103              
1104              
1105 0           static int usage_advanced(const char* exename)
1106             {
1107 0           usage(exename);
1108 0           DISPLAY( "Advanced :\n");
1109 0           DISPLAY( " --little-endian : hash printed using little endian convention (default: big endian)\n");
1110 0           DISPLAY( " -V, --version : display version\n");
1111 0           DISPLAY( " -h, --help : display long help and exit\n");
1112 0           DISPLAY( " -b : benchmark mode \n");
1113 0           DISPLAY( " -i# : number of iterations (benchmark mode; default %i)\n", g_nbIterations);
1114 0           DISPLAY( "\n");
1115 0           DISPLAY( "The following four options are useful only when verifying checksums (-c):\n");
1116 0           DISPLAY( "--strict : don't print OK for each successfully verified file\n");
1117 0           DISPLAY( "--status : don't output anything, status code shows success\n");
1118 0           DISPLAY( "--quiet : exit non-zero for improperly formatted checksum lines\n");
1119 0           DISPLAY( "--warn : warn about improperly formatted checksum lines\n");
1120 0           return 0;
1121             }
1122              
1123             static int badusage(const char* exename)
1124             {
1125 0           DISPLAY("Wrong parameters\n");
1126 0           usage(exename);
1127             return 1;
1128             }
1129              
1130              
1131 0           int main(int argc, const char** argv)
1132             {
1133             int i, filenamesStart=0;
1134 0           const char* exename = argv[0];
1135             U32 benchmarkMode = 0;
1136             U32 fileCheckMode = 0;
1137             U32 strictMode = 0;
1138             U32 statusOnly = 0;
1139             U32 warn = 0;
1140             U32 quiet = 0;
1141             algoType algo = g_defaultAlgo;
1142             endianess displayEndianess = big_endian;
1143              
1144             /* special case : xxh32sum default to 32 bits checksum */
1145 0 0         if (strstr(exename, "xxh32sum") != NULL) algo = algo_xxh32;
1146              
1147 0 0         for(i=1; i
1148 0           const char* argument = argv[i];
1149              
1150 0 0         if(!argument) continue; /* Protection, if argument empty */
1151              
1152 0 0         if (!strcmp(argument, "--little-endian")) { displayEndianess = little_endian; continue; }
1153 0 0         if (!strcmp(argument, "--check")) { fileCheckMode = 1; continue; }
1154 0 0         if (!strcmp(argument, "--strict")) { strictMode = 1; continue; }
1155 0 0         if (!strcmp(argument, "--status")) { statusOnly = 1; continue; }
1156 0 0         if (!strcmp(argument, "--quiet")) { quiet = 1; continue; }
1157 0 0         if (!strcmp(argument, "--warn")) { warn = 1; continue; }
1158 0 0         if (!strcmp(argument, "--help")) { return usage_advanced(exename); }
1159 0 0         if (!strcmp(argument, "--version")) { DISPLAY(WELCOME_MESSAGE(exename)); return 0; }
1160              
1161 0 0         if (*argument!='-') {
1162 0 0         if (filenamesStart==0) filenamesStart=i; /* only supports a continuous list of filenames */
1163 0           continue;
1164             }
1165              
1166             /* command selection */
1167 0           argument++; /* note : *argument=='-' */
1168              
1169 0 0         while (*argument!=0) {
1170 0           switch(*argument)
1171             {
1172             /* Display version */
1173             case 'V':
1174 0           DISPLAY(WELCOME_MESSAGE(exename)); return 0;
1175              
1176             /* Display help on usage */
1177             case 'h':
1178 0           return usage_advanced(exename);
1179              
1180             /* select hash algorithm */
1181             case 'H':
1182 0           algo = (algoType)(argument[1] - '0');
1183 0           argument+=2;
1184 0           break;
1185              
1186             /* File check mode */
1187             case 'c':
1188             fileCheckMode=1;
1189 0           argument++;
1190 0           break;
1191              
1192             /* Warning mode (file check mode only, alias of "--warning") */
1193             case 'w':
1194             warn=1;
1195 0           argument++;
1196 0           break;
1197              
1198             /* Trigger benchmark mode */
1199             case 'b':
1200 0           argument++;
1201             benchmarkMode=1;
1202 0           break;
1203              
1204             /* Modify Nb Iterations (benchmark only) */
1205             case 'i':
1206 0           g_nbIterations = argument[1] - '0';
1207 0           argument+=2;
1208 0           break;
1209              
1210             /* Modify Block size (benchmark only) */
1211             case 'B':
1212 0           argument++;
1213 0           g_sampleSize = 0;
1214 0 0         while (argument[0]>='0' && argument[0]<='9')
1215 0           g_sampleSize *= 10, g_sampleSize += argument[0]-'0', argument++;
1216             break;
1217              
1218             default:
1219 0           return badusage(exename);
1220             }
1221             }
1222             } /* for(i=1; i
1223              
1224             /* Check benchmark mode */
1225 0 0         if (benchmarkMode) {
1226 0           DISPLAY( WELCOME_MESSAGE(exename) );
1227 0           BMK_sanityCheck();
1228 0 0         if (filenamesStart==0) return BMK_benchInternal();
1229 0           return BMK_benchFiles(argv+filenamesStart, argc-filenamesStart);
1230             }
1231              
1232             /* Check if input is defined as console; trigger an error in this case */
1233 0 0         if ( (filenamesStart==0) && IS_CONSOLE(stdin) ) return badusage(exename);
    0          
1234              
1235 0 0         if (filenamesStart==0) filenamesStart = argc;
1236 0 0         if (fileCheckMode) {
1237 0           return checkFiles(argv+filenamesStart, argc-filenamesStart, displayEndianess, strictMode, statusOnly, warn, quiet);
1238             } else {
1239 0           return BMK_hashFiles(argv+filenamesStart, argc-filenamesStart, algo, displayEndianess);
1240             }
1241             }