From 1f09e4cf390b39633073e88ca36232a2b2deb108 Mon Sep 17 00:00:00 2001 From: Cody Boone Ferguson <53008573+xexyl@users.noreply.github.com> Date: Fri, 28 Feb 2025 10:46:56 -0800 Subject: [PATCH] Additional fixes and updates to various tools Add extra sanity checks in check_submission_dir(): it now checks towards the beginning if the required files exist (that is, the three user submitted files, not the JSON file as those don't exist yet, though it does check that they do not exist as dot files here would be an error) and are readable and if they are not size 0 (for Makefile and remarks.md - prog.c may be empty). Updated some literal strings of the three required filenames (as above) to their macros in both mkiocccentry and txzchk. Fixes in chkentry with checking if files exist or do not exist (depending on options) and also add checks for file sizes for certain files, namely Makefile, remarks.md (in non-winning mode) as well as index.html (in winning mode) and README.md (in winning mode). Error messages were improved in chkentry as well though there are some places (the manifest check) where they could be improved (this would require lower level changes that we do not have time for before the freeze later today). Sync [jparse repo](https://github.com/xexyl/jparse/) to jparse/ for new util function (that uses its file_size() function) is_empty(). This is used in the update to chkentry. More work on the chkentry_test.sh script. The script is in better shape so that when the directories are generated (they cannot be static due to version changes - at least without a new option) the script should hopefully be easily updated (though it depends maybe on how the new directories are generated). Updated MKIOCCCENTRY_VERSION to "1.2.36 2025-02-28". Updated TXZCHK_VERSION to "1.1.15 2025-02-28". Updated CHKENTRY_VERSION to "1.1.6 2025-02-28". Updated CHKENTRY_TEST_VERSION to "1.1.1 2025-02-28". --- .gitignore | 1 - CHANGES.md | 33 +- Makefile | 30 +- chkentry.c | 803 ++++++++++++++---------------------- jparse/CHANGES.md | 8 + jparse/util.c | 90 +++- jparse/util.h | 1 + jparse/version.h | 4 +- mkiocccentry.c | 31 +- soup/Makefile | 28 +- soup/version.h | 4 +- test_ioccc/Makefile | 30 +- test_ioccc/chkentry_test.sh | 226 ++++++---- txzchk.c | 12 +- 14 files changed, 687 insertions(+), 614 deletions(-) diff --git a/.gitignore b/.gitignore index 150e53f54..4d7936731 100644 --- a/.gitignore +++ b/.gitignore @@ -44,7 +44,6 @@ random_answers.* /test_ioccc/fnamchk /test_ioccc/.hostchk.*.work/ /test_ioccc/.local.dir.tags -test_ioccc/slot/good/workdir/ /test_ioccc/tags /test_ioccc/test_JSON/ /test_ioccc/test_ioccc.log diff --git a/CHANGES.md b/CHANGES.md index d6b0908de..7dfc8a408 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,12 +11,37 @@ the longer warning to be sure that they know. As for `-Y` it really ought to be used only for the test script but even `-y` should be used with **EXTREME** caution. -It is hoped this is the last update to `mkiocccentry(1)` prior to the soft code -freeze today. +Add extra sanity checks in `check_submission_dir()`: it now checks towards the +beginning if the required files exist (that is, the three user submitted files, +not the JSON file as those don't exist yet, though it does check that they do +not exist as dot files here would be an error) and are readable and if they are +not size 0 (for Makefile and remarks.md - prog.c may be empty). -Update `MKIOCCCENTRY_VERSION` to `"1.2.36 2025-02-28"`. +Updated some literal strings of the three required filenames (as above) to their +macros in both mkiocccentry and txzchk. -TODO: finish work on the `chkentry_test.sh` script. +Fixes in `chkentry` with checking if files exist or do not exist (depending on +options) and also add checks for file sizes for certain files, namely Makefile, +remarks.md (in non-winning mode) as well as index.html (in winning mode) and +README.md (in winning mode). + +Error messages were improved in `chkentry` as well though there are some places +(the manifest check) where they could be improved (this would require lower +level changes that we do not have time for before the freeze later today). + +Sync [jparse repo](https://github.com/xexyl/jparse/) to `jparse/` for new util +function (that uses its `file_size()` function) `is_empty()`. This is used in +the update to `chkentry`. + +More work on the `chkentry_test.sh` script. The script is in better shape so +that when the directories are generated (they cannot be static due to version +changes - at least without a new option) the script should hopefully be easily +updated (though it depends maybe on how the new directories are generated). + +Updated `MKIOCCCENTRY_VERSION` to `"1.2.36 2025-02-28"`. +Updated `TXZCHK_VERSION` to `"1.1.15 2025-02-28"`. +Updated `CHKENTRY_VERSION` to `"1.1.6 2025-02-28"`. +Updated `CHKENTRY_TEST_VERSION` to `"1.1.1 2025-02-28"`. Resequence exit codes in `jparse/util.c`. It appears that this was not done or something went wrong when doing so (as running `make seqcexit` updated the exit diff --git a/Makefile b/Makefile index 6d664bec9..3cfe2102d 100644 --- a/Makefile +++ b/Makefile @@ -2,9 +2,10 @@ # # mkiocccentry and related tools - how to form an IOCCC submission # -# For the mkiocccentry utility: +# For mkiocccentry: # -# Copyright (c) 2021-2025 by Landon Curt Noll. All Rights Reserved. +# Copyright (c) 2021-2025 by Landon Curt Noll and Cody Boone Ferguson. +# All Rights Reserved. # # Permission to use, copy, modify, and distribute this software and # its documentation for any purpose and without fee is hereby granted, @@ -16,17 +17,26 @@ # source works derived from this source # binaries derived from this source or from derived source # -# LANDON CURT NOLL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, -# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO -# EVENT SHALL LANDON CURT NOLL BE LIABLE FOR ANY SPECIAL, INDIRECT OR -# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF -# USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -# PERFORMANCE OF THIS SOFTWARE. +# THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING +# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +# AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # -# chongo (Landon Curt Noll, http://www.isthe.com/chongo/index.html) /\oo/\ +# This tool was co-developed in 2021-2025 by Cody Boone Ferguson and Landon +# Curt Noll: +# +# @xexyl +# https://xexyl.net Cody Boone Ferguson +# https://ioccc.xexyl.net +# and: +# chongo (Landon Curt Noll, http://www.isthe.com/chongo/index.html) /\oo/\ +# +# "Because sometimes even the IOCCC Judges need some help." :-) # # Share and enjoy! :-) +# -- Sirius Cybernetics Corporation Complaints Division, JSON spec department. :-) #### #### diff --git a/chkentry.c b/chkentry.c index fd1f2b80d..157a3debd 100644 --- a/chkentry.c +++ b/chkentry.c @@ -630,7 +630,6 @@ main(int argc, char *argv[]) uintmax_t all_extra_err_count = 0; /* depending on options and conditions, we might need this */ struct json_sem_count_err *sem_count_err = NULL; /* semantic count error to print */ struct json_sem_val_err *sem_val_err = NULL; /* semantic validation error to print */ - char *path = NULL; /* specific checks depending on the option (like -i entry/-i .entry.json) */ uintmax_t c = 0; /* dynamic array index */ uintmax_t len = 0; /* dynamic array length */ int cwd = -1; /* for when we have to use find_path() */ @@ -640,6 +639,15 @@ main(int argc, char *argv[]) bool winning_entry_mode = false; /* true ==> -w used, do other checks */ struct dyn_array *paths = NULL; /* paths to find for find_paths() */ struct dyn_array *found = NULL; /* paths found by find_paths() */ + bool found_entry = false; /* true ==> .entry.json found */ + bool found_prog_c = false; /* true ==> prog.c found */ + bool found_prog = false; /* true ==> prog found */ + bool found_auth = false; /* true ==> .auth.json found */ + bool found_info = false; /* true ==> .info.json found */ + bool found_README = false; /* true ==> README.md found */ + bool found_index = false; /* true ==> index.html found */ + bool found_remarks = false; /* true ==> remarks.md found */ + bool found_Makefile = false; /* true ==> Makefile found */ @@ -755,228 +763,14 @@ main(int argc, char *argv[]) errp(59, __func__, "cannot open ."); not_reached(); } - fts.depth = 1; /* we need depth 1 only */ - fts.match_case = true; /* we must need to match case */ - fts.type = FTS_TYPE_FILE; /* regular files only */ if (winning_entry_mode) { /* - * make sure .entry.json exists unless -i entry + * set up fts */ - if (!ignore_entry) { - path = find_path(ENTRY_JSON_FILENAME, *submission_dir, -1, &cwd, false, &fts); - if (path == NULL) { - dbg(DBG_LOW, "file does NOT exist and -w USED and -i entry NOT used: %s", ENTRY_JSON_FILENAME); - ++all_extra_err_count; - } else { - /* - * now check the permissions are correct - */ - if (!is_mode(path, S_IRUSR | S_IRGRP | S_IROTH)) { - werr(1, __func__, "%s: permissions %o != 0444", path, filemode(path));/*ooo*/ - ++all_extra_err_count; - } - free(path); - path = NULL; - } - - /* - * move back to previous directory - */ - (void) read_fts(NULL, -1, &cwd, NULL); - } - - /* - * unless -i auth .auth.json must NOT exist - */ - if (!ignore_auth) { - path = find_path(AUTH_JSON_FILENAME, *submission_dir, -1, &cwd, false, &fts); - if (path != NULL) { - dbg(DBG_LOW, "file EXISTS and -w USED and -i auth NOT used: %s", AUTH_JSON_FILENAME); - ++all_extra_err_count; - free(path); - path = NULL; - } - - /* - * move back to previous directory - */ - (void) read_fts(NULL, -1, &cwd, NULL); - } - /* - * unless -i info .info.json must NOT exist - */ - if (!ignore_info) { - /* - * .info.json - */ - path = find_path(INFO_JSON_FILENAME, *submission_dir, -1, &cwd, false, &fts); - if (path != NULL) { - dbg(DBG_LOW, "file EXISTS and -w USED and -i info NOT used: %s", INFO_JSON_FILENAME); - ++all_extra_err_count; - free(path); - path = NULL; - } - - /* - * move back to previous directory - */ - (void) read_fts(NULL, -1, &cwd, NULL); - } - /* - * unless -i README README.md MUST exist - */ - if (!ignore_README_md) { - /* - * README.md - */ - path = find_path(README_MD_FILENAME, *submission_dir, -1, &cwd, false, &fts); - if (path == NULL) { - dbg(DBG_LOW, "file does NOT exist and -w USED and -i README NOT used: %s", README_MD_FILENAME); - ++all_extra_err_count; - } else { - /* - * now check the permissions are correct - */ - if (!is_mode(path, S_IRUSR | S_IRGRP | S_IROTH)) { - werr(1, __func__, "%s: permissions %o != 0444", path, filemode(path));/*ooo*/ - ++all_extra_err_count; - } - free(path); - path = NULL; - } - - /* - * move back to previous directory - */ - (void) read_fts(NULL, -1, &cwd, NULL); - } - /* - * unless -i index index.html MUST exist - */ - if (!ignore_index) { - /* - * index.html - */ - path = find_path(INDEX_HTML_FILENAME, *submission_dir, -1, &cwd, false, &fts); - if (path == NULL) { - dbg(DBG_LOW, "file does NOT exist and -w USED and -i index NOT used: %s", INDEX_HTML_FILENAME); - ++all_extra_err_count; - } else { - /* - * now check the permissions are correct - */ - if (!is_mode(path, S_IRUSR | S_IRGRP | S_IROTH)) { - werr(1, __func__, "%s: permissions %o != 0444", path, filemode(path));/*ooo*/ - ++all_extra_err_count; - } - free(path); - path = NULL; - } - - /* - * move back to previous directory - */ - (void) read_fts(NULL, -1, &cwd, NULL); - } - - /* - * unless -i Makefile Makefile MUST exist - */ - if (!ignore_Makefile) { - /* - * Makefile - */ - path = find_path(MAKEFILE_FILENAME, *submission_dir, -1, &cwd, false, &fts); - if (path == NULL) { - dbg(DBG_LOW, "%s does not exist and -w used and -i Makefile not used", MAKEFILE_FILENAME); - ++all_extra_err_count; - } else { - /* - * now check the permissions are correct - */ - if (!is_mode(path, S_IRUSR | S_IRGRP | S_IROTH)) { - werr(1, __func__, "%s: permissions %o != 0444", path, filemode(path));/*ooo*/ - ++all_extra_err_count; - } - free(path); - path = NULL; - } - - /* - * move back to previous directory - */ - (void) read_fts(NULL, -1, &cwd, NULL); - } - /* - * unless -i prog.c prog.c MUST exist - */ - if (!ignore_prog_c) { - /* - * prog.c - */ - path = find_path(PROG_C_FILENAME, *submission_dir, -1, &cwd, false, &fts); - if (path == NULL) { - dbg(DBG_LOW, "%s does not exist and -w used and -i prog.c not used", PROG_C_FILENAME); - ++all_extra_err_count; - } else { - /* - * now check the permissions are correct - */ - if (!is_mode(path, S_IRUSR | S_IRGRP | S_IROTH)) { - werr(1, __func__, "%s: permissions %o != 0444", path, filemode(path));/*ooo*/ - ++all_extra_err_count; - } - free(path); - path = NULL; - } - - /* - * move back to previous directory - */ - (void) read_fts(NULL, -1, &cwd, NULL); - } - /* - * unless -i prog prog must NOT exist - */ - if (!ignore_prog) { - /* - * prog - */ - path = find_path(PROG_FILENAME, *submission_dir, -1, &cwd, false, &fts); - if (path != NULL) { - dbg(DBG_LOW, "%s exists and -w used and -i prog not used", PROG_FILENAME); - ++all_extra_err_count; - free(path); - path = NULL; - } - - /* - * move back to previous directory - */ - (void) read_fts(NULL, -1, &cwd, NULL); - } - /* - * unless -i remarks remarks.md must NOT exist - */ - if (!ignore_remarks_md) { - /* - * remarks.md - */ - path = find_path(REMARKS_FILENAME, *submission_dir, -1, &cwd, false, &fts); - if (path != NULL) { - dbg(DBG_LOW, "%s exists and -w used and -i remarks not used", REMARKS_FILENAME); - ++all_extra_err_count; - free(path); - path = NULL; - } - - /* - * move back to previous directory - */ - (void) read_fts(NULL, -1, &cwd, NULL); - } - + fts.depth = 0; + fts.match_case = true; /* we must match case here */ + fts.type = FTS_TYPE_FILE; /* regular files only */ /* * now for the FTS find_paths() function: we need to find all files * that are NOT ignored (this is why there are two ignore lists) @@ -1003,22 +797,104 @@ main(int argc, char *argv[]) for (c = 0; c < len; ++c) { u = dyn_array_value(found, char *, c); if (u == NULL) { - err(60, __func__, "NULL pointer in ignore list"); + err(60, __func__, "NULL pointer in found files list"); not_reached(); } /* * now we have to determine what file this is - * - * Since we already tested specific files we only have to - * test files that have not been tested. */ - if (!strcmp(u, AUTH_JSON_FILENAME) || !strcmp(u, INFO_JSON_FILENAME) || !strcmp(u, REMARKS_FILENAME) || - !strcmp(u, MAKEFILE_FILENAME) || !strcmp(u, PROG_C_FILENAME) || !strcmp(u, PROG_FILENAME) || - !strcmp(u, ENTRY_JSON_FILENAME) || !strcmp(u, README_MD_FILENAME) || !strcmp(u, INDEX_HTML_FILENAME)) { - /* - * these we already tested, skip - */ + + /* + * Certain files have specific checks so we check those by + * name explicitly. + */ + if (!strcmp(u, AUTH_JSON_FILENAME)) { + if (ignore_auth) { + continue; + } + found_auth = true; + ++all_extra_err_count; + } else if (!strcmp(u, INFO_JSON_FILENAME)) { + if (ignore_info) { + continue; + } + ++all_extra_err_count; + found_info = true; + } else if (!strcmp(u, REMARKS_FILENAME)) { + if (ignore_remarks_md) { + continue; + } + + ++all_extra_err_count; + found_remarks = true; + } else if (!strcmp(u, MAKEFILE_FILENAME)) { + if (ignore_Makefile) { + continue; + } + if (!is_mode(u, S_IRUSR | S_IRGRP | S_IROTH)) { + werr(1, __func__, "%s: permissions %o != 0444", u, filemode(u));/*ooo*/ + ++all_extra_err_count; + } + if (is_empty(u)) { + werr(1, __func__, "%s: file is empty", u); + ++all_extra_err_count; + } + + found_Makefile = true; + } else if (!strcmp(u, PROG_C_FILENAME)) { + if (ignore_prog_c) { + continue; + } + if (!is_mode(u, S_IRUSR | S_IRGRP | S_IROTH)) { + werr(1, __func__, "%s: permissions %o != 0444", u, filemode(u));/*ooo*/ + ++all_extra_err_count; + } + found_prog_c = true; + } else if (!strcmp(u, PROG_FILENAME)) { + if (ignore_prog) { + continue; + } + ++all_extra_err_count; + found_prog = true; + } else if (!strcmp(u, ENTRY_JSON_FILENAME)) { + if (ignore_entry) { + continue; + } + if (!is_mode(u, S_IRUSR | S_IRGRP | S_IROTH)) { + werr(1, __func__, "%s: permissions %o != 0444", u, filemode(u));/*ooo*/ + ++all_extra_err_count; + } + if (is_empty(u)) { + werr(1, __func__, "%s: file is empty", u); + ++all_extra_err_count; + } + found_entry = true; + } else if (!strcmp(u, README_MD_FILENAME)) { + if (ignore_README_md) { + continue; + } + if (!is_mode(u, S_IRUSR | S_IRGRP | S_IROTH)) { + werr(1, __func__, "%s: permissions %o != 0444", u, filemode(u));/*ooo*/ + ++all_extra_err_count; + } + if (is_empty(u)) { + werr(1, __func__, "%s: file is empty", u); + ++all_extra_err_count; + } + found_README = true; + } else if (!strcmp(u, INDEX_HTML_FILENAME)) { + if (ignore_index) { continue; + } + if (!is_mode(u, S_IRUSR | S_IRGRP | S_IROTH)) { + werr(1, __func__, "%s: permissions %o != 0444", u, filemode(u));/*ooo*/ + ++all_extra_err_count; + } + if (is_empty(u)) { + werr(1, __func__, "%s: file is empty", u); + ++all_extra_err_count; + } + found_index = true; } else { /* * not one of the specific filenames so we have to do @@ -1044,246 +920,80 @@ main(int argc, char *argv[]) } } - /* - * move back to previous directory - */ - (void) read_fts(NULL, -1, &cwd, NULL); + } else { + werr(1, __func__, "NO FILES FOUND in submission directory: %s", *submission_dir);/*ooo*/ + ++all_extra_err_count; } - } else { - /* - * case: -w NOT used - */ /* - * additional checks: we have to verify the file permissions are correct - * of files if not ignored. Yes this does mean there is some duplicated - * obtaining of paths. That also happens when -i info is not used. This - * could be said to be a mis-feature or it could be said to be defence - * in depth. You may choose your poison, keeping in mind that: - * - * osis sola facit venenum - * - * -- Paracelsus - * - * :-) + * now we have to verify that certain files do exist or do not exist + * depending on the -i option */ - - /* - * make sure .entry.json does NOT exist unless -i entry - */ - if (!ignore_entry) { - path = find_path(ENTRY_JSON_FILENAME, *submission_dir, -1, &cwd, false, &fts); - if (path != NULL) { - dbg(DBG_LOW, "%s exists and -w and -i entry not used and ", ENTRY_JSON_FILENAME); - ++all_extra_err_count; - free(path); - path = NULL; - } - - /* - * move back to previous directory - */ - (void) read_fts(NULL, -1, &cwd, NULL); + if (!ignore_entry && !found_entry) { + werr(1, __func__, "%s does NOT exist and -w USED and -i entry NOT used", ENTRY_JSON_FILENAME);/*ooo*/ + ++all_extra_err_count; } - - /* - * unless -i auth .auth.json MUST exist - */ - if (!ignore_auth) { - path = find_path(AUTH_JSON_FILENAME, *submission_dir, -1, &cwd, false, &fts); - if (path == NULL) { - dbg(DBG_LOW, "%s does not exist and -i auth not used", AUTH_JSON_FILENAME); - ++all_extra_err_count; - } else { - /* - * now check the permissions are correct - */ - if (!is_mode(path, S_IRUSR | S_IRGRP | S_IROTH)) { - werr(1, __func__, "%s: permissions: %o != 0444", path, filemode(path));/*ooo*/ - ++all_extra_err_count; - } - free(path); - path = NULL; - } - - /* - * move back to previous directory - */ - (void) read_fts(NULL, -1, &cwd, NULL); + if (!ignore_auth && found_auth) { + werr(1, __func__, "%s EXISTS and -w USED and -i auth NOT used", AUTH_JSON_FILENAME);/*ooo*/ + ++all_extra_err_count; } - /* - * unless -i info .info.json MUST exist - */ - if (!ignore_info) { - /* - * .info.json - */ - path = find_path(INFO_JSON_FILENAME, *submission_dir, -1, &cwd, false, &fts); - if (path == NULL) { - dbg(DBG_LOW, "%s does not exist and -i info not used", INFO_JSON_FILENAME); - ++all_extra_err_count; - } else { - /* - * now check the permissions are correct - */ - if (!is_mode(path, S_IRUSR | S_IRGRP | S_IROTH)) { - werr(1, __func__, "%s: permissions: %o != 0444", path, filemode(path));/*ooo*/ - ++all_extra_err_count; - } - free(path); - path = NULL; - } - - /* - * move back to previous directory - */ - (void) read_fts(NULL, -1, &cwd, NULL); + if (!ignore_info && found_info) { + werr(1, __func__, "%s EXISTS and -w USED and -i info NOT used", INFO_JSON_FILENAME);/*ooo*/ + ++all_extra_err_count; } - /* - * unless -i README README.md must NOT exist - */ - if (!ignore_README_md) { - /* - * README.md - */ - path = find_path(README_MD_FILENAME, *submission_dir, -1, &cwd, false, &fts); - if (path != NULL) { - dbg(DBG_LOW, "%s exists and -i README not used", README_MD_FILENAME); - ++all_extra_err_count; - free(path); - path = NULL; - } - - /* - * move back to previous directory - */ - (void) read_fts(NULL, -1, &cwd, NULL); + if (!ignore_README_md && !found_README) { + werr(1, __func__, "%s does NOT exist and -w USED and -i README NOT used", README_MD_FILENAME);/*ooo*/ + ++all_extra_err_count; } - /* - * unless -i index index.html must NOT exist - */ - if (!ignore_index) { - /* - * index.html - */ - path = find_path(INDEX_HTML_FILENAME, *submission_dir, -1, &cwd, false, &fts); - if (path != NULL) { - dbg(DBG_LOW, "%s exists and -i index not used", INDEX_HTML_FILENAME); - ++all_extra_err_count; - free(path); - path = NULL; - } - - /* - * move back to previous directory - */ - (void) read_fts(NULL, -1, &cwd, NULL); + if (!ignore_index && !found_index) { + werr(1, __func__, "%s does NOT exist and -w USED and -i index NOT used", INDEX_HTML_FILENAME);/*ooo*/ + ++all_extra_err_count; + } + if (!ignore_Makefile && !found_Makefile) { + werr(1, __func__, "%s does NOT exist and -w USED and -i Makefile NOT used", MAKEFILE_FILENAME);/*ooo*/ + ++all_extra_err_count; + } + if (!ignore_prog_c && !found_prog_c) { + werr(1, __func__, "%s does NOT exist and -w USED and -i prog.c NOT used", PROG_C_FILENAME);/*ooo*/ + ++all_extra_err_count; + } + if (!ignore_prog && found_prog) { + werr(1, __func__, "%s EXISTS and -w USED and -i prog NOT used", PROG_FILENAME);/*ooo*/ + ++all_extra_err_count; + } + if (!ignore_remarks_md && found_remarks) { + werr(1, __func__, "%s EXISTS and -w USED and -i remarks NOT used", REMARKS_FILENAME);/*ooo*/ + ++all_extra_err_count; } - /* - * unless -i Makefile Makefile MUST exist + * move back to previous directory */ - if (!ignore_Makefile) { - /* - * Makefile - */ - path = find_path(MAKEFILE_FILENAME, *submission_dir, -1, &cwd, false, &fts); - if (path == NULL) { - dbg(DBG_LOW, "%s does not exist and -i Makefile not used", MAKEFILE_FILENAME); - ++all_extra_err_count; - } else { - /* - * now check the permissions are correct - */ - if (!is_mode(path, S_IRUSR | S_IRGRP | S_IROTH)) { - werr(1, __func__, "%s: permissions: %o != 0444", path, filemode(path));/*ooo*/ - ++all_extra_err_count; - } - free(path); - path = NULL; - } - - /* - * move back to previous directory - */ - (void) read_fts(NULL, -1, &cwd, NULL); - } + (void) read_fts(NULL, -1, &cwd, NULL); + } else { /* - * unless -i prog.c prog.c MUST exist + * case: -w NOT used */ - if (!ignore_prog_c) { - /* - * prog.c - */ - path = find_path(PROG_C_FILENAME, *submission_dir, -1, &cwd, false, &fts); - if (path == NULL) { - dbg(DBG_LOW, "%s does not exist and -i prog.c not used", PROG_C_FILENAME); - ++all_extra_err_count; - } else { - /* - * now check the permissions are correct - */ - if (!is_mode(path, S_IRUSR | S_IRGRP | S_IROTH)) { - werr(1, __func__, "%s: permissions: %o != 0444", path, filemode(path));/*ooo*/ - ++all_extra_err_count; - } - free(path); - path = NULL; - } - - /* - * move back to previous directory - */ - (void) read_fts(NULL, -1, &cwd, NULL); - } /* - * unless -i prog prog must NOT exist + * set up fts */ - if (!ignore_prog) { - /* - * prog - */ - path = find_path(PROG_FILENAME, *submission_dir, -1, &cwd, false, &fts); - if (path != NULL) { - dbg(DBG_LOW, "%s exists and -i prog not used", PROG_FILENAME); - ++all_extra_err_count; - free(path); - path = NULL; - } + fts.depth = 0; + fts.match_case = true; /* we must match case here */ + fts.type = FTS_TYPE_FILE; /* regular files only */ - /* - * move back to previous directory - */ - (void) read_fts(NULL, -1, &cwd, NULL); - } /* - * unless -i remarks remarks.md MUST exist + * additional checks: we have to verify the file permissions are correct + * of files if not ignored. Yes this does mean there is some duplicated + * checking of paths. That also happens when -i info is not used. This + * could be said to be a mis-feature or it could be said to be defence + * in depth. You may choose your poison, keeping in mind that: + * + * osis sola facit venenum + * + * -- Paracelsus + * + * :-) */ - if (!ignore_remarks_md) { - /* - * remarks.md - */ - path = find_path(REMARKS_FILENAME, *submission_dir, -1, &cwd, false, &fts); - if (path == NULL) { - dbg(DBG_LOW, "%s does not exist and -i remarks not used", REMARKS_FILENAME); - ++all_extra_err_count; - } else { - /* - * now check the permissions are correct - */ - if (!is_mode(path, S_IRUSR | S_IRGRP | S_IROTH)) { - werr(1, __func__, "%s: permissions: %o != 0444", path, filemode(path));/*ooo*/ - ++all_extra_err_count; - } - free(path); - path = NULL; - } - /* - * move back to previous directory - */ - (void) read_fts(NULL, -1, &cwd, NULL); - } /* * now for the FTS find_paths() function: we need to find all files * that are NOT ignored (this is why there are two ignore lists) @@ -1310,22 +1020,103 @@ main(int argc, char *argv[]) for (c = 0; c < len; ++c) { u = dyn_array_value(found, char *, c); if (u == NULL) { - err(61, __func__, "NULL pointer in ignore list"); + err(61, __func__, "NULL pointer in found files list"); not_reached(); } /* * now we have to determine what file this is * - * Since we already tested specific files we only have to - * test files that have not been tested. + * Specific files have additional checks. */ - if (!strcmp(u, AUTH_JSON_FILENAME) || !strcmp(u, INFO_JSON_FILENAME) || !strcmp(u, REMARKS_FILENAME) || - !strcmp(u, MAKEFILE_FILENAME) || !strcmp(u, PROG_C_FILENAME) || !strcmp(u, PROG_FILENAME) || - !strcmp(u, ENTRY_JSON_FILENAME) || !strcmp(u, README_MD_FILENAME) || !strcmp(u, INDEX_HTML_FILENAME)) { - /* - * these we already tested, skip - */ + if (!strcmp(u, AUTH_JSON_FILENAME)) { + if (ignore_auth) { + continue; + } + if (!is_mode(u, S_IRUSR | S_IRGRP | S_IROTH)) { + werr(1, __func__, "%s: permissions: %o != 0444", u, filemode(u));/*ooo*/ + ++all_extra_err_count; + } + if (is_empty(u)) { + werr(1, __func__, "%s: file is empty", u); + ++all_extra_err_count; + } + found_auth = true; + } else if (!strcmp(u, INFO_JSON_FILENAME)) { + if (ignore_info) { + continue; + } + if (!is_mode(u, S_IRUSR | S_IRGRP | S_IROTH)) { + werr(1, __func__, "%s: permissions: %o != 0444", u, filemode(u));/*ooo*/ + ++all_extra_err_count; + } + if (is_empty(u)) { + werr(1, __func__, "%s: file is empty", u); + ++all_extra_err_count; + } + found_info = true; + } else if (!strcmp(u, REMARKS_FILENAME)) { + if (ignore_remarks_md) { + continue; + } + if (!is_mode(u, S_IRUSR | S_IRGRP | S_IROTH)) { + werr(1, __func__, "%s: permissions: %o != 0444", u, filemode(u));/*ooo*/ + ++all_extra_err_count; + } + if (is_empty(u)) { + werr(1, __func__, "%s: file is empty", u); + ++all_extra_err_count; + } + found_remarks = true; + } else if (!strcmp(u, MAKEFILE_FILENAME)) { + if (ignore_Makefile) { + continue; + } + if (!is_mode(u, S_IRUSR | S_IRGRP | S_IROTH)) { + werr(1, __func__, "%s: permissions: %o != 0444", u, filemode(u));/*ooo*/ + ++all_extra_err_count; + } + if (is_empty(u)) { + werr(1, __func__, "%s: file is empty", u); + ++all_extra_err_count; + } + found_Makefile = true; + } else if (!strcmp(u, PROG_C_FILENAME)) { + if (ignore_prog_c) { + continue; + } + if (!is_mode(u, S_IRUSR | S_IRGRP | S_IROTH)) { + werr(1, __func__, "%s: permissions: %o != 0444", u, filemode(u));/*ooo*/ + ++all_extra_err_count; + } + found_prog_c = true; + } else if (!strcmp(u, PROG_FILENAME)) { + if (ignore_prog) { + continue; + } + werr(1, __func__, "%s EXISTS and -i prog NOT used", PROG_FILENAME);/*ooo*/ + ++all_extra_err_count; + found_prog = true; + } else if (!strcmp(u, ENTRY_JSON_FILENAME)) { + if (ignore_entry) { + continue; + } + werr(1, __func__, "%s EXISTS and -w and -i entry NOT used", ENTRY_JSON_FILENAME);/*ooo*/ + ++all_extra_err_count; + found_entry = true; + } else if (!strcmp(u, README_MD_FILENAME)) { + if (ignore_README_md) { + continue; + } + werr(1, __func__, "%s EXISTS and -w and -i README NOT used", README_MD_FILENAME);/*ooo*/ + ++all_extra_err_count; + found_README = true; + } else if (!strcmp(u, INDEX_HTML_FILENAME)) { + if (ignore_index) { continue; + } + werr(1, __func__, "%s EXISTS and -w and -i index NOT used", INDEX_HTML_FILENAME);/*ooo*/ + ++all_extra_err_count; + found_index = true; } else { /* * not one of the specific filenames so we have to do @@ -1351,11 +1142,55 @@ main(int argc, char *argv[]) } } - /* - * move back to previous directory - */ - (void) read_fts(NULL, -1, &cwd, NULL); + } else { + werr(1, __func__, "no files found in submission directory: %s", *submission_dir);/*ooo*/ + ++all_extra_err_count; + } + /* + * now we have to verify that certain files do exist or do not exist + * depending on the -i option + */ + if (!ignore_entry && found_entry) { + werr(1, __func__, "%s EXISTS and -w and -i entry NOT used and ", ENTRY_JSON_FILENAME);/*ooo*/ + ++all_extra_err_count; + } + if (!ignore_auth && !found_auth) { + werr(1, __func__, "%s does NOT exist and -w and -i auth NOT used", AUTH_JSON_FILENAME);/*ooo*/ + ++all_extra_err_count; + } + if (!ignore_info && !found_info) { + werr(1, __func__, "%s does NOT exist and -w and -i info NOT used", INFO_JSON_FILENAME);/*ooo*/ + ++all_extra_err_count; + } + if (!ignore_README_md && found_README) { + werr(1, __func__, "%s EXISTS and -w and -i README NOT used", README_MD_FILENAME);/*ooo*/ + ++all_extra_err_count; + } + if (!ignore_index && found_index) { + werr(1, __func__, "%s EXISTS and -w and -i index NOT used", INDEX_HTML_FILENAME);/*ooo*/ + ++all_extra_err_count; + } + if (!ignore_Makefile && !found_Makefile) { + werr(1, __func__, "%s does NOT exist and -i Makefile NOT used", MAKEFILE_FILENAME);/*ooo*/ + ++all_extra_err_count; } + if (!ignore_prog_c && !found_prog_c) { + werr(1, __func__, "%s does NOT exist and -i prog.c NOT used", PROG_C_FILENAME);/*ooo*/ + ++all_extra_err_count; + } + if (!ignore_prog && found_prog) { + werr(1, __func__, "%s EXISTS and -i prog NOT used", PROG_FILENAME);/*ooo*/ + ++all_extra_err_count; + } + if (!ignore_remarks_md && !found_remarks) { + werr(1, __func__, "%s does NOT exist and -i remarks NOT used", REMARKS_FILENAME);/*ooo*/ + ++all_extra_err_count; + } + + /* + * move back to previous directory + */ + (void) read_fts(NULL, -1, &cwd, NULL); } /* @@ -1527,10 +1362,10 @@ main(int argc, char *argv[]) all_all_err_count = info_all_err_count + auth_all_err_count; /* - * report details of any .auth.json semantic errors + * report details of any .auth.json errors */ if (auth_all_err_count > 0) { - fpr(stderr, __func__, "What follows are semantic errors for .auth.json file: %s\n", auth_path); + fpr(stderr, __func__, "What follows are errors for .auth.json file: %s\n", auth_path); if (auth_count_err == NULL) { fpr(stderr, __func__, "auth_count_err is NULL!!!\n"); } else { @@ -1549,10 +1384,10 @@ main(int argc, char *argv[]) } /* - * report details of any .info.json semantic errors + * report details of any .info.json errors */ if (info_all_err_count > 0) { - fpr(stderr, __func__, "What follows are semantic errors for .info.json file: %s\n", info_path); + fpr(stderr, __func__, "What follows are errors for .info.json file: %s\n", info_path); if (info_count_err == NULL) { fpr(stderr, __func__, "info_count_err is NULL!!!\n"); } else { @@ -1571,7 +1406,7 @@ main(int argc, char *argv[]) } /* - * report on semantic count errors + * report on error count */ if (all_count_err_count > 0) { if (auth_count_err_count > 0) { @@ -1584,7 +1419,7 @@ main(int argc, char *argv[]) } /* - * report on semantic validation errors + * report on validation errors */ if (all_val_err_count > 0) { if (auth_val_err_count > 0) { @@ -1614,33 +1449,33 @@ main(int argc, char *argv[]) */ if (all_all_err_count > 0) { if (auth_all_err_count > 0) { - dbg(DBG_LOW, "total semantic errors for .auth.json: %ju", auth_all_err_count); + dbg(DBG_LOW, "total errors for .auth.json: %ju", auth_all_err_count); } if (info_all_err_count > 0) { - dbg(DBG_LOW, "total semantic errors for .info.json: %ju", info_all_err_count); + dbg(DBG_LOW, "total errors for .info.json: %ju", info_all_err_count); } - dbg(DBG_LOW, "total semantic errors for both files: %ju", all_all_err_count); + dbg(DBG_LOW, "total errors for both files: %ju", all_all_err_count); } /* - * summarize the JSON semantic check status + * summarize the JSON check status */ if (auth_all_err_count > 0) { if (auth_path == NULL) { - werr(1, __func__, "JSON semantic check failed for .auth.json file: ((NULL))"); /*ooo*/ + werr(1, __func__, "JSON check failed for .auth.json file: ((NULL))"); /*ooo*/ } else { - werr(1, __func__, "JSON semantic check failed for .auth.json file: %s", auth_path); /*ooo*/ + werr(1, __func__, "JSON check failed for .auth.json file: %s", auth_path); /*ooo*/ } } if (info_all_err_count > 0) { if (info_path == NULL) { - werr(1, __func__, "JSON semantic check failed for .info.json file: ((NULL))"); /*ooo*/ + werr(1, __func__, "JSON check failed for .info.json file: ((NULL))"); /*ooo*/ } else { - werr(1, __func__, "JSON semantic check failed for .info.json file: %s", info_path); /*ooo*/ + werr(1, __func__, "JSON check failed for .info.json file: %s", info_path); /*ooo*/ } } - if (all_all_err_count == 0) { - dbg(DBG_LOW, "JSON semantic check OK"); + if (all_all_err_count == 0 && all_extra_err_count == 0) { + dbg(DBG_LOW, "checks OK"); } /* * cleanup - except for info_stream and auth_stream. @@ -1682,6 +1517,9 @@ main(int argc, char *argv[]) free(info_path); info_path = NULL; } + /* + * also free the global ignored paths array + */ if (ignored_paths != NULL) { free_paths_array(&ignored_paths, false); ignored_paths = NULL; @@ -1711,12 +1549,7 @@ main(int argc, char *argv[]) */ reset_fts(&fts, true); /* - * now free the global ignored list as well - */ - free_paths_array(&ignored_paths, false); - ignored_paths = NULL; /* paranoia */ - /* - * free the paths and fund paths arrays too + * free the paths and found paths arrays too */ free_paths_array(&paths, false); paths = NULL; /* paranoia */ diff --git a/jparse/CHANGES.md b/jparse/CHANGES.md index e0fff6ea3..40e1e271e 100644 --- a/jparse/CHANGES.md +++ b/jparse/CHANGES.md @@ -1,5 +1,13 @@ # Significant changes in the JSON parser repo +## Release 2.2.30 2025-02-28 + +Add util function that uses `file_size()` called `is_empty()`. + +Updated `JPARSE_UTILS_VERSION` to `"1.0.26 2025-02-28"`. +Updated `UTIL_TEST_VERSION` to `"1.0.24 2025-02-28"`. + + ## Release 2.2.29 2025-02-27 The script `jparse_bug_report.sh` actually can have the script in the `TOOLS` diff --git a/jparse/util.c b/jparse/util.c index a0af392e9..20217de11 100644 --- a/jparse/util.c +++ b/jparse/util.c @@ -3847,6 +3847,52 @@ file_size(char const *path) return buf.st_size; } +/* + * is_empty - determine if the file is empty + * + * Return if a file is empty (size 0). + * + * given: + * path - the path to test + * + * returns: + * true ==> file size <= 0 + * false ==> file size > 0 + * + * NOTE: if the file does not exist (ENOENT) we return true. In other words if + * file_size() returns <= 0 it is true; otherwise it is false. + */ +bool +is_empty(char const *path) +{ + off_t size = -1; + + /* + * firewall + */ + if (path == NULL) { + err(181, __func__, "called with NULL path"); + not_reached(); + } + + size = file_size(path); + + if (size < 0) { + dbg(DBG_VHIGH, "path %s does not exist", path); + return true; + } else if (size == 0) { + dbg(DBG_VHIGH, "path %s is empty", path); + return true; + } + + /* + * return not empty + */ + dbg(DBG_VHIGH, "path %s is not empty, size: %jd", path, (intmax_t)size); + return false; +} + + /* * cmdprintf - malloc a safer shell command line for use with system() and popen() @@ -8842,7 +8888,7 @@ check_invalid_option(char const *prog, int ch, int opt) */ #include "../json_utf8.h" -#define UTIL_TEST_VERSION "1.0.23 2025-02-26" /* version format: major.minor YYYY-MM-DD */ +#define UTIL_TEST_VERSION "1.0.24 2025-02-28" /* version format: major.minor YYYY-MM-DD */ int main(int argc, char **argv) @@ -10103,6 +10149,14 @@ main(int argc, char **argv) fdbg(stderr, DBG_MED, "successfully deleted copied file util_test.copy.c"); } + /* + * now check that the size is empty + */ + if (!is_empty("util_test.copy.c")) { + errp(156, __func__, "just deleted file util_test.copy.c is not empty"); + not_reached(); + } + /* * free relpath */ @@ -10144,6 +10198,13 @@ main(int argc, char **argv) } else { fdbg(stderr, DBG_MED, "successfully deleted copied file util_test.copy.c"); } + /* + * now check that the size is empty + */ + if (!is_empty("util_test.copy.c")) { + errp(156, __func__, "just deleted file util_test.copy.c is not empty"); + not_reached(); + } /* * free relpath @@ -10192,6 +10253,15 @@ main(int argc, char **argv) } else { fdbg(stderr, DBG_MED, "successfully deleted copied file util_test.copy.c"); } + /* + * now check that the size is empty + */ + if (!is_empty("util.copy.o")) { + errp(156, __func__, "just deleted file util.copy.o is not empty"); + not_reached(); + } + + /* * relpath is NOT allocated so don't free! */ @@ -11769,6 +11839,15 @@ main(int argc, char **argv) } else { fdbg(stderr, DBG_MED, "successfully deleted created file %s", relpath); } + /* + * now check that the size is empty + */ + if (!is_empty(relpath)) { + errp(156, __func__, "just deleted file %s is not empty", relpath); + not_reached(); + } + + /* * restore earlier directory that might have happened with read_fts() @@ -11952,6 +12031,15 @@ main(int argc, char **argv) } else { fdbg(stderr, DBG_MED, "successfully deleted created file %s", relpath); } + /* + * now check that the size is empty + */ + if (!is_empty(relpath)) { + errp(156, __func__, "just deleted file %s is not empty", relpath); + not_reached(); + } + + /* * free paths_found array diff --git a/jparse/util.h b/jparse/util.h index f44103685..3668b9dec 100644 --- a/jparse/util.h +++ b/jparse/util.h @@ -294,6 +294,7 @@ extern bool fd_is_ready(char const *name, bool open_test_only, int fd); extern bool chk_stdio_printf_err(FILE *stream, int ret); extern void flush_tty(char const *name, bool flush_stdin, bool abort_on_error); extern off_t file_size(char const *path); +extern bool is_empty(char const *path); extern char *cmdprintf(char const *format, ...); extern char *vcmdprintf(char const *format, va_list ap); extern int shell_cmd(char const *name, bool flush_stdin, bool abort_on_error, char const *format, ...); diff --git a/jparse/version.h b/jparse/version.h index adecea8b7..5268dc654 100644 --- a/jparse/version.h +++ b/jparse/version.h @@ -34,7 +34,7 @@ * * NOTE: this should match the latest Release string in CHANGES.md */ -#define JPARSE_REPO_VERSION "2.2.28 2025-02-27" /* format: major.minor YYYY-MM-DD */ +#define JPARSE_REPO_VERSION "2.2.29 2025-02-28" /* format: major.minor YYYY-MM-DD */ /* * official jparse version @@ -49,7 +49,7 @@ /* * official utility functions (util.c) version */ -#define JPARSE_UTILS_VERSION "1.0.25 2025-02-26" /* format: major.minor YYYY-MM-DD */ +#define JPARSE_UTILS_VERSION "1.0.26 2025-02-28" /* format: major.minor YYYY-MM-DD */ #endif /* INCLUDE_JPARSE_VERSION_H */ diff --git a/mkiocccentry.c b/mkiocccentry.c index 452e9488b..bf256c856 100644 --- a/mkiocccentry.c +++ b/mkiocccentry.c @@ -2654,16 +2654,35 @@ check_submission_dir(struct info *infop, char *submit_path, char *topdir_path, /* * check for existence of Makefile * - * This is necessary only because if make clobber fails it is because the - * user did not have a clobber rule which although is not something wanted - * by the judges, one is allowed to override it. So as long as Makefile - * exists it is okay; otherwise it is not. (Of course if its size <= 0 it'll be - * an error too but that's flagged by check_Makefile()). + * This is necessary not only because if it doesn't exist something went + * wrong and also because if make clobber fails it's not an error. */ - if (!is_read("Makefile")) { + if (!is_read(MAKEFILE_FILENAME)) { err(4, __func__, "Makefile not a regular readable file in submission directory %s", submit_path);/*ooo*/ not_reached(); + } else if (is_empty(MAKEFILE_FILENAME)) { + err(4, __func__, "Makefile is empty in submission directory %s", submit_path);/*ooo*/ + not_reached(); + } + /* + * prog.c must exist but it may be empty (though it'll be flagged) + */ + if (!is_read(PROG_C_FILENAME)) { + err(4, __func__, "prog.c not a regular readable file in submission directory %s", submit_path);/*ooo*/ + not_reached(); + } + /* + * remarks.md must not be empty + */ + if (!is_read(REMARKS_FILENAME)) { + err(4, __func__, "remarks.md not a regular readable file in submission directory %s", submit_path);/*ooo*/ + not_reached(); + } else if (is_empty(REMARKS_FILENAME)) { + err(4, __func__, "remarks.md is empty in submission directory %s", submit_path);/*ooo*/ + not_reached(); } + + /* * run make clobber on Makefile */ diff --git a/soup/Makefile b/soup/Makefile index fd56bfa8c..01e98b504 100644 --- a/soup/Makefile +++ b/soup/Makefile @@ -2,7 +2,8 @@ # # soup - some delicious IOCCC soup recipes :-) # -# Copyright (c) 2022-2025 by Landon Curt Noll. All Rights Reserved. +# Copyright (c) 2021-2025 by Landon Curt Noll and Cody Boone Ferguson. +# All Rights Reserved. # # Permission to use, copy, modify, and distribute this software and # its documentation for any purpose and without fee is hereby granted, @@ -14,17 +15,26 @@ # source works derived from this source # binaries derived from this source or from derived source # -# LANDON CURT NOLL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, -# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO -# EVENT SHALL LANDON CURT NOLL BE LIABLE FOR ANY SPECIAL, INDIRECT OR -# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF -# USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -# PERFORMANCE OF THIS SOFTWARE. +# THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING +# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +# AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # -# chongo (Landon Curt Noll, http://www.isthe.com/chongo/index.html) /\oo/\ +# The recipes in this Makefile :-) and the tools in this directory were +# co-developed in 2021-2025 by Cody Boone Ferguson and Landon Curt Noll: +# +# @xexyl +# https://xexyl.net Cody Boone Ferguson +# https://ioccc.xexyl.net +# and: +# chongo (Landon Curt Noll, http://www.isthe.com/chongo/index.html) /\oo/\ +# +# "Because sometimes even the IOCCC Judges need some help." :-) # # Share and enjoy! :-) +# -- Sirius Cybernetics Corporation Complaints Division, JSON spec department. :-) #### diff --git a/soup/version.h b/soup/version.h index 9cda21688..5dda64a0f 100644 --- a/soup/version.h +++ b/soup/version.h @@ -121,12 +121,12 @@ /* * official txzchk version */ -#define TXZCHK_VERSION "1.1.14 2025-02-19" /* format: major.minor YYYY-MM-DD */ +#define TXZCHK_VERSION "1.1.15 2025-02-28" /* format: major.minor YYYY-MM-DD */ /* * official chkentry version */ -#define CHKENTRY_VERSION "1.1.5 2025-02-25" /* format: major.minor YYYY-MM-DD */ +#define CHKENTRY_VERSION "1.1.6 2025-02-28" /* format: major.minor YYYY-MM-DD */ /* * Version of info for JSON the .entry.json files. diff --git a/test_ioccc/Makefile b/test_ioccc/Makefile index 6ffa0c6b0..1635b089e 100644 --- a/test_ioccc/Makefile +++ b/test_ioccc/Makefile @@ -2,7 +2,8 @@ # # test_ioccc - mkiocccentry test tools # -# Copyright (c) 2022-2025 by Landon Curt Noll. All Rights Reserved. +# Copyright (c) 2021-2025 by Landon Curt Noll and Cody Boone Ferguson. +# All Rights Reserved. # # Permission to use, copy, modify, and distribute this software and # its documentation for any purpose and without fee is hereby granted, @@ -14,20 +15,30 @@ # source works derived from this source # binaries derived from this source or from derived source # -# LANDON CURT NOLL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, -# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO -# EVENT SHALL LANDON CURT NOLL BE LIABLE FOR ANY SPECIAL, INDIRECT OR -# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF -# USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -# PERFORMANCE OF THIS SOFTWARE. +# THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING +# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +# AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # -# chongo (Landon Curt Noll, http://www.isthe.com/chongo/index.html) /\oo/\ +# These tools were co-developed in 2021-2025 by Cody Boone Ferguson and Landon +# Curt Noll: +# +# @xexyl +# https://xexyl.net Cody Boone Ferguson +# https://ioccc.xexyl.net +# and: +# chongo (Landon Curt Noll, http://www.isthe.com/chongo/index.html) /\oo/\ +# +# "Because sometimes even the IOCCC Judges need some help." :-) # # Share and enjoy! :-) +# -- Sirius Cybernetics Corporation Complaints Division, JSON spec department. :-) #### + ############# # utilities # ############# @@ -678,6 +689,7 @@ clobber: legacy_clobber clean ${E} ${RM} ${RM_V} -f Makefile.orig ${E} ${RM} ${RM_V} -f tags ${LOCAL_DIR_TAGS} ${E} ${RM} ${RM_V} -rf test_iocccsize topdir workdir test_JSON + ${E} ${RM} ${RM_V} -rf slot/good/workdir slot/bad/workdir ${S} echo ${S} echo "${OUR_NAME}: make $@ ending" diff --git a/test_ioccc/chkentry_test.sh b/test_ioccc/chkentry_test.sh index b8a972add..f061d65e8 100755 --- a/test_ioccc/chkentry_test.sh +++ b/test_ioccc/chkentry_test.sh @@ -4,13 +4,8 @@ # # "Because grammar and syntax alone do not make a complete language." :-) # -# Use chkentry to test all the files under test_JSON{info,auth}.json/good/ and -# verify there are no JSON semantic errors. If any JSON semantic errors are -# detected, this script will exit non-zero. -# -# Use chkentry to test all the files under test_JSON{info,auth}.json/bad/ and -# verify there are JSON semantic errors. If any file is found to be free of -# JSON semantic errors, this script will exit non-zero. +# Use chkentry to test all the submission directories under +# test_ioccc/slot/{good,bad}. # # Copyright (c) 2022-2025 by Landon Curt Noll and Cody Boone Ferguson. # All Rights Reserved. @@ -56,9 +51,7 @@ export Q_FLAG="" export CHKENTRY="./chkentry" export LOGFILE="./test_ioccc/chkentry_test.log" export EXIT_CODE=0 -export INVALID_JSON_FOUND="" -export UNEXPECTED_SEMANTIC_ERROR="" -export SEMANTIC_ERROR_MISSED="" +export INVALID_DIRECTORY_FOUND="" export SLOT_TREE="./test_ioccc/slot" export CHKENTRY_TEST_VERSION="1.1.1 2025-02-28" @@ -81,13 +74,10 @@ export USAGE="usage: $0 [-h] [-V] [-v level] [-D dbg_level] [-J level] [-q] [-c Exit codes: 0 all OK - 1 all JSON files were valid JSON; however some chkentry semantic tests failed - 2 -h and help string printed or -V and version string printed + 1 one or more tests failed 3 invalid command line 4 missing or non-executable chkentry 5 missing or non-readable slot_tree directory or subdirectory - 6 some files were invalid JSON; chkentry correctly tested all other files - 7 some files were invalid JSON and some chkentry tests failed >= 10 internal error chkentry_test.sh version: $CHKENTRY_TEST_VERSION" @@ -177,7 +167,7 @@ if [[ ! -d $SLOT_TREE ]]; then exit 5 fi if [[ ! -r $SLOT_TREE ]]; then - echo "$0: ERROR: slot_tree not readable directory: $SLOT_TREE" 1>&2 + echo "$0: ERROR: slot_tree not a readable directory: $SLOT_TREE" 1>&2 exit 5 fi @@ -192,7 +182,14 @@ if [[ ! -d $GOOD_TREE ]]; then exit 5 fi if [[ ! -r $GOOD_TREE ]]; then - echo "$0: ERROR: slot_tree/good for chkentry not readable directory: $GOOD_TREE" 1>&2 + echo "$0: ERROR: slot_tree/good for chkentry not a readable directory: $GOOD_TREE" 1>&2 + exit 5 +fi + +# if slot/good/workdir does not exist, is not a directory or is not readable it +# is an error +if [[ ! -e "$GOOD_TREE/workdir" ]] || [[ ! -d "$GOOD_TREE/workdir" ]] || [[ ! -r "$GOOD_TREE/workdir" ]]; then + echo "$0: ERROR: slot/good/workdir does not exist" 1>&2 exit 5 fi @@ -210,7 +207,20 @@ if [[ ! -r $BAD_TREE ]]; then echo "$0: ERROR: slot_tree/bad for chkentry not readable directory: $BAD_TREE" 1>&2 exit 5 fi +# if slot/bad/workdir does not exist, is not a directory or is not readable it +# is an error +if [[ ! -e "$BAD_TREE/workdir" ]] || [[ ! -d "$BAD_TREE/workdir" ]] || [[ ! -r "$BAD_TREE/workdir" ]]; then + echo "$0: ERROR: slot/bad/workdir does not exist" 1>&2 + exit 5 +fi + +# make sure workdir under bad directory exists +# +if ! mkdir -p "$BAD_TREE"/workdir ; then + echo "$0: ERROR: could not make $BAD_TREE/workdir" 1>&2 + exit 5 +fi # remove logfile so that each run starts out with an empty file # @@ -234,7 +244,7 @@ fi # dbg_level internal test debugging level to use as in: chkentry -v dbg_level # json_dbg_level JSON parser debug level to use in: chkentry -J json_dbg_level # quiet_mode quiet mode to use in: chkentry -q -# directory directory to check +# workdir directory to check # # The return code of this function is non-zero if an internal error occurs OR if # the return value of chkentry is not 0. @@ -251,7 +261,7 @@ run_good_test() declare dbg_level="$2" declare json_dbg_level="$3" declare quiet_mode="$4" - declare directory="$5" + declare workdir="$5" # debugging # @@ -260,13 +270,13 @@ run_good_test() echo "$0: debug[9]: in run_good_test: dbg_level: $dbg_level" 1>&2 echo "$0: debug[9]: in run_good_test: json_dbg_level: $json_dbg_level" 1>&2 echo "$0: debug[9]: in run_good_test: quiet_mode: $quiet_mode" 1>&2 - echo "$0: debug[9]: in run_good_test: directory: $directory" 1>&2 + echo "$0: debug[9]: in run_good_test: workdir: $workdir" 1>&2 fi - # directory must exist + # workdir must exist # - if [[ ! -d "$directory" ]] || [[ ! -r "$directory" ]]; then - echo "$0: ERROR: directory is not a directory or unreadable: $directory" + if [[ ! -d "$workdir" ]] || [[ ! -r "$workdir" ]]; then + echo "$0: ERROR: workdir is not a directory or is unreadable: $workdir" exit 13 fi @@ -274,34 +284,35 @@ run_good_test() # if [[ -z $quiet_mode ]]; then if [[ $V_FLAG -ge 3 ]]; then - echo "$0: debug[3]: about to run test that must pass: $chkentry -v $dbg_level -J $json_dbg_level -- $directory >> ${LOGFILE} 2>&1" 1>&2 + echo "$0: debug[3]: about to run test that must pass: $chkentry -v $dbg_level -J $json_dbg_level -- $workdir >> ${LOGFILE} 2>&1" 1>&2 fi - echo "$0: about to run test that must pass: $chkentry -v $dbg_level -J $json_dbg_level -- $directory >> ${LOGFILE} 2>&1" >> "${LOGFILE}" - "$chkentry" -v "$dbg_level" -J "$json_dbg_level" -- "$directory" >> "${LOGFILE}" 2>&1 + echo "$0: about to run test that must pass: $chkentry -v $dbg_level -J $json_dbg_level -- $workdir >> ${LOGFILE} 2>&1" >> "${LOGFILE}" + "$chkentry" -v "$dbg_level" -J "$json_dbg_level" -- "$workdir" >> "${LOGFILE}" 2>&1 status="$?" else if [[ $V_FLAG -ge 3 ]]; then - echo "$0: debug[3]: about to run test that must pass: $chkentry -v $dbg_level -J $json_dbg_level -q -- $directory >> ${LOGFILE} 2>&1" 1>&2 + echo "$0: debug[3]: about to run test that must pass: $chkentry -v $dbg_level -J $json_dbg_level -q -- $workdir >> ${LOGFILE} 2>&1" 1>&2 fi - echo "$0: about to run test that must pass: $chkentry -v $dbg_level -J $json_dbg_level -q -- $directory >> ${LOGFILE} 2>&1" >> "${LOGFILE}" - "$chkentry" -v "$dbg_level" -J "$json_dbg_level" -q -- "$directory" >> "${LOGFILE}" 2>&1 + echo "$0: about to run test that must pass: $chkentry -v $dbg_level -J $json_dbg_level -q -- $workdir >> ${LOGFILE} 2>&1" >> "${LOGFILE}" + "$chkentry" -v "$dbg_level" -J "$json_dbg_level" -q -- "$workdir" >> "${LOGFILE}" 2>&1 status="$?" fi # examine test result # if [[ $status -eq 0 ]]; then - echo "$0: test $directory should PASS: chkentry PASS with exit code 0" 1>&2 >> "${LOGFILE}" + echo "$0: test $workdir should PASS: chkentry PASS with exit code 0" 1>&2 >> "${LOGFILE}" if [[ $V_FLAG -ge 3 ]]; then - echo "$0: debug[3]: test $directory should PASS: chkentry passed with exit code 0" 1>&2 + echo "$0: debug[3]: test $workdir should PASS: chkentry passed with exit code 0" 1>&2 fi else - echo "$0: test $directory should FAIL: chkentry FAIL correctly with exit code: $status" 1>&2 >> "${LOGFILE}" + echo "$0: test $workdir should PASS: chkentry FAILED with exit code: $status" 1>&2 >> "${LOGFILE}" if [[ $V_FLAG -ge 1 ]]; then if [[ $V_FLAG -ge 3 ]]; then - echo "$0: debug[3]: debug[3]: test $directory should FAIL: chkentry FAIL correctly with exit code: $status" 1>&2 + echo "$0: debug[3]: debug[3]: test $workdir should PASS: chkentry FAILED with exit code: $status" 1>&2 fi fi + EXIT_CODE=1 fi echo >> "${LOGFILE}" @@ -310,71 +321,128 @@ run_good_test() return } +# run_bad_test - run a single chkentry test on a directory that must pass +# +# usage: +# run_bad_test chkentry dbg_level json_dbg_level quiet_mode directory +# +# chkentry path to the chkentry program +# dbg_level internal test debugging level to use as in: chkentry -v dbg_level +# json_dbg_level JSON parser debug level to use in: chkentry -J json_dbg_level +# quiet_mode quiet mode to use in: chkentry -q +# workdir directory to check +# +# The return code of this function is non-zero if an internal error occurs OR if +# the return value of chkentry is 0. +# +run_bad_test() +{ + # parse args + # + if [[ $# -ne 5 ]]; then + echo "$0: ERROR: expected 5 args to run_bad_test, found $#" 1>&2 + exit 12 + fi + declare chkentry="$1" + declare dbg_level="$2" + declare json_dbg_level="$3" + declare quiet_mode="$4" + declare workdir="$5" + # debugging + # + if [[ $V_FLAG -ge 9 ]]; then + echo "$0: debug[9]: in run_bad_test: chkentry: $chkentry" 1>&2 + echo "$0: debug[9]: in run_bad_test: dbg_level: $dbg_level" 1>&2 + echo "$0: debug[9]: in run_bad_test: json_dbg_level: $json_dbg_level" 1>&2 + echo "$0: debug[9]: in run_bad_test: quiet_mode: $quiet_mode" 1>&2 + echo "$0: debug[9]: in run_bad_test: workdir: $workdir" 1>&2 + fi + # workdir must exist + # + if [[ ! -d "$workdir" ]] || [[ ! -r "$workdir" ]]; then + echo "$0: ERROR: workdir is not a directory or is unreadable: $workdir" + exit 13 + fi + + # perform the test + # + if [[ -z $quiet_mode ]]; then + if [[ $V_FLAG -ge 3 ]]; then + echo "$0: debug[3]: about to run test that MUST FAIL: $chkentry -v $dbg_level -J $json_dbg_level -- $workdir >> ${LOGFILE} 2>&1" 1>&2 + fi + echo "$0: about to run test that MUST FAIL: $chkentry -v $dbg_level -J $json_dbg_level -- $workdir >> ${LOGFILE} 2>&1" >> "${LOGFILE}" + "$chkentry" -v "$dbg_level" -J "$json_dbg_level" -- "$workdir" >> "${LOGFILE}" 2>&1 + status="$?" + else + if [[ $V_FLAG -ge 3 ]]; then + echo "$0: debug[3]: about to run test that MUST FAIL: $chkentry -v $dbg_level -J $json_dbg_level -q -- $workdir >> ${LOGFILE} 2>&1" 1>&2 + fi + echo "$0: about to run test that MUST FAIL: $chkentry -v $dbg_level -J $json_dbg_level -q -- $workdir >> ${LOGFILE} 2>&1" >> "${LOGFILE}" + "$chkentry" -v "$dbg_level" -J "$json_dbg_level" -q -- "$workdir" >> "${LOGFILE}" 2>&1 + status="$?" + fi + + # examine test result + # + if [[ $status -eq 0 ]]; then + echo "$0: test $workdir should FAIL: chkentry incorrectly passed with exit code 0" 1>&2 >> "${LOGFILE}" + if [[ $V_FLAG -ge 3 ]]; then + echo "$0: debug[3]: test $workdir: chkentry should FAIL: chkentry incorrectly passed with exit code 0" 1>&2 + fi + EXIT_CODE=1 + else + echo "$0: test $workdir should PASS: chkentry correctly passed with exit code: $status" 1>&2 >> "${LOGFILE}" + if [[ $V_FLAG -ge 1 ]]; then + if [[ $V_FLAG -ge 3 ]]; then + echo "$0: debug[3]: debug[3]: test $workdir should PASS: chkentry correctly passed with exit code: $status" 1>&2 + fi + fi + fi + echo >> "${LOGFILE}" + + # return + # + return +} # run tests that must pass: good # if [[ $V_FLAG -ge 3 ]]; then echo "$0: debug[3]: about to run chkentry tests that must pass: good/ files" 1>&2 fi -while read -r file; do - run_good_test "$CHKENTRY" "$DBG_LEVEL" "$JSON_DBG_LEVEL" "$Q_FLAG" "$file" -done < <(find "$GOOD_TREE" -mindepth 2 -maxdepth 2 -type d -print) +while read -r dir; do + run_good_test "$CHKENTRY" "$DBG_LEVEL" "$JSON_DBG_LEVEL" "$Q_FLAG" "$dir" +done < <(find "$GOOD_TREE/workdir" -mindepth 1 -maxdepth 1 -type d -print) # run tests that must fail: bad # -#if [[ $V_FLAG -ge 3 ]]; then -# echo "$0: debug[3]: about to run chkentry tests that must fail: bad/ files" 1>&2 -#fi -#while read -r file; do -# run_bad_test "$CHKENTRY" "$DBG_LEVEL" "$JSON_DBG_LEVEL" "$Q_FLAG" "$file" -#done < <(find "$BAD_TREE" -type d -print) +if [[ $V_FLAG -ge 3 ]]; then + echo "$0: debug[3]: about to run chkentry tests that must fail: bad/ files" 1>&2 +fi +while read -r dir; do + run_bad_test "$CHKENTRY" "$DBG_LEVEL" "$JSON_DBG_LEVEL" "$Q_FLAG" "$dir" +done < <(find "$BAD_TREE/workdir" -mindepth 1 -maxdepth 1 -type d -print) # determine exit code # -if [[ -z $INVALID_JSON_FOUND ]]; then - if [[ -z $UNEXPECTED_SEMANTIC_ERROR && -z $SEMANTIC_ERROR_MISSED ]]; then - EXIT_CODE=0 - echo "$0: all tests PASSED" >> "${LOGFILE}" - if [[ $V_FLAG -ge 1 ]]; then - echo "$0: debug[1]: all tests PASSED" 1>&2 - fi - echo "$0: about to exit: $EXIT_CODE" >> "${LOGFILE}" - else - EXIT_CODE=1 - echo "$0: all JSON files were valid JSON; however some chkentry semantic tests failed" >> "${LOGFILE}" - if [[ $V_FLAG -ge 1 ]]; then - echo "$0: debug[1]: all JSON files were valid JSON; however some chkentry semantic tests failed" 1>&2 - echo 1>&2 - echo "$0: Notice: chkentry semantic tests failed, see $LOGFILE for details" 1>&2 - echo "$0: ERROR: about to exit: $EXIT_CODE" 1>&2 - fi - echo "$0: ERROR: about to exit: $EXIT_CODE" >> "${LOGFILE}" +if [[ $EXIT_CODE -ne 0 ]]; then + echo "$0: one or more tests FAILED" >> "${LOGFILE}" + if [[ $V_FLAG -ge 1 ]]; then + echo "$0: debug[1]: one or more tests FAILED" 1>&2 + echo "$0: Notice: one or more directories failed chkentry tests, see $LOGFILE for details" 1>&2 + echo "$0: ERROR: about to exit: $EXIT_CODE" 1>&2 fi + echo "$0: about to exit: $EXIT_CODE" >> "${LOGFILE}" else - if [[ -z $UNEXPECTED_SEMANTIC_ERROR && -z $SEMANTIC_ERROR_MISSED ]]; then - EXIT_CODE=6 - echo "$0: some files were invalid JSON; chkentry correctly tested all other files" >> "${LOGFILE}" - if [[ $V_FLAG -ge 1 ]]; then - echo "$0: debug[1]: some files were invalid JSON; chkentry correctly tested all other files" 1>&2 - echo 1>&2 - echo "$0: Notice: some files were invalid JSON, see $LOGFILE for details" 1>&2 - echo "$0: ERROR: about to exit: $EXIT_CODE" 1>&2 - fi - echo "$0: ERROR: about to exit: $EXIT_CODE" >> "${LOGFILE}" - else - EXIT_CODE=7 - echo "$0: some files were invalid JSON and some chkentry tests failed" >> "${LOGFILE}" - if [[ $V_FLAG -ge 1 ]]; then - echo "$0: debug[1]: some files were invalid JSON and some chkentry tests failed" 1>&2 - echo 1>&2 - echo "$0: Notice: chkentry tests failed and some files were invalid JSON: see $LOGFILE for details" 1>&2 - echo "$0: ERROR: about to exit: $EXIT_CODE" 1>&2 - fi - echo "$0: ERROR: about to exit: $EXIT_CODE" >> "${LOGFILE}" + echo "$0: all tests PASSED" >> "${LOGFILE}" + if [[ $V_FLAG -ge 1 ]]; then + echo "$0: debug[1]: all tests PASSED" 1>&2 + echo 1>&2 fi + echo "$0: about to exit: $EXIT_CODE" >> "${LOGFILE}" fi # All Done!!! All Done!!! -- Jessica Noll, Age 2 diff --git a/txzchk.c b/txzchk.c index e0a61b685..d2bf0b939 100644 --- a/txzchk.c +++ b/txzchk.c @@ -805,15 +805,15 @@ check_file_size(char const *tarball_path, off_t size, struct txz_file *file) ++tarball.total_feathers; tarball.empty_info_json = true; warn("txzchk", "%s: found empty %s file", tarball_path, INFO_JSON_FILENAME); - } else if (!strcmp(file->basename, "remarks.md")) { + } else if (!strcmp(file->basename, REMARKS_FILENAME)) { ++tarball.total_feathers; tarball.empty_remarks_md = true; warn("txzchk", "%s: found empty remarks.md", tarball_path); - } else if (!strcmp(file->basename, "Makefile")) { + } else if (!strcmp(file->basename, MAKEFILE_FILENAME)) { ++tarball.total_feathers; tarball.empty_Makefile = true; warn("txzchk", "%s: found empty Makefile", tarball_path); - } else if (!strcmp(file->basename, "prog.c")) { + } else if (!strcmp(file->basename, PROG_C_FILENAME)) { /* this is NOT a feather: it's only for informational purposes! */ tarball.empty_prog_c = true; } @@ -823,11 +823,11 @@ check_file_size(char const *tarball_path, off_t size, struct txz_file *file) tarball.auth_json_size = size; } else if (!strcmp(file->basename, INFO_JSON_FILENAME)) { tarball.info_json_size = size; - } else if (!strcmp(file->basename, "remarks.md")) { + } else if (!strcmp(file->basename, REMARKS_FILENAME)) { tarball.remarks_md_size = size; - } else if (!strcmp(file->basename, "Makefile")) { + } else if (!strcmp(file->basename, MAKEFILE_FILENAME)) { tarball.Makefile_size = size; - } else if (!strcmp(file->basename, "prog.c")) { + } else if (!strcmp(file->basename, PROG_C_FILENAME)) { tarball.prog_c_size = size; } }