58#if defined(_WIN32) && !defined(_MINGW) && !defined(S_ISDIR)
59# define S_ISDIR(mode) (((mode) & _S_IFMT) == _S_IFDIR)
61#if defined(_WIN32) && !defined(_MINGW) && !defined(S_ISREG)
62# define S_ISREG(mode) (((mode) & _S_IFMT) == _S_IFREG)
65#if !defined(_WIN32) || defined(_MINGW)
68# define SDDSCHECK_USE_DIRENT 1
71# define SDDSCHECK_USE_DIRENT 0
76# define SDDSCHECK_USE_OPENMP 1
78# define SDDSCHECK_USE_OPENMP 0
91 CLO_CHECKDEFINITIONSONLY,
98static char *option[N_OPTIONS] = {
108 "checkDefinitionsOnly",
127 long checkDefinitionsOnly;
145 "sddscheck [options] <filename|directory> [<filename|directory>...]\n\n"
146 "This program allows you to determine whether SDDS files have been\n"
147 "corrupted. It reads the entire file and prints a message to stdout.\n"
149 "If the file is ok, \"ok\" is printed.\n"
150 "If the file has a problem, one of the following will be printed:\n"
151 " - \"nonexistent\": The file does not exist.\n"
152 " - \"badHeader\": The file header is invalid.\n"
153 " - \"corrupted\": The file contains errors.\n"
154 "If multiple files are provided, each output line is prefixed with the file name.\n"
157 " -printErrors: Deliver error messages to stderr.\n"
158 " -threads=<number>: Number of input files to check concurrently (default: 1).\n"
159 " -summary: Print aggregate status counts after per-file output.\n"
160 " -failuresOnly: Suppress ok result lines.\n"
161 " -failOnError: Exit with status 1 if any checked file is not ok.\n"
162 " -recursive: Recursively expand directory arguments.\n"
163 " -pattern=<glob>: Include only matching base names during directory expansion.\n"
164 " May be repeated. If omitted with -recursive, all files are included.\n"
165 " -maxErrors=<number>: Stop starting new checks after this many failed files.\n"
166 " -showPages: Add pagesRead=<number> to each result line.\n"
167 " -checkDefinitionsOnly: Validate only SDDS header and definitions.\n"
168 " -verbose: Add file size, page count, and SDDS layout metadata.\n"
169 " -stdin: Read additional file or directory names from standard input.\n"
171 "Program by Michael Borland. (" __DATE__
" " __TIME__
", SVN revision: " SVN_VERSION
")\n";
173static void *checkedMalloc(
size_t size) {
183static void *checkedRealloc(
void *ptr,
size_t size) {
186 ptr = realloc(ptr, size);
192static char *checkedStrdup(
const char *text) {
197 length = strlen(text) + 1;
198 copy = checkedMalloc(length);
199 memcpy(copy, text, length);
203static int parseLongOptionValue(
const char *text,
long minimum,
long maximum,
long *value) {
210 parsed = strtol(text, &endptr, 10);
211 if (errno == ERANGE || endptr == text || *endptr || parsed < minimum || parsed > maximum)
217static void stringListAppend(
StringList *list,
const char *text) {
218 if (list->items >= list->allocated) {
219 list->allocated = list->allocated ? 2 * list->allocated : 16;
220 list->item = checkedRealloc(list->item,
sizeof(*list->item) * list->allocated);
222 list->item[list->items++] = checkedStrdup(text);
227 for (i = 0; i < list->items; i++)
230 memset(list, 0,
sizeof(*list));
233static int compareStringPointers(
const void *left,
const void *right) {
234 const char *
const *leftString = left;
235 const char *
const *rightString = right;
236 return strcmp(*leftString, *rightString);
239static const char *checkStatusString(CheckStatus status) {
243 case CHECK_NONEXISTENT:
244 return "nonexistent";
245 case CHECK_BADHEADER:
247 case CHECK_CORRUPTED:
253static const char *dataModeString(
int mode) {
266static const char *baseName(
const char *path) {
268 const char *backslash;
270 slash = strrchr(path,
'/');
271 backslash = strrchr(path,
'\\');
272 if (backslash && (!slash || backslash > slash))
274 return slash ? slash + 1 : path;
277static int matchesPatterns(
const char *path,
const StringList *patterns) {
281 if (!patterns->items)
283 name = baseName(path);
284 for (i = 0; i < patterns->items; i++) {
285 if (
wild_match((
char *)name, patterns->item[i]))
291static int isDirectory(
const char *path) {
292 struct stat statBuffer;
293 if (stat(path, &statBuffer) != 0)
295 return S_ISDIR(statBuffer.st_mode);
298static char *joinPath(
const char *directory,
const char *name) {
300 size_t directoryLength, nameLength, length;
303 directoryLength = strlen(directory);
304 nameLength = strlen(name);
305 needsSlash = directoryLength && directory[directoryLength - 1] !=
'/' && directory[directoryLength - 1] !=
'\\';
306 length = directoryLength + needsSlash + nameLength + 1;
307 path = checkedMalloc(length);
308 snprintf(path, length,
"%s%s%s", directory, needsSlash ?
"/" :
"", name);
312static void collectDirectoryFiles(
const char *directory,
long recursive,
const StringList *patterns,
StringList *files) {
316#if SDDSCHECK_USE_DIRENT
318 struct dirent *entry;
320 dir = opendir(directory);
322 fprintf(stderr,
"warning: unable to read directory %s: %s\n", directory, strerror(errno));
326 while ((entry = readdir(dir))) {
327 if (strcmp(entry->d_name,
".") == 0 || strcmp(entry->d_name,
"..") == 0)
329 stringListAppend(&entries, entry->d_name);
333 WIN32_FIND_DATAA entry;
338 searchPattern = joinPath(directory,
"*");
339 findHandle = FindFirstFileA(searchPattern, &entry);
340 if (findHandle == INVALID_HANDLE_VALUE) {
341 error = GetLastError();
342 if (error != ERROR_FILE_NOT_FOUND && error != ERROR_PATH_NOT_FOUND)
343 fprintf(stderr,
"warning: unable to read directory %s: Windows error %lu\n",
344 directory, (
unsigned long)error);
350 if (strcmp(entry.cFileName,
".") == 0 || strcmp(entry.cFileName,
"..") == 0 ||
351 (entry.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
353 stringListAppend(&entries, entry.cFileName);
354 }
while (FindNextFileA(findHandle, &entry));
355 error = GetLastError();
356 if (error != ERROR_NO_MORE_FILES)
357 fprintf(stderr,
"warning: unable to finish reading directory %s: Windows error %lu\n",
358 directory, (
unsigned long)error);
359 FindClose(findHandle);
366 SDDS_Bomb(
"-recursive and directory -pattern expansion are not supported on this platform");
369 if (entries.items > 1)
370 qsort(entries.item, entries.items,
sizeof(*entries.item), compareStringPointers);
372 for (i = 0; i < entries.items; i++) {
374 struct stat statBuffer;
376 path = joinPath(directory, entries.item[i]);
377#if SDDSCHECK_USE_DIRENT
378 if (lstat(path, &statBuffer) != 0) {
380 if (stat(path, &statBuffer) != 0) {
385 if (S_ISDIR(statBuffer.st_mode)) {
387 collectDirectoryFiles(path, recursive, patterns, files);
388 }
else if (S_ISREG(statBuffer.st_mode) && matchesPatterns(path, patterns)) {
389 stringListAppend(files, path);
393 stringListFree(&entries);
396static void readInputNamesFromStdin(
StringList *input) {
399 while (fgets(buffer,
sizeof(buffer), stdin)) {
400 size_t length = strlen(buffer);
401 while (length && (buffer[length - 1] ==
'\n' || buffer[length - 1] ==
'\r'))
402 buffer[--length] = 0;
405 stringListAppend(input, buffer);
412 struct stat statBuffer;
415 memset(&result, 0,
sizeof(result));
417 result.status = CHECK_CORRUPTED;
420 if (stat(input, &statBuffer) != 0) {
421 result.status = CHECK_NONEXISTENT;
424 result.sizeKnown = 1;
425 result.sizeBytes = (
long long)statBuffer.st_size;
432 result.status = CHECK_BADHEADER;
436 result.metadataKnown = 1;
437 result.dataMode = SDDS_input.layout.data_mode.mode;
438 result.columns = SDDS_input.layout.n_columns;
439 result.parameters = SDDS_input.layout.n_parameters;
440 result.arrays = SDDS_input.layout.n_arrays;
441 result.associates = SDDS_input.layout.n_associates;
443 if (options->checkDefinitionsOnly) {
444 result.status = CHECK_OK;
456 result.status = CHECK_OK;
465 result.status = CHECK_CORRUPTED;
469static void printCheckResult(
const char *input,
const CheckResult *result,
long singleResult,
long showPages,
long verbose) {
470 if (singleResult && !showPages && !verbose) {
471 puts(checkStatusString(result->status));
475 printf(
"%s: %s", input, checkStatusString(result->status));
476 if (showPages || verbose)
477 printf(
" pagesRead=%ld", result->pagesRead);
479 if (result->sizeKnown)
480 printf(
" sizeBytes=%lld", result->sizeBytes);
481 if (result->metadataKnown) {
482 printf(
" dataMode=%s columns=%d parameters=%d arrays=%d associates=%d",
483 dataModeString(result->dataMode), result->columns, result->parameters, result->arrays, result->associates);
489static void printSummary(
long checked,
long *statusCounts) {
490 printf(
"checked: %ld ok: %ld nonexistent: %ld badHeader: %ld corrupted: %ld\n",
491 checked, statusCounts[CHECK_OK], statusCounts[CHECK_NONEXISTENT], statusCounts[CHECK_BADHEADER], statusCounts[CHECK_CORRUPTED]);
494int main(
int argc,
char **argv) {
500 long i_arg, print_errors, threads, summary, failuresOnly, failOnError, recursive, useStdin, maxErrors, showPages, verbose;
501 long checked, failures, statusCounts[4];
508 argc =
scanargs(&s_arg, argc, argv);
509 if (!s_arg || argc < 2) {
513 memset(&checkOptions, 0,
sizeof(checkOptions));
514 memset(statusCounts, 0,
sizeof(statusCounts));
527 for (i_arg = 1; i_arg < argc; i_arg++) {
528 if (s_arg[i_arg].arg_type == OPTION) {
530 switch (
match_string(s_arg[i_arg].list[0], option, N_OPTIONS, 0)) {
531 case CLO_PRINTERRORS:
535 if (s_arg[i_arg].n_items != 2 ||
536 !parseLongOptionValue(s_arg[i_arg].list[1], 1, INT_MAX, &threads))
542 case CLO_FAILURESONLY:
545 case CLO_FAILONERROR:
552 if (s_arg[i_arg].n_items != 2 || !strlen(s_arg[i_arg].list[1]))
554 stringListAppend(&patterns, s_arg[i_arg].list[1]);
557 if (s_arg[i_arg].n_items != 2 ||
558 !parseLongOptionValue(s_arg[i_arg].list[1], 1, LONG_MAX, &maxErrors))
564 case CLO_CHECKDEFINITIONSONLY:
565 checkOptions.checkDefinitionsOnly = 1;
578 stringListAppend(&rawInput, s_arg[i_arg].list[0]);
583 readInputNamesFromStdin(&rawInput);
585 if (rawInput.items < 1)
588 for (i_arg = 0; i_arg < rawInput.items; i_arg++) {
589 if ((recursive || patterns.items) && isDirectory(rawInput.item[i_arg]))
590 collectDirectoryFiles(rawInput.item[i_arg], recursive, &patterns, &input);
592 stringListAppend(&input, rawInput.item[i_arg]);
595 result = checkedMalloc(
sizeof(*result) * input.items);
596 memset(result, 0,
sizeof(*result) * input.items);
600 if (input.items == 1 && !summary && !failuresOnly && !showPages && !verbose) {
601 result[0] = checkFile(input.item[0], &checkOptions, 0);
603 statusCounts[result[0].status]++;
604 failures = result[0].status != CHECK_OK;
605 if (print_errors && result[0].status == CHECK_CORRUPTED)
606 checkFile(input.item[0], &checkOptions, 1);
607 printCheckResult(input.item[0], &result[0], 1, showPages, verbose);
608 if (print_errors && result[0].status == CHECK_BADHEADER)
609 checkFile(input.item[0], &checkOptions, 1);
611 stringListFree(&patterns);
612 stringListFree(&rawInput);
613 stringListFree(&input);
614 return (failOnError && failures) ? 1 : 0;
617 if (threads <= 1 || input.items <= 1) {
618 for (i_arg = 0; i_arg < input.items; i_arg++) {
619 result[i_arg] = checkFile(input.item[i_arg], &checkOptions, print_errors);
621 statusCounts[result[i_arg].status]++;
622 if (result[i_arg].status != CHECK_OK) {
624 if (maxErrors && failures >= maxErrors)
629 long sharedFailures = 0;
630 if (threads > input.items)
631 threads = input.items;
632#if SDDSCHECK_USE_OPENMP
633 omp_set_num_threads((
int)threads);
634#pragma omp parallel for schedule(dynamic)
635 for (i_arg = 0; i_arg < input.items; i_arg++) {
636 long failuresSeen = 0;
638#pragma omp critical(sddscheck_failures)
640 failuresSeen = sharedFailures;
642 if (failuresSeen >= maxErrors)
645 result[i_arg] = checkFile(input.item[i_arg], &checkOptions, 0);
646 if (maxErrors && result[i_arg].status != CHECK_OK) {
647#pragma omp critical(sddscheck_failures)
654 for (i_arg = 0; i_arg < input.items; i_arg++) {
655 if (maxErrors && sharedFailures >= maxErrors)
657 result[i_arg] = checkFile(input.item[i_arg], &checkOptions, 0);
658 if (result[i_arg].status != CHECK_OK)
663 for (i_arg = 0; i_arg < input.items; i_arg++) {
664 if (result[i_arg].checked && result[i_arg].status != CHECK_OK && result[i_arg].status != CHECK_NONEXISTENT)
665 checkFile(input.item[i_arg], &checkOptions, 1);
668 for (i_arg = 0; i_arg < input.items; i_arg++) {
669 if (!result[i_arg].checked)
672 statusCounts[result[i_arg].status]++;
673 if (result[i_arg].status != CHECK_OK)
678 for (i_arg = 0; i_arg < input.items; i_arg++) {
679 if (!result[i_arg].checked)
681 if (failuresOnly && result[i_arg].status == CHECK_OK)
683 printCheckResult(input.item[i_arg], &result[i_arg], input.items == 1, showPages, verbose);
686 printSummary(checked, statusCounts);
689 stringListFree(&patterns);
690 stringListFree(&rawInput);
691 stringListFree(&input);
692 return (failOnError && failures) ? 1 : 0;
SDDS (Self Describing Data Set) Data Types Definitions and Function Prototypes.
void SDDS_PrintErrors(FILE *fp, int32_t mode)
Prints recorded error messages to a specified file stream.
void SDDS_ClearErrors()
Clears all recorded error messages from the SDDS error stack.
void SDDS_RegisterProgramName(const char *name)
Registers the executable program name for use in error messages.
void SDDS_Bomb(char *message)
Terminates the program after printing an error message and recorded errors.
void bomb(char *error, char *usage)
Reports error messages to the terminal and aborts the program.
long match_string(char *string, char **option, long n_options, long mode)
Matches a given string against an array of option strings based on specified modes.
int scanargs(SCANNED_ARG **scanned, int argc, char **argv)
int wild_match(char *string, char *template)
Determine whether one string is a wildcard match for another.