pax_global_header00006660000000000000000000000064150770463020014516gustar00rootroot0000000000000052 comment=0bf769e0fe710c101bd625571296efbd028b6395 libjodycode/000077500000000000000000000000001507704630200133515ustar00rootroot00000000000000libjodycode/.gitignore000066400000000000000000000006551507704630200153470ustar00rootroot00000000000000# # Build ignores # #.* *.o *.o.* *.a *.so* *.dll* *.exe *.obj *.lib apiver cacheinfo vercheck *.3.gz # # Never ignore these # !.gitignore # # Normal output and testing dirs # /build_date.h /libjodycode-*-*/ /libjodycode-*-*.zip /*.pkg.tar.* testscript.txt output.log # # Backups / patches # *~ *.orig *.rej /*.patch # # debugging and editor stuff # core .gdb_history .gdbinit .*.swp *.gcda *.gcno *.gcov # Mac OS .DS_Store libjodycode/CHANGES.txt000066400000000000000000000052031507704630200151620ustar00rootroot00000000000000libjodycode 4.1.1 (2025-10-25) - Fix moderate performance regression caused by jc_setup_unicode_terminal() - Performance optimization for Windows directory reading - Fix some man page issues - Add a project "skeleton" for easily starting new libjodycode programs libjodycode 4.1 (2025-09-25) - Add jc_setup_unicode_terminal() - Add size-prefixed string type JC_STR_T and some relevant functions - Move manual from section 7 to 3 - Update manual to include all existing function definitions - Build fixes for old Synology toolchains libjodycode 4.0.1 (2025-08-21) - Fix jc_link() return value on Windows libjodycode 4.0 (feature level 4) (2025-08-15) - Add loads of new jc_* functions and constants - Add jc_errno to replace errno - Completely remove API feature tables - Fixes for building on macOS and Hurd - Add rolling hash capability to jc_block_hash() - Add jc_get_d_namlen() to enable strlen() skips for directory objects - Add file linking functions with batch processing (incomplete, do not use yet) - Time functions now have nanosecond precision on Windows - Add jc_str_t data type (string prefixed with its own length) - Add jc_close() stub function - jc_get_proc_cacheinfo() is a stub function on non-Linux for future use libjodycode 3.1.1 (2024-04-01) - Several minor bug fixes and build fixes libjodycode 3.1 (feature level 2) (2023-07-02) - Alarms now increment jc_alarm_ring for each trigger instead of always setting to 1 libjodycode 3.0.1 (2023-06-17) - Fix alarms on Windows (no changes for non-Windows systems) libjodycode 3.0 (feature level 1) (2023-06-16) - Add new APIs (error, alarm) and enhance existing ones - No library calls call exit() outside of the OOM functions - New "feature level" number for incremental API addition checking libjodycode 2.0.1 (2023-05-12) - Makefile fixes for cross-compilation/non-x86 builds (no code/API changes) - On Windows, libjodycode.dll now contains embedded version information libjodycode 2.0 (2023-05-08) - New API versioning table system and helper code - size_suffix: add bit shifts to suffix table - Add a wrapper so jody_hash updates don't require editing anymore - Improved comments in libjodycode.h to document what the functions do libjodycode 1.3 (2023-05-06) - Update jody_hash to version 7.2 libjodycode 1.2 (2023-04-23) - Add size_suffix constants libjodycode 1.1 (2023-04-17) - Update jody_hash to version 6 - string_malloc is now deprecated and only a stub will build by default libjodycode 1.0 (2023-04-09) - Initial release - Includes the following code from other Jody Bruchon projects: cacheinfo, jody_hash, oom, paths, sort, string, string_malloc, strtoepoch, win_stat, win_unicode libjodycode/LICENSE.txt000066400000000000000000000021141507704630200151720ustar00rootroot00000000000000MIT License Copyright (C) 2014-2025 by Jody Bruchon Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. libjodycode/Makefile000066400000000000000000000176511507704630200150230ustar00rootroot00000000000000# libjodycode Makefile CFLAGS ?= -O2 -g PREFIX ?= /usr/local PROGRAM_NAME = libjodycode LIB_DIR ?= $(PREFIX)/lib INC_DIR ?= $(PREFIX)/include MAN_BASE_DIR ?= $(PREFIX)/share/man MAN3_DIR ?= $(MAN_BASE_DIR)/man3 CC ?= gcc INSTALL = install RM = rm -f LN = ln -sf RMDIR = rmdir -p MKDIR = mkdir -p INSTALL_PROGRAM = $(INSTALL) -m 0755 INSTALL_DATA = $(INSTALL) -m 0644 SO_SUFFIX = .so LIB_SUFFIX = .a # Make Configuration COMPILER_OPTIONS = -Wall -Wwrite-strings -Wcast-align -Wstrict-aliasing -Wstrict-prototypes -Wpointer-arith -Wundef COMPILER_OPTIONS += -Wshadow -Wfloat-equal -Waggregate-return -Wcast-qual -Wswitch-default -Wswitch-enum -Wconversion -Wunreachable-code -Wformat=2 COMPILER_OPTIONS += -D_FILE_OFFSET_BITS=64 -fstrict-aliasing -pipe -fPIC UNAME_S = $(shell uname -s) UNAME_M = $(shell uname -m) VERSION = $(shell grep -m 1 '^.define LIBJODYCODE_VER ' libjodycode.h | sed 's/[^"]*"//;s/".*//') VERSION_MAJOR = $(shell grep -m 1 '^.define LIBJODYCODE_VER ' libjodycode.h | sed 's/[^"]*"//;s/\..*//') SO_VER_FULL = $(SO_SUFFIX).$(VERSION) SO_VER_MAJOR = $(SO_SUFFIX).$(VERSION_MAJOR) CROSS_DETECT = $(shell true | $(CC) -dM -E - | grep -m 1 __x86_64 || echo "cross") # Are we running on a Windows OS? ifeq ($(OS), Windows_NT) ifndef NO_WINDOWS ON_WINDOWS=1 SO_SUFFIX=.dll LIB_SUFFIX=.lib SO_VER_FULL=$(SO_SUFFIX) SO_VER_MAJOR=$(SO_SUFFIX) endif endif ifeq ($(UNAME_S), Darwin) SO_SUFFIX = .dylib SO_VER_FULL = .$(VERSION)$(SO_SUFFIX) SO_VER_MAJOR = .$(VERSION_MAJOR)$(SO_SUFFIX) LINK_OPTIONS += -Wl,-install_name,$(PROGRAM_NAME)$(SO_VER_MAJOR) # Don't use unsupported compiler options on gcc 3/4 (Mac OS X 10.5.8 Xcode) GCCVERSION = $(shell expr `LC_ALL=C gcc -v 2>&1 | grep '[cn][cg] version' | sed 's/[^0-9]*//;s/[ .].*//'` \>= 5) STRIP_UNNEEDED = strip -S STRIP_DEBUG = strip -S else LINK_OPTIONS += -Wl,-soname,$(PROGRAM_NAME)$(SO_VER_MAJOR) GCCVERSION = 1 STRIP_UNNEEDED = strip --strip-unneeded STRIP_DEBUG = strip --strip-debug endif ifeq ($(GCCVERSION), 1) COMPILER_OPTIONS += -Wextra -Wstrict-overflow=5 -Winit-self endif ifdef USE_LTO COMPILER_OPTIONS += -flto LINK_OPTIONS += -flto endif # C standard selection ifndef USE_C99 COMPILER_OPTIONS += -std=gnu11 else COMPILER_OPTIONS += -std=gnu99 OBJS += c99_aligned_alloc.o endif # Debugging code inclusion ifdef LOUD DEBUG=1 CFLAGS += -DLOUD_DEBUG endif ifdef DEBUG CFLAGS += -DDEBUG else CFLAGS += -DNDEBUG endif ifdef HARDEN CFLAGS += -Wformat -Wformat-security -D_FORTIFY_SOURCE=2 -fstack-protector-strong -Wl,-z,relro -Wl,-z,now endif # MinGW needs this for printf() conversions to work ifdef ON_WINDOWS ifndef NO_UNICODE UNICODE=1 COMPILER_OPTIONS += -municode PROGRAM_SUFFIX=.exe endif ifeq ($(UNAME_S), MINGW32_NT-5.1) OBJS += winres_xp.o else OBJS += winres.o endif # COMPILER_OPTIONS += -D__USE_MINGW_ANSI_STDIO=1 COMPILER_OPTIONS += -DON_WINDOWS=1 endif # Do not build SIMD code if not on x86_64 ifneq ($(UNAME_M), x86_64) NO_SIMD=1 endif ifeq ($(CROSS_DETECT), cross) NO_SIMD=1 endif # SIMD SSE2/AVX2 jody_hash code ifdef NO_SIMD COMPILER_OPTIONS += -DNO_SIMD -DNO_SSE2 -DNO_AVX2 else SIMD_OBJS += jody_hash_simd.o ifdef NO_SSE2 COMPILER_OPTIONS += -DNO_SSE2 else SIMD_OBJS += jody_hash_sse2.o endif ifdef NO_AVX2 COMPILER_OPTIONS += -DNO_AVX2 else SIMD_OBJS += jody_hash_avx2.o endif endif CFLAGS += $(COMPILER_OPTIONS) $(CFLAGS_EXTRA) LDFLAGS += $(LINK_OPTIONS) # ADDITIONAL_OBJECTS - some platforms will need additional object files # to support features not supplied by their vendor. Eg: GNU getopt() #ADDITIONAL_OBJECTS += getopt.o OBJS += access.o alarm.o batch.o block_hash.o cacheinfo.o dir.o OBJS += error.o fopen.o jc_fwprint.o getcwd.o jody_hash.o link.o OBJS += linkfiles.o numstrcmp.o oom.o paths.o OBJS += remove.o rename.o size_suffix.o stat.o OBJS += string.o str_t.o time.o version.o win_unicode.o OBJS += $(ADDITIONAL_OBJECTS) all: manual sharedlib staticlib -@test "$(CROSS_DETECT)" = "cross" && echo "NOTICE: SIMD disabled: !x86_64 or a cross-compiler detected (CC = $(CC))" || true sharedlib: $(OBJS) $(SIMD_OBJS) $(CC) -shared -o $(PROGRAM_NAME)$(SO_VER_FULL) $(OBJS) $(SIMD_OBJS) $(LDFLAGS) $(CFLAGS) $(CFLAGS_EXTRA) -test "$(ON_WINDOWS)" != "1" && $(LN) $(PROGRAM_NAME)$(SO_VER_FULL) $(PROGRAM_NAME)$(SO_VER_MAJOR) -test "$(ON_WINDOWS)" != "1" && $(LN) $(PROGRAM_NAME)$(SO_VER_MAJOR) $(PROGRAM_NAME)$(SO_SUFFIX) staticlib: $(OBJS) $(SIMD_OBJS) $(AR) rcs libjodycode$(LIB_SUFFIX) $(OBJS) $(SIMD_OBJS) jody_hash_simd.o: $(CC) $(CFLAGS) $(COMPILER_OPTIONS) $(WIN_CFLAGS) $(CFLAGS_EXTRA) $(CPPFLAGS) -mavx2 -c -o jody_hash_simd.o jody_hash_simd.c jody_hash_avx2.o: jody_hash_simd.o $(CC) $(CFLAGS) $(COMPILER_OPTIONS) $(WIN_CFLAGS) $(CFLAGS_EXTRA) $(CPPFLAGS) -mavx2 -c -o jody_hash_avx2.o jody_hash_avx2.c jody_hash_sse2.o: jody_hash_simd.o $(CC) $(CFLAGS) $(COMPILER_OPTIONS) $(WIN_CFLAGS) $(CFLAGS_EXTRA) $(CPPFLAGS) -msse2 -c -o jody_hash_sse2.o jody_hash_sse2.c apiver: helper_code/libjodycode_apiver.c $(CC) $(CFLAGS) $(COMPILER_OPTIONS) $(WIN_CFLAGS) $(CFLAGS_EXTRA) -I. -o apiver helper_code/libjodycode_apiver.c vercheck: helper_code/libjodycode_check.c $(CC) $(CFLAGS) $(COMPILER_OPTIONS) $(WIN_CFLAGS) $(CFLAGS_EXTRA) -DJC_TEST -I. -c -o vercheck.o helper_code/libjodycode_check.c $(CC) $(CFLAGS) $(COMPILER_OPTIONS) $(WIN_CFLAGS) $(CFLAGS_EXTRA) -I. -L. -Wl,-Bstatic vercheck.o -ljodycode -Wl,-Bdynamic -o vercheck cacheinfo: $(CC) cacheinfo.c -DJC_TEST $(CFLAGS) $(LDFLAGS) -o cacheinfo lnk: tests/linkfiles_dedupe.c $(CC) -Wall -Wextra -pedantic $(CFLAGS) $(LDFLAGS) -I. -std=gnu11 tests/linkfiles_dedupe.c libjodycode.a -o lnk .c.o: $(CC) -c $(COMPILER_OPTIONS) $(CFLAGS) $(CPPFLAGS) $< -o $@ manual: gzip -9 < libjodycode.3 > libjodycode.3.gz winres.o: winres.rc winres.manifest.xml ./tune_winres.sh windres winres.rc winres.o winres_xp.o: winres_xp.rc ./tune_winres.sh windres winres_xp.rc winres_xp.o installdirs: test -e $(DESTDIR)$(LIB_DIR) || $(MKDIR) $(DESTDIR)$(LIB_DIR) test -e $(DESTDIR)$(INC_DIR) || $(MKDIR) $(DESTDIR)$(INC_DIR) test -e $(DESTDIR)$(MAN3_DIR) || $(MKDIR) $(DESTDIR)$(MAN3_DIR) installfiles: installdirs $(INSTALL_PROGRAM) $(PROGRAM_NAME)$(SO_VER_FULL) $(DESTDIR)$(LIB_DIR)/$(PROGRAM_NAME)$(SO_VER_FULL) -test "$(ON_WINDOWS)" != "1" && $(LN) $(PROGRAM_NAME)$(SO_VER_FULL) $(DESTDIR)$(LIB_DIR)/$(PROGRAM_NAME)$(SO_VER_MAJOR) -test "$(ON_WINDOWS)" != "1" && $(LN) $(PROGRAM_NAME)$(SO_VER_MAJOR) $(DESTDIR)$(LIB_DIR)/$(PROGRAM_NAME)$(SO_SUFFIX) $(INSTALL_DATA) $(PROGRAM_NAME)$(LIB_SUFFIX) $(DESTDIR)$(LIB_DIR)/$(PROGRAM_NAME)$(LIB_SUFFIX) $(INSTALL_DATA) $(PROGRAM_NAME).h $(DESTDIR)$(INC_DIR)/$(PROGRAM_NAME).h $(INSTALL_DATA) $(PROGRAM_NAME).3.gz $(DESTDIR)$(MAN3_DIR)/$(PROGRAM_NAME).3.gz install: installdirs installfiles uninstalldirs: -test -e $(DESTDIR)$(LIB_DIR) && $(RMDIR) $(DESTDIR)$(LIB_DIR) -test -e $(DESTDIR)$(INC_DIR) && $(RMDIR) $(DESTDIR)$(INC_DIR) -test -e $(DESTDIR)$(MAN3_DIR) && $(RMDIR) $(DESTDIR)$(MAN3_DIR) uninstallfiles: $(RM) $(DESTDIR)$(LIB_DIR)/$(PROGRAM_NAME)$(SO_VER_FULL) $(RM) $(DESTDIR)$(LIB_DIR)/$(PROGRAM_NAME)$(SO_VER_MAJOR) $(RM) $(DESTDIR)$(LIB_DIR)/$(PROGRAM_NAME)$(SO_SUFFIX) $(RM) $(DESTDIR)$(LIB_DIR)/$(PROGRAM_NAME)$(LIB_SUFFIX) $(RM) $(DESTDIR)$(INC_DIR)/$(PROGRAM_NAME).h $(RM) $(DESTDIR)$(MAN3_DIR)/$(PROGRAM_NAME).3.gz uninstall: uninstallfiles uninstalldirs test: ./test.sh stripped: sharedlib staticlib $(STRIP_UNNEEDED) libjodycode$(SO_SUFFIX) $(STRIP_DEBUG) libjodycode$(LIB_SUFFIX) objsclean: $(RM) $(OBJS) $(SIMD_OBJS) vercheck.o *.obj clean: objsclean $(RM) $(PROGRAM_NAME)$(SO_SUFFIX) $(PROGRAM_NAME)$(SO_VER_MAJOR) $(PROGRAM_NAME)$(SO_VER_FULL) $(RM) $(PROGRAM_NAME)$(LIB_SUFFIX) apiver cacheinfo vercheck $(PROGRAM_NAME).3.gz $(RM) *~ helper_code/*~ libjodycode.a libjodycode.so.* libjodycode.dll.* .*.un~ *.gcno *.gcda *.gcov distclean: objsclean clean $(RM) *.pkg.tar.* $(RM) -r $(PROGRAM_NAME)-*-*/ $(PROGRAM_NAME)-*-*.zip chrootpackage: +./chroot_build.sh package: +./generate_packages.sh libjodycode/README.md000066400000000000000000000100421507704630200146250ustar00rootroot00000000000000Introduction ------------------------------------------------------------------------------- libjodycode is a software code library containing code shared among several of the programs written by Jody Bruchon such as imagepile, jdupes, winregfs, and zeromerge. These shared pieces of code were copied between each program as they were updated. As the number of programs increased and keeping these pieces of code synced became more annoying, the decision was made to combine all of them into a single reusable shared library. Please consider financially supporting continued development of libjodycode using the links on my home page (Ko-fi, PayPal, SubscribeStar, etc.): https://www.jodybruchon.com/ Version compatibility ------------------------------------------------------------------------------- libjodycode 3.0 introduced a new "feature level" number which changes on every revision to the public API. Programs can check this number against the number that corresponds to the newest library interface that they use. Whenever any function or variable is added to the public API this number will increase. To find the number your program should store and check against this number, find every interface you use documented in FEATURELEVELS.txt and choose the highest feature level number out of those. In libjodycode 2.0 a new version table was introduced that maintains a separate version number for each logical section of the library; this table was removed in libjodycode 4.0 in favor of the feature level number. Programs can use the `libjodycode_check.c/.h` helper code provided to check for linking against an incompatible version of libjodycode. Copy the files to the program's code base, add `#include "libjodycode_check.h"` to the main C file, and add `if (libjodycode_version_check(verbose, bail) != 0) failure_action();` somewhere early in `main()`. Set verbose to 1 to output detailed error info via `fprintf(stderr, ...)` if a bad version is found. Set bail to 1 to have the check code immediately exit if a bad version is found instead of returning to the caller with a non-zero return value. Building information ------------------------------------------------------------------------------- Some parts of libjodycode use C11 aligned_alloc(). Some users have ancient toolchains only supporting C99 properly. libjodycode now includes support code to allow C99 compilation. To build using C99 instead of C11, use USE_C99: `make USE_C99=1` You can use link-time optimization (LTO) if your compiler supports it: `make USE_LTO=1` Contact information ------------------------------------------------------------------------------- General information, help, and tech info: https://www.jdupes.com/ Development, source code, releases: https://codeberg.org/jbruchon/libjodycode Have a bug report or questions? contact Jody Bruchon Legal information and software license ------------------------------------------------------------------------------- libjodycode is Copyright (C) 2014-2025 by Jody Bruchon The MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. libjodycode/access.c000066400000000000000000000016721507704630200147640ustar00rootroot00000000000000/* libjodycode: access() stdio call * * Copyright (C) 2014-2025 by Jody Bruchon * Released under The MIT License */ #include #include #ifndef ON_WINDOWS #include #endif #include "likely_unlikely.h" #include "libjodycode.h" #ifdef UNICODE #define WIN32_LEAN_AND_MEAN #include #include #endif /* Check file exist/read/write, converting for Windows if necessary */ int jc_access(const char *pathname, int mode) { int retval; #ifdef UNICODE JC_WCHAR_T *widename; #endif if (unlikely(pathname == NULL)) { jc_errno = EFAULT; return -1; } #ifdef ON_WINDOWS #ifdef UNICODE if (jc_string_to_wstring(pathname, &widename) != 0) { jc_errno = ENOMEM; return -1; } retval = _waccess(widename, mode); free(widename); #else retval = _access(pathname, mode); #endif #else retval = access(pathname, mode); #endif if (retval != 0) jc_errno = errno; return retval; } libjodycode/alarm.c000066400000000000000000000037021507704630200146130ustar00rootroot00000000000000/* libjodycode: cross-platform alarms * * Copyright (C) 2023-2025 by Jody Bruchon * Released under The MIT License */ #ifdef ON_WINDOWS #define _WIN32_WINNT 0x0500 #define WIN32_LEAN_AND_MEAN #include #else #include #endif #include #include #include #include #ifndef ON_WINDOWS #include #endif #include "likely_unlikely.h" #include "libjodycode.h" int jc_alarm_ring = 0; #ifdef ON_WINDOWS static HANDLE hTimer; #else static int jc_alarm_repeat = 0; #endif /* ON_WINDOWS */ #ifdef ON_WINDOWS void CALLBACK jc_catch_alarm(PVOID arg1, BOOLEAN arg2) { (void)arg1; (void)arg2; jc_alarm_ring++; return; } int jc_start_alarm(const unsigned int seconds, const int repeat) { unsigned int secs = seconds * 1000; unsigned int period = 0; if (repeat != 0) period = secs; if (!CreateTimerQueueTimer(&hTimer, NULL, (WAITORTIMERCALLBACK)jc_catch_alarm, 0, secs, period, 0)) { jc_errno = jc_GetLastError(); return JC_EALARM; } jc_alarm_ring++; return 0; } int jc_stop_alarm(void) { if (CloseHandle(hTimer) == 0) { jc_errno = jc_GetLastError(); return JC_EALARM; } return 0; } #else /* not ON_WINDOWS */ void jc_catch_alarm(const int signum) { (void)signum; jc_alarm_ring++; if (jc_alarm_repeat != 0) alarm(1); return; } int jc_start_alarm(const unsigned int seconds, const int repeat) { struct sigaction sa_run; memset(&sa_run, 0, sizeof(struct sigaction)); sa_run.sa_handler = jc_catch_alarm; if (repeat != 0) jc_alarm_repeat = 1; if (sigaction(SIGALRM, &sa_run, NULL) != 0) { jc_errno = errno; return JC_EALARM; } alarm(seconds); return 0; } int jc_stop_alarm(void) { struct sigaction sa_stop; alarm(0); memset(&sa_stop, 0, sizeof(struct sigaction)); sa_stop.sa_handler = SIG_IGN; jc_alarm_repeat = 0; if (sigaction(SIGALRM, &sa_stop, NULL) != 0) { jc_errno = errno; return JC_EALARM; } return 0; } #endif /* ON_WINDOWS */ libjodycode/batch.c000066400000000000000000000032331507704630200145770ustar00rootroot00000000000000/* libjodycode: fileinfo batch handlers * * Copyright (C) 2014-2025 by Jody Bruchon * Released under The MIT License */ #include #include #include "libjodycode.h" #include "likely_unlikely.h" /* allocs: 0 = just the batch, 1 = with dirents, 2 = with stats, 3 = with both */ struct jc_fileinfo_batch *jc_fileinfo_batch_alloc(const int filecnt, const int stat, const int namlen) { struct jc_fileinfo_batch *batch; struct JC_STAT *stats; struct JC_DIRENT *dirents; int i; if (unlikely(filecnt <= 0)) return NULL; batch = calloc(1, (size_t)((int)sizeof(struct jc_fileinfo_batch) + ((int)sizeof(struct jc_fileinfo) * filecnt))); if (batch == NULL) return NULL; batch->count = filecnt; if (stat != 0) { stats = calloc(1, (size_t)(((int)sizeof(struct JC_STAT) * filecnt))); if (stats == NULL) goto error_cleanup; for (i = 0; i < filecnt; i++) batch->files[i].stat = (struct JC_STAT *)((uintptr_t)stats + (uintptr_t)(((int)sizeof(struct JC_STAT) * i))); } if (namlen != 0) { dirents = calloc(1, (size_t)(((int)sizeof(struct JC_DIRENT) + namlen) * filecnt)); if (dirents == NULL) goto error_cleanup; for (i = 0; i < filecnt; i++) batch->files[i].dirent = (struct JC_DIRENT *)((uintptr_t)dirents + (uintptr_t)(((int)sizeof(struct JC_DIRENT) + namlen) * i)); } return batch; error_cleanup: jc_fileinfo_batch_free(batch); return NULL; } void jc_fileinfo_batch_free(struct jc_fileinfo_batch * const restrict batch) { if (unlikely(batch == NULL)) return; if (batch->files[0].stat != NULL) free(batch->files[0].stat); if (batch->files[0].dirent != NULL) free(batch->files[0].dirent); free(batch); return; } libjodycode/block_hash.c000066400000000000000000000007451507704630200156200ustar00rootroot00000000000000/* libjodycode: wrapper stub for jody_hash * * Copyright (C) 2023-2025 by Jody Bruchon * Released under The MIT License */ #include #include "jody_hash.h" #include "libjodycode.h" int jc_block_hash(const enum jc_e_hash type, jodyhash_t *data, jodyhash_t *hash, const size_t count) { switch (type) { default: case NORMAL: return jody_block_hash(data, hash, count); case ROLLING: return jody_rolling_block_hash(data, hash, count); } } libjodycode/build_msvc.bat000066400000000000000000000001771507704630200161750ustar00rootroot00000000000000cl /DON_WINDOWS /DUNICODE /O2 /W4 /std:c17 /c *.c link /lib *.obj /out:libjodycode.lib link /dll *.obj /out:libjodycode.dll libjodycode/c99_aligned_alloc.c000066400000000000000000000007331507704630200167610ustar00rootroot00000000000000/* Implementation of C11 aligned_alloc() using POSIX 2001 posix_memalign() * * Created by Jody Bruchon * * Released into the public domain, and under the Creative Commons 0 license: * https://creativecommons.org/public-domain/cc0/ */ #include #include "c99_aligned_alloc.h" void *aligned_alloc(size_t alignment, size_t size) { void *mem; int i; i = posix_memalign(&mem, alignment, size); if (i != 0) mem = NULL; return mem; } libjodycode/c99_aligned_alloc.h000066400000000000000000000012131507704630200167600ustar00rootroot00000000000000/* Implementation of C11 aligned_alloc() using POSIX 2001 posix_memalign() * * Created by Jody Bruchon * * Released into the public domain, and under the Creative Commons 0 license: * https://creativecommons.org/public-domain/cc0/ */ #ifndef C99_ALIGNED_ALLOC_H #define C99_ALIGNED_ALLOC_H #ifdef __cplusplus extern "C" { #endif /* Required for uint64_t */ #include #if !(_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) #error POSIX 2001 compliant environment is required #endif extern void *aligned_alloc(size_t alignment, size_t size); #ifdef __cplusplus } #endif #endif /* C99_ALIGNED_ALLOC_H */ libjodycode/cacheinfo.c000066400000000000000000000100631507704630200154340ustar00rootroot00000000000000/* libjodycode: detect and report size of CPU caches on Linux * * Copyright (C) 2017-2025 by Jody Bruchon * Distributed under The MIT License * * If an error occurs or a cache is missing, zeroes are returned * Unified caches populate l1/l2/l3; split caches populate lXi/lXd instead */ #include #include #include #include #include "likely_unlikely.h" #include "libjodycode.h" /* For non-Linux systems, use a default dummy set of numbers. * This opens up the possibility of future implementation on Windows and macOS. */ #ifndef __linux__ static const struct jc_proc_cacheinfo static_cacheinfo = { .l1 = 0, .l1i = 0, .l1d = 0, .l2 = 0, .l2i = 0, .l2d = 0, .l3 = 0, .l3i = 0, .l3d = 0 }; struct jc_proc_cacheinfo *jc_get_proc_cacheinfo(int cleanup) { static struct jc_proc_cacheinfo *pci = NULL; if (unlikely(cleanup != 0)) { if (likely(pci != NULL)) free(pci); return NULL; } pci = (struct jc_proc_cacheinfo *)calloc(1, sizeof(struct jc_proc_cacheinfo)); if (pci == NULL) return NULL; memcpy(pci, &static_cacheinfo, sizeof(struct jc_proc_cacheinfo)); return pci; } #endif /* not __linux */ /* None of this code is useful outside of Linux */ #ifdef __linux__ static char *pathidx; static char buf[16]; static char path[64] = "/sys/devices/system/cpu/cpu0/cache/index"; /*** End declarations, begin code ***/ /* Linux sysfs */ static size_t read_procfile(const char * const restrict name) { FILE *fp; size_t i; if (unlikely(name == NULL)) return 0; memset(buf, 0, 16); /* Create path */ *pathidx = '\0'; strcpy(pathidx, name); fp = fopen(path, "rb"); if (fp == NULL) return 0; i = fread(buf, 1, 16, fp); if (ferror(fp)) return 0; fclose(fp); *(buf + 15) = '\0'; return i; } struct jc_proc_cacheinfo *jc_get_proc_cacheinfo(int cleanup) { static struct jc_proc_cacheinfo *pci = NULL; char *idx; size_t i; size_t size; int level; char type; char index; if (unlikely(cleanup != 0)) { if (likely(pci != NULL)) free(pci); return NULL; } if (pci != NULL) return pci; i = strlen(path); if (i > 48) return NULL; idx = path + i; pathidx = idx + 1; *pathidx = '/'; pathidx++; pci = (struct jc_proc_cacheinfo *)calloc(1, sizeof(struct jc_proc_cacheinfo)); if (pci == NULL) return NULL; for (index = '0'; index < '9'; index++) { *idx = index; /* Get the level for this index */ if (read_procfile("level") == 0) goto finish; if (*buf < '1' || *buf > '3') goto error; else level = (*buf) + 1 - '1'; /* Get the size */ if (read_procfile("size") == 0) goto error; size = (size_t)atoi(buf) * 1024; if (size == 0) goto error; /* Get the type */ if (read_procfile("type") == 0) goto error; if (*buf != 'U' && *buf != 'I' && *buf != 'D') goto error; type = *buf; /* Act on it */ switch (type) { case 'D': switch (level) { case 1: pci->l1d = size; break; case 2: pci->l2d = size; break; case 3: pci->l3d = size; break; default: goto error; }; break; case 'I': switch (level) { case 1: pci->l1i = size; break; case 2: pci->l2i = size; break; case 3: pci->l3i = size; break; default: goto error; }; break; case 'U': switch (level) { case 1: pci->l1 = size; break; case 2: pci->l2 = size; break; case 3: pci->l3 = size; break; default: goto error; }; break; default: break; } /* Continue to next index */ } finish: if (unlikely(pci->l1d == 0 && pci->l1i == 0 && pci->l1 == 0)) goto error; return pci; error: if (pci != NULL) free(pci); return NULL; } #endif /* __linux__ */ /* This is for testing only */ #ifdef JC_TEST int main(void) { static struct jc_proc_cacheinfo pci; jc_get_proc_cacheinfo(&pci); printf("Cache info:\n\n"); printf("L1 I %4luK, D %4luK, U %4luK\n", pci.l1i >> 10, pci.l1d >> 10, pci.l1 >> 10); printf("L2 I %4luK, D %4luK, U %4luK\n", pci.l2i >> 10, pci.l2d >> 10, pci.l2 >> 10); if ((pci.l3d | pci.l3i | pci.l3) != 0) printf("L3 I %4luK, D %4luK, U %4luK\n", pci.l3i >> 10, pci.l3d >> 10, pci.l3 >> 10); else printf("L3 does not exist\n"); return 0; } #endif libjodycode/chroot_build.sh000077500000000000000000000045231507704630200163710ustar00rootroot00000000000000#!/bin/sh # Jody's generic chroot build script # Version 1.0 ARCHES="i386 x86-64 uclibc-i386 uclibc-x86-64" test -z "$NAME" && NAME="$(basename "$(pwd)")" test -e "libjodycode.h" && VER="$(grep '#define LIBJODYCODE_VER ' libjodycode.h | tr -d \\\" | cut -d' ' -f3)" test -z "$VER" && VER=0 export NAME export VER export CHROOT_BASE=/chroots export WD="$(pwd)" export PKG="pkg" echo "chroot builder: building '$NAME' version '$VER'" trap clean_exit INT QUIT ABRT HUP clean_exit () { umount $CHROOT/proc $CHROOT/sys $CHROOT/tmp $CHROOT/dev $CHROOT/usr/src $CHROOT/home } do_build () { test -z "$WD" && echo "WD not set, aborting" && exit 1 test -z "$PKG" && echo "PKG not set, aborting" && exit 1 if [ -e ./generate_packages.sh ] then ./generate_packages.sh else make clean PN="${NAME}_$VER-$ARCH.pkg.tar.xz" if ! make -j$JOBS all then echo "Build failed"; exit 1 else echo "WD/PKG: $WD/$PKG" test -d $WD/$PKG && rm -rf $WD/$PKG mkdir $WD/$PKG make DESTDIR=$WD/$PKG install && \ tar -C pkg -c usr | xz -e > "$PN" # Set ownership to current directory ownership chown "$(stat -c '%u:%g' .)" "$PN" echo "Built $PN" make clean fi fi } if [ "$(id -u)" != "0" ] then echo "You must be root to auto-build chroot packages." exit 1 fi if [ "$DO_CHROOT_BUILD" = "1" ] then test -z "$1" && echo "No arch specified" && exit 1 test ! -d "$1" && echo "Not a directory: $1" && exit 1 cd $1 export WD="$1" do_build echo "finished: $1" exit else echo baz export DO_CHROOT_BUILD=1 for ARCH in $ARCHES do export ARCH export CHROOT="$CHROOT_BASE/$ARCH" test ! -d $CHROOT && echo "$CHROOT not present, not building $ARCH package." && continue echo "Performing package build for $CHROOT" test ! -x $CHROOT/bin/sh && echo "$CHROOT does not seem to be a chroot; aborting." && exit 1 mount --bind /dev $CHROOT/dev || clean_exit mount --bind /usr/src $CHROOT/usr/src || clean_exit mount --bind /home $CHROOT/home || clean_exit mount -t proc proc $CHROOT/proc || clean_exit mount -t sysfs sysfs $CHROOT/sys || clean_exit mount -t tmpfs tmpfs $CHROOT/tmp || clean_exit if echo "$ARCH" | grep -q "i386" then linux32 chroot $CHROOT $WD/$0 $WD else chroot $CHROOT $WD/$0 $WD fi umount $CHROOT/proc $CHROOT/sys $CHROOT/tmp $CHROOT/dev $CHROOT/usr/src $CHROOT/home test -d $WD/$PKG && rm -rf $WD/$PKG done fi libjodycode/dir.c000066400000000000000000000106361507704630200143010ustar00rootroot00000000000000/* libjodycode: directory calls * * Copyright (C) 2014-2025 by Jody Bruchon * Released under The MIT License */ #ifdef ON_WINDOWS #define WIN32_LEAN_AND_MEAN #include #else #include #endif /* ON_WINDOWS */ #include #include #include #include #include "likely_unlikely.h" #include "libjodycode.h" #ifdef ON_WINDOWS JC_DIR *dirp_head = NULL; #endif /* Open a directory; handle Windows doing readdir() equivalent too */ JC_DIR *jc_opendir(const char * restrict path) { #ifdef ON_WINDOWS JC_DIR *dirp; char *tempname, *p; WIN32_FIND_DATA ffd; HANDLE hFind = INVALID_HANDLE_VALUE; #ifdef UNICODE JC_WCHAR_T *widename = NULL; #endif if (unlikely(path == NULL)) { jc_errno = EFAULT; return NULL; } tempname = (char *)malloc(JC_PATHBUF_SIZE + 4); if (unlikely(tempname == NULL)) goto error_nomem; /* Windows requires \* at the end of directory names */ strncpy_s(tempname, JC_PATHBUF_SIZE, path, JC_PATHBUF_SIZE - 1); p = tempname + strlen(tempname) - 1; if (*p == '/' || *p == '\\') *p = '\0'; strncat_s(tempname, JC_PATHBUF_SIZE, "\\*", JC_PATHBUF_SIZE - 1); #ifdef UNICODE widename = (wchar_t *)malloc(JC_PATHBUF_SIZE + 4); if (unlikely(widename == NULL)) goto error_nomem; if (unlikely(jc_string_to_wstring(tempname, &widename) != 0)) goto error_nomem; #endif /* UNICODE */ #ifdef UNICODE hFind = FindFirstFileExW(widename, FindExInfoStandard, &ffd, FindExSearchNameMatch, NULL, FIND_FIRST_EX_LARGE_FETCH); free(widename); #else hFind = FindFirstFileExA(tempname, FindExInfoStandard, &ffd, FindExSearchNameMatch, NULL, FIND_FIRST_EX_LARGE_FETCH); #endif free(tempname); if (unlikely(hFind == INVALID_HANDLE_VALUE)) goto error_fff; if (jc_ffd_to_dirent(&dirp, hFind, &ffd) != 0) return NULL; /* attach dirp to a linked list of them */ dirp->next = dirp_head; dirp_head = dirp; return dirp; error_fff: jc_errno = jc_GetLastError(); return NULL; error_nomem: if (tempname != NULL) free(tempname); #ifdef UNICODE if (widename != NULL) free(widename); #endif jc_errno = ENOMEM; return NULL; #else JC_DIR *retval; retval = opendir(path); if (retval == NULL) jc_errno = errno; return retval; #endif /* ON_WINDOWS */ } /* Extract d_namlen from struct dirent */ size_t jc_get_d_namlen(const struct JC_DIRENT * const restrict dirent) { if (unlikely(dirent == NULL)) goto error_bad_dirent; #ifdef _DIRENT_HAVE_D_NAMLEN return dirent->d_namlen; #elif defined _DIRENT_HAVE_D_RECLEN const size_t base = (sizeof(struct dirent) - sizeof(((struct dirent *)0)->d_name)) - offsetof(struct dirent, d_name) - 1; size_t skip; skip = dirent->d_reclen - (sizeof(struct dirent) - sizeof(((struct dirent *)0)->d_name)); if (skip > 0) skip -= base; return skip + strlen(dirent->d_name + skip); #else return strlen(dirent->d_name); #endif error_bad_dirent: jc_errno = EFAULT; return 0; } /* Open a directory; handle Windows doing readdir() equivalent too */ struct JC_DIRENT *jc_readdir(JC_DIR *dirp) { #ifdef ON_WINDOWS int i; if (unlikely(dirp == NULL)) goto error_bad_dirp; if (dirp->cached == 1) { dirp->cached = 0; goto skip_fnf; } i = FindNextFile(dirp->hFind, &(dirp->ffd)); if (i == 0) goto error_fnf; if (jc_ffd_to_dirent(&dirp, NULL, NULL) != 0) return NULL; skip_fnf: return &(dirp->dirent); error_bad_dirp: jc_errno = EFAULT; return NULL; error_fnf: jc_errno = jc_GetLastError(); return NULL; #else /* Non-Windows */ struct JC_DIRENT *retval; errno = 0; retval = readdir(dirp); if (retval == NULL) jc_errno = errno; return retval; #endif /* ON_WINDOWS */ } #ifdef ON_WINDOWS /* De-allocate a directory struct and remove from the list */ static void jc_destroy_dirp(JC_DIR * const restrict dirp) { JC_DIR *prev, *cur; if (dirp == NULL) return; for (prev = NULL, cur = dirp_head; cur != dirp && cur != NULL; prev = cur, cur = cur->next); if (cur != dirp) return; if (dirp == dirp_head) { dirp_head = cur->next; free(dirp); return; } prev->next = cur->next; free(dirp); return; } #endif /* ON_WINDOWS */ /* Close a directory */ int jc_closedir(JC_DIR * const restrict dirp) { int retval; #ifdef ON_WINDOWS if (unlikely(dirp == NULL)) { jc_errno = EBADF; return -1; } retval = FindClose(dirp->hFind); jc_destroy_dirp(dirp); if (retval != 0) jc_errno = jc_GetLastError(); return -1; #else retval = closedir(dirp); if (retval != 0) jc_errno = errno; return retval; #endif /* ON_WINDOWS */ } libjodycode/error.c000066400000000000000000000040161507704630200146470ustar00rootroot00000000000000/* libjodycode: error strings and printing functions * * Copyright (C) 2023-2025 by Jody Bruchon * Released under The MIT License */ #include #include #include "libjodycode.h" int32_t jc_errno; struct jc_error { const char *name; const char *desc; }; #define JC_ERRCNT 15 static const int errcnt = JC_ERRCNT; static const struct jc_error jc_error_list[JC_ERRCNT + 1] = { { "no_error", "success" }, // 0 - not a real error { "null", "function received a bad NULL parameter" }, // 1 { "cdotdot", "jc_collapse_dotdot() call failed" }, // 2 { "grn_end_dir", "get_relative_name() result has directory at end" }, // 3 { "bad_errnum", "invalid error number" }, // 4 { "bad_argv", "bad argv pointer" }, // 5 { "mb_wc_fail", "a multibyte/wide char conversion failed" }, // 6 { "alarm_fail", "alarm call failed" }, // 7 { "alloc_fail", "memory allocation failed" }, // 8 { "numstrcmp", "jc_numeric_strcmp() was passed a NULL pointer" }, // 9 { "datetime", "date/time string is invalid" }, // 10 { "win32api", "a Win32 API call failed" }, // 11 { "kernelver", "kernel version is too old" }, // 12 { "no_memory", "memory allocation failed (out of memory)" }, // 13 { "vbuf_fail", "setvbuf() call failed" }, //14 { NULL, NULL }, // 15 }; const char *jc_get_errname(int errnum) { if (errnum > errcnt) return NULL; if (errnum < 0) errnum = -errnum; return jc_error_list[errnum].name; } const char *jc_get_errdesc(int errnum) { if (errnum > errcnt) return NULL; if (errnum < 0) errnum = -errnum; return jc_error_list[errnum].desc; } int jc_print_error(int errnum) { if (errnum > errcnt) return JC_EBADERR; if (errnum < 0) errnum = -errnum; fprintf(stderr, "error: %s (%s)\n", jc_error_list[errnum].desc, jc_error_list[errnum].name); return 0; } #ifdef JC_TEST int main(void) { int i; for (i = 0; i < errcnt; i++) printf("[%d] %s: %s\n", i, jc_get_errname(i), jc_get_errdesc(i)); for (i = 0; i < errcnt; i++) jc_print_error(i); } #endif libjodycode/fopen.c000066400000000000000000000021131507704630200146210ustar00rootroot00000000000000/* libjodycode: fopen() stdio call * * Copyright (C) 2014-2025 by Jody Bruchon * Released under The MIT License */ #include #include #include "likely_unlikely.h" #include "libjodycode.h" #ifdef UNICODE #define WIN32_LEAN_AND_MEAN #include #endif /* Open a file, converting the name for Unicode on Windows if necessary */ FILE *jc_fopen(const char *pathname, const JC_WCHAR_T *mode) { FILE *fp; #ifdef ON_WINDOWS errno_t retval; #endif #ifdef UNICODE JC_WCHAR_T *widename; #endif if (unlikely(pathname == NULL || mode == NULL)) { jc_errno = EFAULT; return NULL; } #ifdef ON_WINDOWS #ifdef UNICODE if (jc_string_to_wstring(pathname, &widename) != 0) { jc_errno = ENOMEM; return NULL; } retval = _wfopen_s(&fp, widename, mode); free(widename); #else retval = fopen_s(&fp, pathname, mode); #endif /* UNICODE */ if (retval != 0) jc_errno = errno; #else fp = fopen(pathname, mode); if (fp == NULL) jc_errno = errno; #endif /* ON_WINDOWS */ return fp; } int jc_fclose(FILE *stream) { return fclose(stream); } libjodycode/generate_packages.sh000077500000000000000000000050221507704630200173370ustar00rootroot00000000000000#!/bin/bash # Generate package folders with variant builds # Number of parallel make processes test -z "$PM" && PM=12 NAME="libjodycode" VER="$(cat libjodycode.h | grep '#define\s\s*LIBJODYCODE_VER .*"' | cut -d\" -f2)" echo "Program version: $VER" TA=__NONE__ PKGTYPE=gz EXT1=.so; EXT2=.a UNAME_S="$(uname -s | tr '[:upper:]' '[:lower:]')" UNAME_P="$(uname -p)" UNAME_M="$(uname -m)" # Detect macOS if [ "$UNAME_S" = "darwin" ] then PKGTYPE=zip TA=mac32 test "$UNAME_M" = "x86_64" && TA=mac64 fi # Detect Power Macs under macOS if [[ "$UNAME_P" = "Power Macintosh" || "$UNAME_P" = "powerpc" ]] then PKGTYPE=zip TA=macppc32 test "$(sysctl hw.cpu64bit_capable)" = "hw.cpu64bit_capable: 1" && TA=macppc64 fi # Detect Linux if [ "$UNAME_S" = "linux" ] then TA="linux-$UNAME_M" [ ! -z "$ARCH" ] && TA="linux-$ARCH" PKGTYPE=xz fi # Fall through - assume Windows if [ "$TA" = "__NONE__" ] then PKGTYPE=zip TGT=$(gcc -v 2>&1 | grep Target | cut -d\ -f2- | cut -d- -f1) test "$TGT" = "i686" && TA=win32 test "$TGT" = "x86_64" && TA=win64 test "$UNAME_S" = "MINGW32_NT-5.1" && TA=winxp EXT1=".dll" fi echo "Target architecture: $TA" test "$TA" = "__NONE__" && echo "Failed to detect system type" && exit 1 PKGNAME="${NAME}-${VER}-$TA" echo "Generating package for: $PKGNAME" mkdir -p "$PKGNAME" test ! -d "$PKGNAME" && echo "Can't create directory for package" && exit 1 cp CHANGES.txt README.md LICENSE.txt libjodycode.h $PKGNAME/ E1=1 make clean && make -j$PM stripped && cp -R $NAME${EXT1}* $NAME$EXT2 $PKGNAME/ && E1=0 #make clean [ $E1 -gt 0 ] && echo "Error building packages; aborting." && exit 1 # Make a fat binary on macOS x86_64 if possible #if [ "$TA" = "mac64" ] && ld -v 2>&1 | grep -q 'archs:.*i386' # then # ERR=0 # TYPE=-i386; CE=-m32 # # On macOS Big Sur (Darwin 20) or higher, try to build a x86_64 + arm64 binary # [ $(uname -r | cut -d. -f1) -ge 20 ] && TYPE=-arm64 && CE="-target arm64-apple-macos11" # make clean && make -j$PM CFLAGS_EXTRA="$CE" stripped && cp $NAME$EXT1 $PKGNAME/$NAME$EXT$TYPE || ERR=1 # [ $ERR -eq 0 ] && lipo -create -output $PKGNAME/libjodycode_temp $PKGNAME/$NAME$EXT$TYPE $PKGNAME/$NAME$EXT && mv $PKGNAME/libjodycode_temp $PKGNAME/$NAME$EXT # make clean # test $ERR -gt 0 && echo "Error building packages; aborting." && exit 1 # rm -f $PKGNAME/$NAME$EXT$TYPE #fi test "$PKGTYPE" = "zip" && zip -9r $PKGNAME.zip $PKGNAME/ test "$PKGTYPE" = "gz" && tar -c $PKGNAME/ | gzip -9 > $PKGNAME.pkg.tar.gz test "$PKGTYPE" = "xz" && tar -c $PKGNAME/ | xz -e > $PKGNAME.pkg.tar.xz echo "Package generation complete." libjodycode/getcwd.c000066400000000000000000000022561507704630200147770ustar00rootroot00000000000000/* libjodycode: getcwd() stdio call * * Copyright (C) 2014-2025 by Jody Bruchon * Released under The MIT License */ #include #ifndef ON_WINDOWS #include #endif #include "likely_unlikely.h" #include "libjodycode.h" #ifdef UNICODE #define WIN32_LEAN_AND_MEAN #include #endif /* Check file exist/read/write, converting for Windows if necessary */ char *jc_getcwd(char * const restrict pathname, const size_t size) { char *retval; #ifdef UNICODE const size_t wsize = size * 2; JC_WCHAR_T *widename; int i; #endif if (unlikely(pathname == NULL)) { jc_errno = EFAULT; return NULL; } #ifdef ON_WINDOWS #ifdef UNICODE widename = (JC_WCHAR_T *)calloc(1, wsize); if (widename == NULL) { jc_errno = ENOMEM; return NULL; } retval = (char *)_wgetcwd(widename, (int)wsize); i = W2M_SIZED(widename, pathname, (int)size); free(widename); if (unlikely(i == 0)) { jc_errno = jc_GetLastError(); return NULL; } #else retval = _getcwd(pathname, (int)size); #endif /* UNICODE */ #else /* Not Windows */ retval = getcwd(pathname, size); #endif /* ON_WINDOWS */ if (retval == NULL) jc_errno = errno; return retval; } libjodycode/helper_code/000077500000000000000000000000001507704630200156225ustar00rootroot00000000000000libjodycode/helper_code/libjodycode_apiver.c000066400000000000000000000010501507704630200216170ustar00rootroot00000000000000/* Prints linked libjodycode API version/sub-version information * Useful for detecting the API that will be linked to by the compiler * Released into the public domain by Jody Bruchon #include "libjodycode.h" int main(void) { printf("Version: %s\n", LIBJODYCODE_VER); printf("Date: %s\n", LIBJODYCODE_VERDATE); printf("API version: %d\n", LIBJODYCODE_API_VERSION); printf("API feature level: %d\n", LIBJODYCODE_API_FEATURE_LEVEL); printf("Unicode: %d\n", LIBJODYCODE_WINDOWS_UNICODE); return 0; } libjodycode/helper_code/libjodycode_check.c000066400000000000000000000076201507704630200214170ustar00rootroot00000000000000/* libjodycode version checks * * Code to embed the libjodycode version info and check against the currently * linked libjodycode to check for and report incompatibilities * Can also be compiled with -DSTANDALONE for version checking purposes * * Copyright (C) 2023-2025 by Jody Bruchon * Licensed under The MIT License */ #include #include #include #include "libjodycode_check.h" /* Set your required feature level in libjodycode_check.h */ const int jc_build_min_featurelevel = MY_FEATURELEVEL_REQ; #ifndef LIBJODYCODE_API_VERSION #error Your libjodycode version cannot be detected or is ancient. #error Compilation will fail. Update libjodycode and try again. #endif #if LIBJODYCODE_API_VERSION < 3 #define LIBJODYCODE_API_FEATURE_LEVEL LIBJODYCODE_API_VERSION #define jc_api_featurelevel LIBJODYCODE_API_VERSION #endif /* libjodycode < 3 is missing some Unicode stuff */ #ifndef LIBJODYCODE_WINDOWS_UNICODE #if PATHBUF_SIZE == 8192 /* PATHBUF_SIZE can be used as a proxy for Windows Unicode */ #define LIBJODYCODE_WINDOWS_UNICODE 1 #define jc_windows_unicode 1 #elif PATHBUF_SIZE == 4096 #define LIBJODYCODE_WINDOWS_UNICODE 0 #define jc_windows_unicode 0 #endif #endif const char *jc_build_version = LIBJODYCODE_VER; const int jc_build_api_version = LIBJODYCODE_API_VERSION; const int jc_build_api_featurelevel = LIBJODYCODE_API_FEATURE_LEVEL; #ifdef LIBJODYCODE_WINDOWS_UNICODE const int jc_build_windows_unicode = LIBJODYCODE_WINDOWS_UNICODE; #else #define jc_build_windows_unicode 1 #endif #ifdef JC_TEST #define JC_TEST_ONLY(a) a #else #define JC_TEST_ONLY(a) #endif int libjodycode_version_check(int verbose, int bail) { JC_TEST_ONLY(if (verbose > 1) fprintf(stderr, "libjodycode version check test code\n\n");) JC_TEST_ONLY(if (verbose > 1) goto incompatible_version;) if (jc_build_api_version != jc_api_version) goto incompatible_version; if (jc_build_min_featurelevel > jc_api_featurelevel) goto incompatible_version; #ifdef LIBJODYCODE_WINDOWS_UNICODE if (jc_build_windows_unicode != jc_windows_unicode) goto incompatible_version; #endif return 0; incompatible_version: if (verbose) { fprintf(stderr, "\n==============================================================================\n"); fprintf(stderr, "internal error: libjodycode on this system is an incompatible version\n\n"); fprintf(stderr, "Currently using libjodycode v%s, API %d, feature level %d\n", jc_version, jc_api_version, jc_api_featurelevel); fprintf(stderr, " Built against libjodycode v%s, API %d, feature level %d\n\n", jc_build_version, jc_build_api_version, jc_build_api_featurelevel); if (jc_windows_unicode != jc_build_windows_unicode) fprintf(stderr, "libjodycode was built with%s Windows Unicode but %sUnicode is required.\n\n", jc_windows_unicode == 1 ? "" : "out", jc_build_windows_unicode == 1 ? "" : "non-"); if (jc_build_min_featurelevel > jc_build_api_featurelevel) fprintf(stderr, "libjodycode feature level >= %d is required but linked library is level %d\n\n", jc_build_min_featurelevel, jc_build_api_featurelevel); fprintf(stderr, "==============================================================================\n\n"); fprintf(stderr, "\nUpdate libjodycode on your system and try again. If you continue to get this\n"); fprintf(stderr, "error, contact the package or distribution maintainer. If all else fails, send\n"); fprintf(stderr, "an email to jody@jodybruchon.com for help (but only as a last resort, please.)\n\n"); } if (bail) exit(EXIT_FAILURE); return 1; } #if defined (JC_TEST) || defined (STANDALONE) #ifdef UNICODE int wmain(void) #else int main(void) #endif { #ifdef JC_TEST libjodycode_version_check(2, 0); #else printf("libjodycode ver,api,featurelevel,reqlevel:%s:%d:%d:%d\n", jc_build_version, jc_build_api_version, jc_build_api_featurelevel, jc_build_min_featurelevel); #endif return 0; } #endif libjodycode/helper_code/libjodycode_check.h000066400000000000000000000011571507704630200214230ustar00rootroot00000000000000/* libjodycode version check headear * See libjodycode_check.c for license information */ #ifndef LIBJODYCODE_CHECK_H #define LIBJODYCODE_CHECK_H #ifdef __cplusplus extern "C" { #endif /* Set this to the minimum feature level required by your program */ #define MY_FEATURELEVEL_REQ 4 extern const int jc_build_api_major; extern const int jc_build_api_minor; extern const char *jc_build_version; extern const char *jc_build_featurelevel; extern const unsigned char jc_build_api_versiontable[]; extern int libjodycode_version_check(int verbose, int bail); #ifdef __cplusplus } #endif #endif /* LIBJODYCODE_CHECK_H */ libjodycode/helper_code/makefile_nearby.txt000066400000000000000000000025061507704630200215030ustar00rootroot00000000000000For easy integration of libjodycode into your build process you can use this code stub to link against a built copy in ../libjodycode; Modify as necessary. COMPILER_OPTIONS should be added to CFLAGS for each object. LINK_OPTIONS should be added to LDFLAGS for the final link. Add the static_jc and dynamic_jc rules to be able to build with libjodycode as a static inclusion or to link to it dynamically. Add the libjodycode_hint rule and add it to 'all' to tell the user about the static libjodycode make option. ### Find and use nearby libjodycode by default ifndef IGNORE_NEARBY_JC ifneq ("$(wildcard ../libjodycode/libjodycode.h)","") $(info Found and using nearby libjodycode at ../libjodycode) COMPILER_OPTIONS += -I../libjodycode -L../libjodycode ifeq ("$(wildcard ../libjodycode/version.o)","") $(error You must build libjodycode before building winregfs) endif endif ifdef FORCE_JC_DLL LINK_OPTIONS += -l:../libjodycode/libjodycode.dll else LINK_OPTIONS += -ljodycode endif endif dynamic_jc: $(PROGRAM_NAME) $(CC) $(CFLAGS) $(OBJS) -Wl,-Bdynamic $(LDFLAGS) -o $(PROGRAM_NAME)$(SUFFIX) static_jc: $(PROGRAM_NAME) $(CC) $(CFLAGS) $(OBJS) -Wl,-Bstatic $(LDFLAGS) -Wl,-Bdynamic -o $(PROGRAM_NAME)$(SUFFIX) libjodycode_hint: $(info hint: if ../libjodycode is built but jdupes won't run, try doing 'make static_jc') libjodycode/jc_fwprint.c000066400000000000000000000044701507704630200156670ustar00rootroot00000000000000/* libjodycode: easy wide single-string fprintf() * * Copyright (C) 2014-2025 by Jody Bruchon * Released under The MIT License */ #include #include #include #include #include #include "likely_unlikely.h" #include "libjodycode.h" #ifdef ON_WINDOWS #define WIN32_LEAN_AND_MEAN #include #include static int jc_out_mode = _O_TEXT; static int jc_err_mode = _O_TEXT; #endif /* ON_WINDOWS */ #ifdef ON_WINDOWS /* Set jc_fwprint output modes for stdout/stderr to TEXT, BINARY, or UTF-16 * 0: no change * 1: set to text mode * 2: set to binary (UTF-8) mode * 3: set to UTF-16 mode * 4: set to UTF-16 mode for terminals, otherwise set to binary mode */ void jc_set_output_modes(const int out, const int err) { switch (out) { default: case 0: break; case 1: jc_out_mode = _O_TEXT; break; case 2: jc_out_mode = _O_BINARY; break; case 3: jc_out_mode = _O_U16TEXT; break; case 4: if (!_isatty(_fileno(stdout))) jc_out_mode = _O_BINARY; else jc_out_mode = _O_U16TEXT; break; } switch (err) { default: case 0: break; case 1: jc_err_mode = _O_TEXT; break; case 2: jc_err_mode = _O_BINARY; break; case 3: jc_err_mode = _O_U16TEXT; break; case 4: if (!_isatty(_fileno(stderr))) jc_err_mode = _O_BINARY; else jc_err_mode = _O_U16TEXT; break; } return; } #endif /* ON_WINDOWS */ /* Print a string that is wide on Windows but normal on POSIX */ int jc_fwprint(FILE * const restrict stream, const char * const restrict str, const int cr) { #ifdef UNICODE int retval; int stream_mode; JC_WCHAR_T *wstr; stream_mode = (stream == stderr) ? jc_err_mode : jc_out_mode; if (stream_mode == _O_U16TEXT) { /* Convert to wide string and send to wide console output */ if (jc_string_to_wstring(str, &wstr) != 0) return JC_EALLOC; fflush(stream); _setmode(_fileno(stream), stream_mode); if (cr == 2) retval = fwprintf(stream, L"%ls%C", wstr, 0); else retval = fwprintf(stream, L"%ls%ls", wstr, cr == 1 ? L"\n" : L""); fflush(stream); _setmode(_fileno(stream), _O_TEXT); free(wstr); return retval; } else { #endif if (cr == 2) return fprintf(stream, "%s%c", str, 0); else return fprintf(stream, "%s%s", str, cr == 1 ? "\n" : ""); #ifdef UNICODE } #endif } libjodycode/jody_hash.c000066400000000000000000000073351507704630200154750ustar00rootroot00000000000000/* Jody Bruchon's fast hashing function * * This function was written to generate a fast hash that also has a * fairly low collision rate. The collision rate is much higher than * a secure hash algorithm, but the calculation is drastically simpler * and faster. * * Copyright (C) 2014-2025 by Jody Bruchon * Released under The MIT License */ #include #include #include #include #include "jody_hash.h" #include "jody_hash_simd.h" #include "likely_unlikely.h" /* Rolling hash block size (4K by default) */ #ifndef ROLLBSIZE #define ROLLBSIZE 4096 #endif #define ROLLBSIZEW (ROLLBSIZE / sizeof(jodyhash_t)) static const jodyhash_t jh_s_constant = JH_ROR2(JODY_HASH_CONSTANT); /* Hash a block of arbitrary size; must be divisible by sizeof(jodyhash_t) * The first block should pass an initial hash of zero. * All blocks after the first should pass hash as the value * returned by the last call to this function. This allows hashing * of any amount of data. If data is not divisible by the size of * jodyhash_t, it is MANDATORY that the caller provide a data buffer * which is divisible by sizeof(jodyhash_t). */ extern int jody_block_hash(jodyhash_t *data, jodyhash_t *hash, const size_t count) { jodyhash_t element, element2; size_t length = 0; /* Don't bother trying to hash a zero-length block */ if (unlikely(count == 0)) return 0; #ifndef NO_AVX2 #if defined __GNUC__ || defined __clang__ __builtin_cpu_init (); if (__builtin_cpu_supports ("avx2")) { #endif /* __GNUC__ || __clang__ */ if (count >= 32) { if (jody_block_hash_avx2(&data, hash, count, &length) != 0) return 1; goto skip_sse2; } else length = count / sizeof(jodyhash_t); #if defined __GNUC__ || defined __clang__ } else length = count / sizeof(jodyhash_t); #endif #else length = count / sizeof(jodyhash_t); #endif /* NO_AVX2 */ #ifndef NO_SSE2 #if defined __GNUC__ || defined __clang__ __builtin_cpu_init (); if (__builtin_cpu_supports ("sse2")) { #endif /* __GNUC__ || __clang__ */ if (count >= 32) { if (jody_block_hash_sse2(&data, hash, count, &length) != 0) return 1; } else length = count / sizeof(jodyhash_t); #if defined __GNUC__ || defined __clang__ } else length = count / sizeof(jodyhash_t); #endif #else length = count / sizeof(jodyhash_t); #endif /* NO_SSE2 */ #ifndef NO_AVX2 skip_sse2: #endif /* Hash everything (normal) or remaining small tails (SSE2) */ for (; length > 0; length--) { element = *data; element2 = JH_ROR(element); element2 ^= jh_s_constant; element += JODY_HASH_CONSTANT; *hash += element; *hash ^= element2; *hash = JH_ROL2(*hash); *hash += element; data++; } /* Handle data tail (for blocks indivisible by sizeof(jodyhash_t)) */ length = count & (sizeof(jodyhash_t) - 1); if (length) { element = *data & tail_mask[length]; element2 = JH_ROR(element); element2 ^= jh_s_constant; element += JODY_HASH_CONSTANT; *hash += element; *hash ^= element2; *hash = JH_ROL2(*hash); *hash += element2; } return 0; } extern int jody_rolling_block_hash(jodyhash_t *data, jodyhash_t *hash, const size_t count) { jodyhash_t rollhash; size_t blocks = (count & ~((uint64_t)ROLLBSIZE - 1)) / ROLLBSIZE; for (unsigned int i = 0; i < blocks; i++) { //fprintf(stderr, "rolling([%u] %p, %016" PRIx64 ", %d)\n", i, (void *)data, *hash, ROLLBSIZE); rollhash = 0; if (jody_block_hash(data, &rollhash, ROLLBSIZE)) return 1; *hash ^= rollhash; data += ROLLBSIZEW; } /* Hash the last block */ blocks = count - (blocks * ROLLBSIZE); if (blocks > 0) { //fprintf(stderr, "rolltail(%p, %016" PRIx64 ", %d)\n", (void *)data, *hash, ROLLBSIZE); rollhash = 0; if (jody_block_hash(data, &rollhash, blocks)) return 1; *hash ^= rollhash; } return 0; } libjodycode/jody_hash.h000066400000000000000000000063001507704630200154710ustar00rootroot00000000000000/* Jody Bruchon's fast hashing function (headers) * See jody_hash.c for license information */ #ifndef JODY_HASH_H #define JODY_HASH_H #ifdef __cplusplus extern "C" { #endif /* Required for uint64_t */ #include /* Width of a jody_hash. Changing this will also require * changing the width of tail masks to match. */ #ifndef JODY_HASH_WIDTH #define JODY_HASH_WIDTH 64 #endif /* Version increments when algorithm changes incompatibly */ #define JODY_HASH_VERSION 7 /* DO NOT modify shifts/contants unless you know what you're doing. They were * chosen after lots of testing. Changes will likely cause lots of hash * collisions. The vectorized versions also use constants that have this value * "baked in" which must be updated before using them. */ #ifndef JODY_HASH_SHIFT #define JODY_HASH_SHIFT 14 #endif /* The constant value's purpose is to cause each byte in the * jodyhash_t word to have a positionally dependent variation. * It is injected into the calculation to prevent a string of * identical bytes from easily producing an identical hash. */ /* The tail mask table is used for block sizes that are * indivisible by the width of a jodyhash_t. It is ANDed with the * final jodyhash_t-sized element to zero out data in the buffer * that is not part of the data to be hashed. */ /* Set hash parameters based on requested hash width */ #if JODY_HASH_WIDTH == 64 typedef uint64_t jodyhash_t; #ifndef JODY_HASH_CONSTANT #define JODY_HASH_CONSTANT 0x71812e0f5463d3c8ULL #endif static const jodyhash_t tail_mask[] = { 0x0000000000000000, 0x00000000000000ff, 0x000000000000ffff, 0x0000000000ffffff, 0x00000000ffffffff, 0x000000ffffffffff, 0x0000ffffffffffff, 0x00ffffffffffffff, 0xffffffffffffffff }; #endif /* JODY_HASH_WIDTH == 64 */ #if JODY_HASH_WIDTH == 32 typedef uint32_t jodyhash_t; #ifndef JODY_HASH_CONSTANT #define JODY_HASH_CONSTANT 0x8748ee5dU #endif static const jodyhash_t tail_mask[] = { 0x00000000, 0x000000ff, 0x0000ffff, 0x00ffffff, 0xffffffff }; #endif /* JODY_HASH_WIDTH == 32 */ #if JODY_HASH_WIDTH == 16 typedef uint16_t jodyhash_t; #ifndef JODY_HASH_CONSTANT #define JODY_HASH_CONSTANT 0x1f5bU #endif static const jodyhash_t tail_mask[] = { 0x0000, 0x00ff, 0xffff }; #endif /* JODY_HASH_WIDTH == 16 */ /* Double-length shift for double-rotation optimization */ #define JH_SHIFT2 ((JODY_HASH_SHIFT * 2) - (((JODY_HASH_SHIFT * 2) > JODY_HASH_WIDTH) * JODY_HASH_WIDTH)) #define JODY_HASH_CONSTANT_ROR2 (JODY_HASH_CONSTANT >> JH_SHIFT2 | (JODY_HASH_CONSTANT << ((sizeof(jodyhash_t) * 8) - JH_SHIFT2))) /* Macros for bitwise rotation */ #define JH_ROL(a) (jodyhash_t)((a << JODY_HASH_SHIFT) | (a >> ((sizeof(jodyhash_t) * 8) - JODY_HASH_SHIFT))) #define JH_ROR(a) (jodyhash_t)((a >> JODY_HASH_SHIFT) | (a << ((sizeof(jodyhash_t) * 8) - JODY_HASH_SHIFT))) #define JH_ROL2(a) (jodyhash_t)(a << JH_SHIFT2 | (a >> ((sizeof(jodyhash_t) * 8) - JH_SHIFT2))) #define JH_ROR2(a) (jodyhash_t)(a >> JH_SHIFT2 | (a << ((sizeof(jodyhash_t) * 8) - JH_SHIFT2))) extern int jody_block_hash(jodyhash_t *data, jodyhash_t *hash, const size_t count); extern int jody_rolling_block_hash(jodyhash_t *data, jodyhash_t *hash, const size_t count); #ifdef __cplusplus } #endif #endif /* JODY_HASH_H */ libjodycode/jody_hash_avx2.c000066400000000000000000000054121507704630200164270ustar00rootroot00000000000000/* Jody Bruchon's fast hashing function * * This function was written to generate a fast hash that also has a * fairly low collision rate. The collision rate is much higher than * a secure hash algorithm, but the calculation is drastically simpler * and faster. * * Copyright (C) 2014-2025 by Jody Bruchon * Released under The MIT License */ #include #include #include #include "jody_hash.h" #include "jody_hash_simd.h" #ifndef NO_AVX2 int jody_block_hash_avx2(jodyhash_t **data, jodyhash_t *hash, const size_t count, size_t *length) { size_t vec_allocsize; __m256i *aligned_data; /* Regs used in groups of 3; 1=ROR/XOR work, 2=temp, 3=data+constant */ __m256i vx1, vx2, vx3; __m256i avx_const, avx_ror2; jodyhash_t qhash = *hash; /* Constants preload */ avx_const = _mm256_load_si256(&vec_constant.v256); avx_ror2 = _mm256_load_si256(&vec_constant_ror2.v256); /* How much memory do we need to align the data? */ vec_allocsize = count & 0xffffffffffffffe0U; /* Only alloc/copy if not already aligned */ if (((uintptr_t)*data & (uintptr_t)0x1fULL) != 0) { aligned_data = (__m256i *)aligned_alloc(32, vec_allocsize); if (!aligned_data) return 1; memcpy(aligned_data, *data, vec_allocsize); } else aligned_data = (__m256i *)*data; for (size_t i = 0; i < (vec_allocsize / 32); i++) { vx1 = _mm256_load_si256(&aligned_data[i]); vx3 = _mm256_load_si256(&aligned_data[i]); /* "element2" gets RORed (two logical shifts ORed together) */ vx1 = _mm256_srli_epi64(vx1, JODY_HASH_SHIFT); vx2 = _mm256_slli_epi64(vx3, (64 - JODY_HASH_SHIFT)); vx1 = _mm256_or_si256(vx1, vx2); vx1 = _mm256_xor_si256(vx1, avx_ror2); // XOR against the ROR2 constant /* Add the constant to "element" */ vx3 = _mm256_add_epi64(vx3, avx_const); /* Perform the rest of the hash */ for (int j = 0; j < 4; j++) { uint64_t ep1, ep2; switch (j) { default: case 0: ep1 = (uint64_t)_mm256_extract_epi64(vx3, 0); ep2 = (uint64_t)_mm256_extract_epi64(vx1, 0); break; case 1: ep1 = (uint64_t)_mm256_extract_epi64(vx3, 1); ep2 = (uint64_t)_mm256_extract_epi64(vx1, 1); break; case 2: ep1 = (uint64_t)_mm256_extract_epi64(vx3, 2); ep2 = (uint64_t)_mm256_extract_epi64(vx1, 2); break; case 3: ep1 = (uint64_t)_mm256_extract_epi64(vx3, 3); ep2 = (uint64_t)_mm256_extract_epi64(vx1, 3); break; } qhash += ep1; qhash ^= ep2; qhash = JH_ROL2(qhash); qhash += ep1; } // End of hash finish loop } // End of main AVX for loop *data += vec_allocsize / sizeof(jodyhash_t); if (((uintptr_t)*data & (uintptr_t)0x1fULL) != 0) ALIGNED_FREE(aligned_data); *length = (count - vec_allocsize) / sizeof(jodyhash_t); *hash = qhash; return 0; } #endif /* NO_AVX2 */ libjodycode/jody_hash_simd.c000066400000000000000000000015201507704630200164770ustar00rootroot00000000000000/* Jody Bruchon's fast hashing function * * This function was written to generate a fast hash that also has a * fairly low collision rate. The collision rate is much higher than * a secure hash algorithm, but the calculation is drastically simpler * and faster. * * Copyright (C) 2014-2025 by Jody Bruchon * Released under The MIT License */ #include #include "jody_hash.h" #include "jody_hash_simd.h" #if (!defined NO_SSE2 || !defined NO_AVX2) const union UINT256 vec_constant = { .v64[0] = JODY_HASH_CONSTANT, .v64[1] = JODY_HASH_CONSTANT, .v64[2] = JODY_HASH_CONSTANT, .v64[3] = JODY_HASH_CONSTANT }; const union UINT256 vec_constant_ror2 = { .v64[0] = JODY_HASH_CONSTANT_ROR2, .v64[1] = JODY_HASH_CONSTANT_ROR2, .v64[2] = JODY_HASH_CONSTANT_ROR2, .v64[3] = JODY_HASH_CONSTANT_ROR2 }; #endif libjodycode/jody_hash_simd.h000066400000000000000000000032771507704630200165170ustar00rootroot00000000000000/* Jody Bruchon's fast hashing function (headers) * See jody_hash.c for license information */ #ifndef JODY_HASH_SIMD_H #define JODY_HASH_SIMD_H #ifdef __cplusplus extern "C" { #endif #include "jody_hash.h" /* Disable SIMD if not 64-bit width or not 64-bit x86 code */ #if JODY_HASH_WIDTH != 64 || !defined __x86_64__ || SIZE_MAX == 0xffffffff || (defined NO_SSE2 && defined NO_AVX2) #ifndef NO_SSE2 #define NO_SSE2 #endif #ifndef NO_AVX2 #define NO_AVX2 #endif #ifndef NO_SIMD #define NO_SIMD #endif #endif /* Use SIMD by default */ #if !defined NO_SIMD #if (__STDC_VERSION__ < 201112L) #include "c99_aligned_alloc.h" #endif #if defined __APPLE__ /* Mac OS X < 10.15 don't support aligned_alloc */ #include #define aligned_alloc(a,b) malloc(b) #define ALIGNED_FREE(a) free(a) #elif defined _MSC_VER || defined _WIN32 || defined __MINGW32__ /* Microsoft C/C++-compatible compiler */ #include #define aligned_alloc(a,b) _aligned_malloc(b,a) #define ALIGNED_FREE(a) _aligned_free(a) #elif (defined __GNUC__ || defined __clang__ ) && (defined __x86_64__ || defined __i386__ ) /* GCC or Clang targeting x86/x86-64 */ #include #define ALIGNED_FREE(a) free(a) #endif #endif /* !NO_SIMD */ #if !defined NO_SSE2 || !defined NO_AVX2 union UINT256 { __m256i v256; __m128i v128[2]; uint64_t v64[4]; }; extern const union UINT256 vec_constant, vec_constant_ror2; #endif extern int jody_block_hash_avx2(jodyhash_t **data, jodyhash_t *hash, const size_t count, size_t *length); extern int jody_block_hash_sse2(jodyhash_t **data, jodyhash_t *hash, const size_t count, size_t *length); #ifdef __cplusplus } #endif #endif /* JODY_HASH_SIMD_H */ libjodycode/jody_hash_sse2.c000066400000000000000000000071451507704630200164300ustar00rootroot00000000000000/* Jody Bruchon's fast hashing function * * This function was written to generate a fast hash that also has a * fairly low collision rate. The collision rate is much higher than * a secure hash algorithm, but the calculation is drastically simpler * and faster. * * Copyright (C) 2014-2025 by Jody Bruchon * Released under The MIT License */ #include #include #include #include "jody_hash.h" #include "jody_hash_simd.h" #ifndef NO_SSE2 int jody_block_hash_sse2(jodyhash_t **data, jodyhash_t *hash, const size_t count, size_t *length) { size_t vec_allocsize; __m128i *aligned_data; __m128i v1, v2, v3, v4, v5, v6; __m128 vzero; __m128i vec_const, vec_ror2; jodyhash_t qhash = *hash; #if defined __GNUC__ || defined __clang__ __builtin_cpu_init (); if (__builtin_cpu_supports ("avx")) { asm volatile ("vzeroall" : : : "ymm0", "ymm1", "ymm2", "ymm3", "ymm4", "ymm5", "ymm6", "ymm7", "ymm8", "ymm9", "ymm10", "ymm11", "ymm12", "ymm13", "ymm14", "ymm15"); } #endif /* __GNUC__ || __clang__ */ /* Constants preload */ vec_const = _mm_load_si128(&vec_constant.v128[0]); vec_ror2 = _mm_load_si128(&vec_constant_ror2.v128[0]); vzero = _mm_setzero_ps(); /* How much memory do we need to align the data? */ vec_allocsize = count & 0xffffffffffffffe0U; /* Only alloc/copy if not already aligned */ if (((uintptr_t)*data & (uintptr_t)0x0fULL) != 0) { aligned_data = (__m128i *)aligned_alloc(16, vec_allocsize); if (!aligned_data) return 1; memcpy(aligned_data, *data, vec_allocsize); } else aligned_data = (__m128i *)*data; for (size_t i = 0; i < (vec_allocsize / 16); i++) { v1 = _mm_load_si128(&aligned_data[i]); v3 = _mm_load_si128(&aligned_data[i]); i++; v4 = _mm_load_si128(&aligned_data[i]); v6 = _mm_load_si128(&aligned_data[i]); /* "element2" gets RORed (two logical shifts ORed together) */ v1 = _mm_srli_epi64(v1, JODY_HASH_SHIFT); v2 = _mm_slli_epi64(v3, (64 - JODY_HASH_SHIFT)); v1 = _mm_or_si128(v1, v2); v1 = _mm_xor_si128(v1, vec_ror2); // XOR against the ROR2 constant v4 = _mm_srli_epi64(v4, JODY_HASH_SHIFT); v5 = _mm_slli_epi64(v6, (64 - JODY_HASH_SHIFT)); v4 = _mm_or_si128(v4, v5); v4 = _mm_xor_si128(v4, vec_ror2); // XOR against the ROR2 constant /* Add the constant to "element" */ v3 = _mm_add_epi64(v3, vec_const); v6 = _mm_add_epi64(v6, vec_const); /* Perform the rest of the hash */ for (int j = 0; j < 4; j++) { uint64_t ep1, ep2; switch (j) { default: case 0: /* Lower v1-v3 */ ep1 = (uint64_t)_mm_cvtsi128_si64(v3); ep2 = (uint64_t)_mm_cvtsi128_si64(v1); break; case 1: /* Upper v1-v3 */ ep1 = (uint64_t)_mm_cvtsi128_si64(_mm_castps_si128(_mm_movehl_ps(vzero, _mm_castsi128_ps(v3)))); ep2 = (uint64_t)_mm_cvtsi128_si64(_mm_castps_si128(_mm_movehl_ps(vzero, _mm_castsi128_ps(v1)))); break; case 2: /* Lower v4-v6 */ ep1 = (uint64_t)_mm_cvtsi128_si64(v6); ep2 = (uint64_t)_mm_cvtsi128_si64(v4); break; case 3: /* Upper v4-v6 */ ep1 = (uint64_t)_mm_cvtsi128_si64(_mm_castps_si128(_mm_movehl_ps(vzero, _mm_castsi128_ps(v6)))); ep2 = (uint64_t)_mm_cvtsi128_si64(_mm_castps_si128(_mm_movehl_ps(vzero, _mm_castsi128_ps(v4)))); break; } qhash += ep1; qhash ^= ep2; qhash = JH_ROL2(qhash); qhash += ep1; } // End of hash finish loop } // End of main SSE for loop *data += vec_allocsize / sizeof(jodyhash_t); if (((uintptr_t)*data & (uintptr_t)0x0fULL) != 0) ALIGNED_FREE(aligned_data); *length = (count - vec_allocsize) / sizeof(jodyhash_t); *hash = qhash; return 0; } #endif /* NO_SSE2 */ libjodycode/libjodycode.3000066400000000000000000000221621507704630200157270ustar00rootroot00000000000000.\" Copyright (C) 2023-2025 by Jody Bruchon .TH "LIBJODYCODE" "3" "2025-10-14" "4.1" "libjodycode" .SH NAME libjodycode \- shared code used by several tools written by Jody Bruchon .SH SYNOPSIS .B #include .SH DESCRIPTION libjodycode is a software code library containing code shared among several of the programs written by Jody Bruchon such as imagepile, jdupes, winregfs, and zeromerge. These shared pieces of code were copied between each program as they were updated. As the number of programs increased and keeping these pieces of code synced became more annoying, the decision was made to combine all of them into a single reusable shared library. .SH APPLICATION USAGE .SS "Standard C library swap-in calls" .nf .BI "int jc_access(const char *" pathname ", int " mode ")" .BI "int jc_closedir(JC_DIR * const restrict " dirp ")" .BI "int jc_fclose(FILE *" stream ")" .BI "FILE *jc_fopen(const char *" pathname ", const JC_WCHAR_T *" mode ")" .BI "char *jc_getcwd(char * const restrict " pathname ", const size_t " size ")" .BI "int jc_link(const char *" path1 ", const char *" path2 ");" .BI "JC_DIR *jc_opendir(const char * restrict " path ")" .BI "struct JC_DIRENT *jc_readdir(JC_DIR * const restrict " dirp ")" .BI "int jc_rename(const char *" oldpath ", const char *" newpath ")" .BI "int jc_remove(const char *" pathname ")" .BI "int jc_stat(const char * const " filename ", struct JC_STAT * const restrict " buf ")" .SS "Alarm API" .nf .BI "int jc_start_alarm(const unsigned int " seconds ", const int " repeat ")" .BI "int jc_stop_alarm(void)" .IP int\ jc_alarm_ring Number of alarm triggers that have occurred; writable by the program .SS "Batch file list API" .nf .BI "struct jc_fileinfo_batch *jc_fileinfo_batch_alloc(const int " filecnt ", const int " stat ", const int " namlen ")" .BI "void jc_fileinfo_batch_free(struct jc_fileinfo_batch * const restrict " batch ")" .SS "Cacheinfo API" .nf .BI "struct jc_proc_cacheinfo *jc_get_proc_cacheinfo(int " cleanup ")" .SS "Directory API" .nf .BI "size_t jc_get_d_namlen(const struct JC_DIRENT * const restrict " dirent ")" .SS "Error API" .nf .BI "const char *jc_get_errname(int " errnum ")" .BI "const char *jc_get_errdesc(int " errnum ")" .BI "int jc_print_error(int " errnum ")" .IP int32_t\ jc_errno libjodycode equivalent to C library's errno .SS "jodyhash API" .nf .BI "int jc_block_hash(const enum jc_e_hash " type ", jodyhash_t *" data ", jodyhash_t *" hash ", const size_t " count ")" .IP enum\ jc_e_hash Selects hash type to perform, NORMAL or ROLLING .IP "#define JODY_HASH_VERSION" Version of \fIjody_hash\fP the library currently uses .SS "Linkfiles API" .nf .BI "int jc_linkfiles(struct jc_fileinfo_batch * const restrict " batch ", const enum jc_e_link " linktype ")" .IP enum\ jc_e_link Selects type of link to perform: SYMLINK, HARDLINK, or REFLINK .SS "OOM (out-of-memory) API" .nf .BI "void jc_oom(const char * restrict " msg ")" .BI "void jc_nullptr(const char * restrict " func ")" .SS "Path manipulation API" .nf .BI "int jc_collapse_dotdot(char * const " path ")" .BI "int jc_make_relative_link_name(const char * const " src ", const char * const " dest ", char *" rel_path ")" .SS "Size Suffix API" .nf .BI "const struct jc_size_suffix jc_size_suffix[]" .SS "Numerically correct string comparison API" .nf .BI "int jc_numeric_strcmp(char * restrict " c1 ", char * restrict " c2 ", int " insensitive ")" .SS "String API" .nf .BI "int jc_fwprint(FILE * const restrict " stream ", const char * const restrict " str ", const int " cr ")" .BI "int jc_strncaseeq(const char *" s1 ", const char *" s2 ", const size_t " len ")" .BI "int jc_strcaseeq(const char *" s1 ", const char *" s2 ")" .BI "int jc_strneq(const char *" s1 ", const char *" s2 ", const size_t " len ")" .BI "int jc_streq(const char *" s1 ", const char *" s2 ")" .BI "JC_STR_T *jc_str_init(const char *" string ", uint32_t " len ")" .BI "int jc_strteq(const JC_STR_T *" s1 ", const JC_STR_T *" s2 ")" .BI "int jc_strtneq(const JC_STR_T *" s1 ", const JC_STR_T *" s2 ", const size_t " len ")" .BI "int jc_strtcaseeq(const JC_STR_T *" s1 ", const JC_STR_T *" s2 ")" .BI "int jc_strtncaseeq(const JC_STR_T *" s1 ", const JC_STR_T *" s2 ", const size_t " len ")" .BI "int jc_strtcmp(const JC_STR_T *" s1 ", const JC_STR_T * " s2 " ")" .BI "int jc_strtncmp(const JC_STR_T *" s1 ", const JC_STR_T * " s2 ", size_t " len " ")" .BI "int jc_strtcasecmp(const JC_STR_T *" s1 ", const JC_STR_T *" s2 " ")" .BI "int jc_strtncasecmp(const JC_STR_T *" s1 ", const JC_STR_T *" s2 ", size_t " len ")" .IP JC_STR_T Structure for a string with its length prefixed as a 32-bit unsigned integer Members: len = length, str = string .SS "Time API" .nf .BI "time_t jc_strtoepoch(const char * const " datetime ")" .BI "int jc_nttime_to_unixtime(const FILETIME * const restrict " filetime ", struct JC_TIMESPEC * const restrict " unixtime ")" .BI "int jc_unixtime_to_nttime(const struct JC_TIMESPEC * const restrict " unixtime ", FILETIME * const restrict " filetime ")" .IP "struct JC_TIMESPEC" libjodycode equivalent to \fIstruct timespec\fP from \fItime.h\fP .SS "Version API" .nf .BI "const char *jc_version" .BI "const char *jc_verdate" .BI "const int jc_api_version" .BI "const int jc_api_featurelevel" .BI "const int jc_jodyhash_version" .BI "const unsigned char jc_api_versiontable[]" .BI "int jc_get_kernel_version(" void ")" (Linux only) get current Linux kernel version as an integer .SS "Windows-specific stat() mode test definitions" .IP S_ISARCHIVE(st_mode) 22 is Windows archive attribute set? .IP S_ISRO(st_mode) 22 is Windows read-only attribute set? .IP S_ISHIDDEN(st_mode) 22 is Windows hidden attribute set? .IP S_ISSYSTEM(st_mode) 22 is Windows system attribute set? .IP S_ISCRYPT(st_mode) 22 is it a Windows encrypted file/dir? .IP S_ISDIR(st_mode) 22 is it a directory? .IP S_ISCOMPR(st_mode) 22 is it a Windows compressed file? .IP S_ISREPARSE(st_mode) 22 is it a Windows reparse point? .IP S_ISSPARSE(st_mode) 22 is it a Windows sparse file? .IP S_ISTEMP(st_mode) 22 is it a Windows temporary file? .IP S_ISREG(st_mode) 22 is it a regular file? .SS "Windows Unicode API" .nf .BI "int jc_ffd_to_dirent(JC_DIR **" dirp ", HANDLE " hFind ", WIN32_FIND_DATA *" ffd ")" .BI "void jc_set_output_modes(const int " out ", const int " err ")" .BI "int jc_setup_unicode_terminal(int " argc ", JC_WCHAR_T **" wargv ", char ***" argv ", int * " stdout_tty ")" .BI "void jc_slash_convert(char *" path ")" .BI "int jc_string_to_wstring(const char * const restrict " string ", JC_WCHAR_T **" wstring ")" .BI "int jc_widearg_to_argv(int " argc ", JC_WCHAR_T **" wargv ", char **" argv ")" .SS "Windows Unicode wide equivalence definitions" .nf .IP JC_WCHAR_T 31 wchar_t for Windows Unicode, char otherwise .IP JC_FILE_MODE_RDONLY 31 string "rb" .IP JC_FILE_MODE_WRONLY 31 string "wb" .IP JC_FILE_MODE_RW 31 string "w+b" .IP JC_FILE_MODE_RW_EXISTING 31 string "r+b" .IP JC_FILE_MODE_WRONLY_APPEND 31 string "ab" .IP JC_FILE_MODE_RW_APPEND 31 string "a+b" .IP JC_FILE_MODE_RDONLY_SEQ 31 string "rbS" (Windows) or "rb" .IP JC_FILE_MODE_WRONLY_SEQ 31 string "wbS" (Windows) or "wb" .IP JC_FILE_MODE_RW_SEQ 31 string "w+bS" (Windows) or "w+b" .IP JC_FILE_MODE_RW_EXISTING_SEQ 31 string "r+bS" (Windows) or "r+b" .IP JC_FILE_MODE_WRONLY_APPEND_SEQ 31 string "abS" (Windows) or "ab" .IP JC_FILE_MODE_RW_APPEND_SEQ 31 string "a+bS" (Windows) or "a+b" .IP JC_F_OK 10 unistd.h F_OK .IP JC_R_OK 10 unistd.h R_OK .IP JC_W_OK 10 unistd.h W_OK .IP JC_X_OK 10 unistd.h X_OK .SS "Windows Unicode conversion shortcut definitions" .nf .BI "M2W(char *" a ", JC_WCHAR_T *" b ")" convert string a to wide string b using MultiByteToWideChar() .BI "W2M(JC_WCHAR_T *" a ", char *" b ")" convert wide string a to normal string b using WideCharToMultiByte() .SH NOTES libjodycode is created and maintained by Jody Bruchon General information, help, and tech info: http://www.jdupes.com/ Development, source code, releases: https://codeberg.org/jbruchon/libjodycode If you find this software useful, please consider financially supporting its development through the author's home page: https://www.jodybruchon.com/ .SH LICENSE MIT License Copyright (c) 2014-2025 Jody Lee Bruchon Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. libjodycode/libjodycode.h000066400000000000000000000376731507704630200160310ustar00rootroot00000000000000/* Jody Bruchon's helpful code library header * Copyright (C) 2014-2025 by Jody Bruchon * Licensed under The MIT License * Source code: https://codeberg.org/jbruchon/libjodycode */ #ifndef LIBJODYCODE_H #define LIBJODYCODE_H #ifdef __cplusplus extern "C" { #endif /* libjodycode version information * The major/minor version number and API version/revision MUST match! * Major version must change whenever an interface incompatibly changes * Minor version must change when new interfaces are added * Revision version must not change interfaces in any way * Revision is optional in version string, so "2.0" is identical to 2.0.0 * Feature level is incremented whenever the available interfaces change * regardless of compatibility; the lowest feature level possible that * supports the used interfaces should be chosen by programs that check * version information for compatibility. See README for more information. */ #define LIBJODYCODE_API_VERSION 4 #define LIBJODYCODE_API_FEATURE_LEVEL 5 #define LIBJODYCODE_VER "4.1.1" #define LIBJODYCODE_VERDATE "2025-10-25" #ifdef UNICODE #define LIBJODYCODE_WINDOWS_UNICODE 1 #else #define LIBJODYCODE_WINDOWS_UNICODE 0 #endif /* Define ON_WINDOWS if not otherwise defined and building on Windows */ #if defined _WIN32 || defined __WIN32 || defined WIN64 || defined __WIN64 #ifndef ON_WINDOWS #define ON_WINDOWS #endif #endif /* Silence a MSVC++ warning */ #ifdef _MSC_VER #pragma warning(disable : 4200) #endif #include #include #include #define JC_PATHBUF_SIZE 32768 #ifdef ON_WINDOWS #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MAN #endif #include #include /* Unicode conversion on Windows */ #ifndef M2W #define M2W(a,b) MultiByteToWideChar(CP_UTF8, 0, a, -1, (LPWSTR)b, JC_PATHBUF_SIZE) #endif #ifndef M2W_SIZED #define M2W_SIZED(a,b,c) MultiByteToWideChar(CP_UTF8, 0, a, -1, (LPWSTR)b, c) #endif #ifndef W2M #define W2M(a,b) WideCharToMultiByte(CP_UTF8, 0, a, -1, (LPSTR)b, JC_PATHBUF_SIZE, NULL, FALSE) #endif #ifndef W2M_SIZED #define W2M_SIZED(a,b,c) WideCharToMultiByte(CP_UTF8, 0, a, -1, (LPSTR)b, c, NULL, FALSE) #endif #define jc_GetLastError() (int32_t)GetLastError() #else #include #include #include #endif /* ON_WINDOWS */ /* Extend an allocation length to the next 64-bit (8-byte) boundary */ #define JC_EXTEND64(a) (((a) & 0x7) > 0 ? (((a) & (~0x7)) + 8) : (a)) /* File modes and wchar_t type */ /* Uther things depend on this, keep it at the top */ #if defined _WIN32 || defined __WIN32 || defined ON_WINDOWS #ifdef UNICODE #define JC_WCHAR_T wchar_t #define JC_FILE_MODE_RDONLY L"rb" #define JC_FILE_MODE_WRONLY L"wb" #define JC_FILE_MODE_RW L"w+b" #define JC_FILE_MODE_RW_EXISTING L"r+b" #define JC_FILE_MODE_WRONLY_APPEND L"ab" #define JC_FILE_MODE_RW_APPEND L"a+b" #define JC_FILE_MODE_RDONLY_SEQ L"rbS" #define JC_FILE_MODE_WRONLY_SEQ L"wbS" #define JC_FILE_MODE_RW_SEQ L"w+bS" #define JC_FILE_MODE_RW_EXISTING_SEQ L"r+bS" #define JC_FILE_MODE_WRONLY_APPEND_SEQ L"abS" #define JC_FILE_MODE_RW_APPEND_SEQ L"a+bS" #else /* Windows, not UNICODE */ #define JC_WCHAR_T char #define JC_FILE_MODE_RDONLY "rb" #define JC_FILE_MODE_WRONLY "wb" #define JC_FILE_MODE_RW "w+b" #define JC_FILE_MODE_RW_EXISTING "r+b" #define JC_FILE_MODE_WRONLY_APPEND "ab" #define JC_FILE_MODE_RW_APPEND "a+b" #define JC_FILE_MODE_RDONLY_SEQ "rbS" #define JC_FILE_MODE_WRONLY_SEQ "wbS" #define JC_FILE_MODE_RW_SEQ "w+bS" #define JC_FILE_MODE_RW_EXISTING_SEQ "r+bS" #define JC_FILE_MODE_WRONLY_APPEND_SEQ "abS" #define JC_FILE_MODE_RW_APPEND_SEQ "a+bS" #endif #define JC_F_OK 0 #define JC_R_OK 4 #define JC_W_OK 2 #define JC_X_OK 6 #else /* Not Windows */ #define JC_WCHAR_T char #define JC_FILE_MODE_RDONLY "rb" #define JC_FILE_MODE_WRONLY "wb" #define JC_FILE_MODE_RW "w+b" #define JC_FILE_MODE_RW_EXISTING "r+b" #define JC_FILE_MODE_WRONLY_APPEND "ab" #define JC_FILE_MODE_RW_APPEND "a+b" #define JC_FILE_MODE_RDONLY_SEQ "rb" #define JC_FILE_MODE_WRONLY_SEQ "wb" #define JC_FILE_MODE_RW_SEQ "w+b" #define JC_FILE_MODE_RW_EXISTING_SEQ "r+b" #define JC_FILE_MODE_WRONLY_APPEND_SEQ "ab" #define JC_FILE_MODE_RW_APPEND_SEQ "a+b" #define JC_F_OK F_OK #define JC_R_OK R_OK #define JC_W_OK W_OK #define JC_X_OK X_OK #endif /* Windows */ /*** time ***/ /* This is out of order because other things depend on it */ #ifdef ON_WINDOWS struct JC_TIMESPEC { time_t tv_sec; long tv_nsec; }; extern int jc_nttime_to_unixtime(const FILETIME * const restrict filetime, struct JC_TIMESPEC * const restrict unixtime); extern int jc_unixtime_to_nttime(const struct JC_TIMESPEC * const restrict unixtime, FILETIME * const restrict filetime); #else #define JC_TIMESPEC timespec #endif /* ON_WINDOWS */ /*** stat ***/ /* This is out of order because other things depend on it */ #ifdef ON_WINDOWS struct JC_STAT { uint64_t st_ino; int64_t st_size; uint32_t st_dev; uint32_t st_nlink; uint32_t st_mode; struct JC_TIMESPEC st_atim; struct JC_TIMESPEC st_mtim; struct JC_TIMESPEC st_ctim; /* legacy st_*time should be avoided. Use st_*tim.tv_sec instead. */ }; /* stat() macros for Windows "mode" flags (file attributes) */ #define JC_S_IFMT FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_READONLY | \ FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM | \ FILE_ATTRIBUTE_ENCRYPTED | FILE_ATTRIBUTE_DIRECTORY | \ FILE_ATTRIBUTE_COMPRESSED | FILE_ATTRIBUTE_REPARSE_POINT | \ FILE_ATTRIBUTE_SPARSE | FILE_ATTRIBUTE_TEMPORARY #define JC_S_IFARCHIVE FILE_ATTRIBUTE_ARCHIVE #define JC_S_IFRO FILE_ATTRIBUTE_READONLY #define JC_S_IFHIDDEN FILE_ATTRIBUTE_HIDDEN #define JC_S_IFSYSTEM FILE_ATTRIBUTE_SYSTEM #define JC_S_IFCRYPT FILE_ATTRIBUTE_ENCRYPTED #define JC_S_IFDIR FILE_ATTRIBUTE_DIRECTORY #define JC_S_IFCOMPR FILE_ATTRIBUTE_COMPRESSED #define JC_S_IFREPARSE FILE_ATTRIBUTE_REPARSE_POINT #define JC_S_IFSPARSE FILE_ATTRIBUTE_SPARSE #define JC_S_IFTEMP FILE_ATTRIBUTE_TEMPORARY #define JC_S_IFREG !(FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT) #define JC_S_IFLNK FILE_ATTRIBUTE_REPARSE_POINT #define JC_S_ISARCHIVE(st_mode) ((st_mode & FILE_ATTRIBUTE_ARCHIVE) ? 1 : 0) #define JC_S_ISRO(st_mode) ((st_mode & FILE_ATTRIBUTE_READONLY) ? 1 : 0) #define JC_S_ISHIDDEN(st_mode) ((st_mode & FILE_ATTRIBUTE_HIDDEN) ? 1 : 0) #define JC_S_ISSYSTEM(st_mode) ((st_mode & FILE_ATTRIBUTE_SYSTEM) ? 1 : 0) #define JC_S_ISCRYPT(st_mode) ((st_mode & FILE_ATTRIBUTE_ENCRYPTED) ? 1 : 0) #define JC_S_ISDIR(st_mode) ((st_mode & FILE_ATTRIBUTE_DIRECTORY) ? 1 : 0) #define JC_S_ISCOMPR(st_mode) ((st_mode & FILE_ATTRIBUTE_COMPRESSED) ? 1 : 0) #define JC_S_ISREPARSE(st_mode) ((st_mode & FILE_ATTRIBUTE_REPARSE_POINT) ? 1 : 0) #define JC_S_ISSPARSE(st_mode) ((st_mode & FILE_ATTRIBUTE_SPARSE) ? 1 : 0) #define JC_S_ISTEMP(st_mode) ((st_mode & FILE_ATTRIBUTE_TEMPORARY) ? 1 : 0) #define JC_S_ISREG(st_mode) ((st_mode & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT)) ? 0 : 1) #define JC_S_ISLNK(st_mode) ((st_mode & FILE_ATTRIBUTE_REPARSE_POINT) ? 1 : 0) #else #include #define JC_STAT stat #define JC_S_IFMT S_IFMT #define JC_S_IFARCHIVE 0 #define JC_S_IFRO 0 #define JC_S_IFHIDDEN 0 #define JC_S_IFSYSTEM 0 #define JC_S_IFCRYPT 0 #define JC_S_IFDIR S_IFDIR #define JC_S_IFCOMPR 0 #define JC_S_IFREPARSE 0 #define JC_S_IFSPARSE 0 #define JC_S_IFTEMP 0 #define JC_S_IFREG S_IFREG #define JC_S_IFBLK S_IFBLK #define JC_S_IFCHR S_IFCHR #define JC_S_IFIFO S_IFIFO #define JC_S_IFSOCK S_IFSOCK #define JC_S_ISARCHIVE(st_mode) 0 #define JC_S_ISRO(st_mode) 0 #define JC_S_ISHIDDEN(st_mode) 0 #define JC_S_ISSYSTEM(st_mode) 0 #define JC_S_ISCRYPT(st_mode) 0 #define JC_S_ISDIR(st_mode) S_ISDIR(st_mode) #define JC_S_ISCOMPR(st_mode) 0 #define JC_S_ISREPARSE(st_mode) 0 #define JC_S_ISSPARSE(st_mode) 0 #define JC_S_ISTEMP(st_mode) 0 #define JC_S_ISREG(st_mode) S_ISREG(st_mode) #define JC_S_ISLNK(st_mode) S_ISLNK(st_mode) #endif /* ON_WINDOWS */ extern int jc_stat(const char * const filename, struct JC_STAT * const restrict buf); /*** access ***/ extern int jc_access(const char *pathname, int mode); /*** alarm ***/ extern int jc_alarm_ring; extern int jc_start_alarm(const unsigned int seconds, const int repeat); extern int jc_stop_alarm(void); /*** cacheinfo ***/ /* Cache information structure * Split caches populate i/d, unified caches populate non-i/d */ struct jc_proc_cacheinfo { size_t l1; size_t l1i; size_t l1d; size_t l2; size_t l2i; size_t l2d; size_t l3; size_t l3i; size_t l3d; }; extern struct jc_proc_cacheinfo *jc_get_proc_cacheinfo(int cleanup); /*** dir ***/ /* Directory stream type * Must be hijacked because FindFirstFileW() does one readdir() equivalent too * When the first file is returned, this entry is removed from the linked list */ #ifdef ON_WINDOWS struct JC_DIRENT { uint64_t d_ino; uint32_t d_namlen; /* we already do a strlen() so may as well pass it on */ unsigned char d_type; char d_name[]; }; typedef struct _JC_DIR_T { struct _JC_DIR_T *next; int cached; HANDLE hFind; WIN32_FIND_DATA ffd; struct JC_DIRENT dirent; } JC_DIR; #define JC_DIRENT_HAVE_D_NAMLEN #define JC_DIRENT_HAVE_D_TYPE #define JC_DT_BLK 6 #define JC_DT_CHR 2 #define JC_DT_DIR 4 #define JC_DT_FIFO 1 #define JC_DT_LNK 10 #define JC_DT_REG 8 #define JC_DT_SOCK 12 #define JC_DT_UNKNOWN 0 #define JC_DT_WHT 14 #else #define JC_DIR DIR #define JC_DIRENT dirent #ifdef _DIRENT_HAVE_D_NAMLEN #define JC_DIRENT_HAVE_D_NAMLEN #endif #ifdef _DIRENT_HAVE_D_TYPE #define JC_DIRENT_HAVE_D_TYPE #endif #ifdef _DIRENT_HAVE_D_RECLEN #define JC_DIRENT_HAVE_D_RECLEN #endif #ifdef _DIRENT_HAVE_D_OFF #define JC_DIRENT_HAVE_D_OFF #endif #ifdef DT_UNKNOWN /* Cheap way to detect d_type support in the preprocessor */ #define JC_DT_BLK DT_BLK #define JC_DT_CHR DT_CHR #define JC_DT_DIR DT_DIR #define JC_DT_FIFO DT_FIFO #define JC_DT_LNK DT_LNK #define JC_DT_REG DT_REG #define JC_DT_SOCK DT_SOCK #define JC_DT_UNKNOWN DT_UNKNOWN #define JC_DT_WHT DT_WHT #endif /* DT_UNKNOWN */ #endif /* ON_WINDOWS */ extern JC_DIR *jc_opendir(const char * restrict path); extern size_t jc_get_d_namlen(const struct JC_DIRENT * const restrict dirent); extern struct JC_DIRENT *jc_readdir(JC_DIR * const restrict dirp); extern int jc_closedir(JC_DIR * const restrict dirp); /*** batch ***/ /* This is out of order because it depends on the dir definitions */ struct jc_fileinfo { struct JC_STAT *stat; struct JC_DIRENT *dirent; int status; }; struct jc_fileinfo_batch { int count; struct jc_fileinfo files[]; }; extern struct jc_fileinfo_batch *jc_fileinfo_batch_alloc(const int filecnt, const int stat, const int namlen); extern void jc_fileinfo_batch_free(struct jc_fileinfo_batch * const restrict batch); /*** error ***/ extern int32_t jc_errno; extern const char *jc_get_errname(int errnum); extern const char *jc_get_errdesc(int errnum); extern int jc_print_error(int errnum); #define JC_ERRORCODE_START 1024 #define JC_ENOERROR 1024 // 0 #define JC_ENULL 1025 #define JC_ECDOTDOT 1026 #define JC_EGRNEND 1027 #define JC_EBADERR 1028 #define JC_EBADARGV 1029 #define JC_EMBWC 1030 #define JC_EALARM 1031 #define JC_EALLOC 1032 #define JC_ENUMSTRCMP 1033 #define JC_EDATETIME 1034 // 10 #define JC_EWIN32API 1035 #define JC_EKERNVER 1036 #define JC_ENOMEM 1037 #define JC_ESETVBUF 1038 /*** fopen ***/ extern FILE *jc_fopen(const char *pathname, const JC_WCHAR_T *mode); extern int jc_fclose(FILE *stream); /*** jc_fwprint ***/ extern int jc_fwprint(FILE * const restrict stream, const char * const restrict str, const int cr); /*** getcwd ***/ extern char *jc_getcwd(char * const restrict pathname, const size_t size); /*** jody_hash ***/ #ifndef JODY_HASH_H #define JODY_HASH_VERSION 7 #define JODY_HASH_WIDTH 64 typedef uint64_t jodyhash_t; #endif enum jc_e_hash { NORMAL = 0, ROLLING = 1 }; extern int jc_block_hash(const enum jc_e_hash type, jodyhash_t *data, jodyhash_t *hash, const size_t count); /*** link ***/ extern int jc_link(const char *path1, const char *path2); /*** linkfiles ***/ enum jc_e_link { SYMLINK = 0, HARDLINK = 1, REFLINK = 2 }; extern int jc_linkfiles(struct jc_fileinfo_batch * const restrict batch, const enum jc_e_link linktype); /*** oom ***/ /* Out-of-memory and null pointer error-exit functions */ extern void jc_oom(const char * restrict msg); extern void jc_nullptr(const char * restrict func); /*** paths ***/ /* Remove "middle" '..' components in a path: 'foo/../bar/baz' => 'bar/baz' */ extern int jc_collapse_dotdot(char * const path); /* Given a src and dest path, create a relative path name from src to dest */ extern int jc_make_relative_link_name(const char * const src, const char * const dest, char * rel_path); /*** rename ***/ extern int jc_rename(const char *oldpath, const char *newpath); /*** remove ***/ extern int jc_remove(const char *pathname); /*** size_suffix ***/ /* Suffix definitions (treat as case-insensitive) */ struct jc_size_suffix { const char * const suffix; const int64_t multiplier; const int shift; }; extern const struct jc_size_suffix jc_size_suffix[]; /*** sort ***/ /* Numerically-correct string sort with a little extra intelligence insensitive: 0 = case-sensitive, 1 = case-insensitive */ extern int jc_numeric_strcmp(const char * restrict c1, const char * restrict c2, const int insensitive); /*** string ***/ extern const char *jc_emptystring; /* string type with length prefix */ typedef struct _JC_STR_T { uint64_t len; char str[1]; } JC_STR_T; extern JC_STR_T *jc_str_init(const char *string, uint32_t len); extern int jc_strncaseeq(const char *s1, const char *s2, const size_t len); extern int jc_strcaseeq(const char *s1, const char *s2); extern int jc_strneq(const char *s1, const char *s2, const size_t len); extern int jc_streq(const char *s1, const char *s2); extern int jc_strteq(const JC_STR_T *s1, const JC_STR_T *s2); extern int jc_strtneq(const JC_STR_T *s1, const JC_STR_T *s2, const size_t len); extern int jc_strtcaseeq(const JC_STR_T *s1, const JC_STR_T *s2); extern int jc_strtncaseeq(const JC_STR_T *s1, const JC_STR_T *s2, const size_t len); extern int jc_numeric_strtcmp(const JC_STR_T *c1, const JC_STR_T *c2, const int insensitive); /*** strtoepoch ***/ /* Convert a date/time string to seconds since the epoch * Format must be "YYYY-MM-DD" or "YYYY-MM-DD HH:MM:SS" */ extern time_t jc_strtoepoch(const char * const datetime); /*** version ***/ /* libjodycode version information */ extern const char *jc_version; extern const char *jc_verdate; extern const int jc_api_version; extern const int jc_api_featurelevel; extern const int jc_jodyhash_version; extern const int jc_windows_unicode; #ifdef __linux__ extern int jc_get_kernel_version(void); #endif /*** win_unicode ***/ /* Cross-platform help for strings in Unicode mode on Windows * On non-Windows platforms a lot of these are just wrappers */ #ifdef ON_WINDOWS #define JC_MODE_NO_CHANGE 0 /* Don't change the output mode */ #define JC_MODE_TEXT 1 /* Set output mode to _O_TEXT */ #define JC_MODE_BINARY 2 /* Set output mode to _O_BINARY (UTF-8) */ #define JC_MODE_UTF16 3 /* Set output mode to _O_U16TEXT (UTF-16) */ #define JC_MODE_UTF16_TTY 4 /* Set non-_O_TEXT output mode based on if it's a terminal or not */ extern JC_DIR *dirp_head; extern int jc_ffd_to_dirent(JC_DIR **dirp, HANDLE hFind, WIN32_FIND_DATA *ffd); extern void jc_slash_convert(char *path); extern void jc_set_output_modes(const int out, const int err); /* These are used for Unicode output and string work on Windows only */ #ifdef UNICODE extern int jc_string_to_wstring(const char * const restrict string, JC_WCHAR_T **wstring); extern int jc_widearg_to_argv(int argc, JC_WCHAR_T **wargv, char ***cargv); #endif /* UNICODE */ #else #define jc_slash_convert(a) #define jc_set_output_modes(a,b) #endif extern int jc_setup_unicode_terminal(int argc, JC_WCHAR_T **wargv, char ***argv, int *stdout_tty); #ifdef __cplusplus } #endif #endif /* LIBJODYCODE_H */ libjodycode/likely_unlikely.h000066400000000000000000000011731507704630200167310ustar00rootroot00000000000000/* likely()/unlikely() macros for branch optimization * By Jody Bruchon * Released to the public domain */ #ifndef LIKELY_UNLIKELY_H #define LIKELY_UNLIKELY_H #ifdef __cplusplus extern "C" { #endif /* Un-define if already defined */ #if !defined NO_LIKELY_UNLIKELY && (defined __GNUC__ || defined __clang__) #ifdef likely #undef likely #endif #ifdef unlikely #undef unlikely #endif #define likely(a) __builtin_expect((a), 1) #define unlikely(a) __builtin_expect((a), 0) #else /* no GCC/Clang */ #define likely(a) a #define unlikely(a) a #endif #ifdef __cplusplus } #endif #endif /* LIKELY_UNLIKELY_H */ libjodycode/link.c000066400000000000000000000022221507704630200144500ustar00rootroot00000000000000/* libjodycode: link() stdio call * * Copyright (C) 2014-2025 by Jody Bruchon * Released under The MIT License */ #include #include #include #include #include "likely_unlikely.h" #include "libjodycode.h" #ifdef UNICODE #define WIN32_LEAN_AND_MEAN #include #endif /* Hard link a file, converting for Windows if necessary */ int jc_link(const char *path1, const char *path2) { int retval = 0; #ifdef UNICODE JC_WCHAR_T *widename1, *widename2; #endif if (unlikely(path1 == NULL || path2 == NULL)) { jc_errno = EFAULT; return -1; } #ifdef ON_WINDOWS #ifdef UNICODE if (jc_string_to_wstring(path1, &widename1) != 0 || jc_string_to_wstring(path2, &widename2) != 0) { jc_errno = ENOMEM; return -1; } if (CreateHardLinkW((LPCWSTR)widename2, (LPCWSTR)widename1, NULL) == 0) retval = -1; free(widename1); free(widename2); #else if (CreateHardLink(path2, path1, NULL) == 0) retval = -1; #endif /* UNICODE */ if (retval != 0) jc_errno = jc_GetLastError(); #else retval = link(path1, path2); if (retval != 0) jc_errno = errno; #endif /* ON_WINDOWS */ return retval; } libjodycode/linkfiles.c000066400000000000000000000317571507704630200155120ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include "libjodycode.h" #include "likely_unlikely.h" /* Apple clonefile() is basically a hard link */ #ifdef __APPLE__ #include #include #ifndef NO_CLONEFILE #include #define ENABLE_CLONEFILE 1 #endif /* NO_CLONEFILE */ #endif /* __APPLE__ */ #ifdef __linux__ #include #ifndef FICLONE #define FICLONE _IOW(0x94, 9, int) #endif #include #endif /* __linux__ */ #define TEMPORARY_SUFFIX "._link_.tmp"; static const char *tempsuffix = TEMPORARY_SUFFIX; static const size_t templen = sizeof TEMPORARY_SUFFIX; static char *alloc_tempname(struct JC_DIRENT *dirent) { char *tempname = NULL; size_t len; len = jc_get_d_namlen(dirent); if (len == 0) goto error_params; tempname = (char *)malloc(len + templen); if (tempname == NULL) goto error_oom; memcpy(tempname, dirent->d_name, len); memcpy(tempname + len, tempsuffix, templen); return tempname; error_params: jc_errno = EFAULT; return NULL; error_oom: jc_errno = ENOMEM; return NULL; } /* Rename a file to a temporary name for safe linking */ static int safe_rename_for_link(const struct jc_fileinfo * const restrict file, const char **newname) { char *tempname = NULL; if (unlikely(file == NULL)) goto error_params; tempname = alloc_tempname(file->dirent); if (tempname == NULL) goto error_params; errno = 0; if (unlikely(jc_rename(file->dirent->d_name, tempname) == -1)) goto error_with_errno; if (newname == NULL) free(tempname); else *newname = tempname; return 0; error_params: jc_errno = EFAULT; return -1; error_with_errno: if (tempname != NULL) free(tempname); jc_errno = errno; return -1; } static int safe_rename_revert(const struct jc_fileinfo * const restrict file, char *newname) { char *tempname; if (unlikely(file == NULL)) goto error_params; if (newname == NULL) { tempname = alloc_tempname(file->dirent); if (tempname == NULL) goto error_params; } else tempname = newname; errno = 0; if (unlikely(jc_rename(tempname, file->dirent->d_name) == -1)) goto error_with_errno; free(tempname); return 0; error_params: jc_errno = EFAULT; return -1; error_with_errno: if (tempname != NULL) free(tempname); jc_errno = errno; return -1; } int jc_linkfiles(struct jc_fileinfo_batch * const restrict batch, const enum jc_e_link linktype) { int i, retval = 0; if (unlikely(batch == NULL || batch->count < 2 || linktype > 2 || linktype < 0)) goto error_bad_params; /* All batch statuses default to general failure */ for (i = 1; i < batch->count; i++) { batch->files[i].status = ECANCELED; if (unlikely(batch->files[i].dirent == NULL)) goto error_bad_params; } #ifdef __linux__ if (unlikely(jc_get_kernel_version() < 3012000)) goto error_kernel_version; #endif /* __linux__ */ /* TODO: hard link + clonefile link, symlink */ switch (linktype) { /* "Soft" (symbolic) link */ case 0: if (unlikely(batch->files[0].stat->st_mode == JC_DT_LNK)) goto error_bad_params; if (unlikely(batch->files[0].dirent->d_type == JC_DT_LNK)) goto error_bad_params; break; /* Hard link */ default: case 1: break; /* Clone link or reflink */ case 2: #if defined __linux__ && defined FICLONE { /* Linux FICLONE (reflink copy) */ int src_fd, dest_fd; if (batch->files[0].dirent == NULL) goto error_bad_params; errno = 0; src_fd = open(batch->files[0].dirent->d_name, O_RDONLY); if (unlikely(src_fd < 0)) { batch->files[0].status = errno; jc_errno = EIO; return -1; } for (i = 1; i < batch->count; i++) { errno = 0; dest_fd = open(batch->files[i].dirent->d_name, O_RDWR); if (unlikely(dest_fd < 0)) goto error_ficlone; errno = 0; if (unlikely(ioctl(dest_fd, FICLONE, src_fd) == -1)) goto error_ficlone; batch->files[i].status = errno; close(dest_fd); continue; error_ficlone: batch->files[i].status = errno; if (errno != 0) jc_errno = EIO; retval = -1; continue; } close(src_fd); } #else /* not __linux__ */ jc_errno = ENOTSUP; return -1; #endif /* __linux__ */ break; } return retval; #ifdef __linux__ error_kernel_version: jc_errno = JC_EKERNVER; return -1; #endif /* __linux__ */ error_bad_params: jc_errno = EFAULT; return -1; } #if 0 /* linktype: 0=soft (symbolic), 1=hard, 2=clone/reflink */ int jc_linkfiles(struct jc_fileinfo_batch *batch, const int linktype) { int *srcfile; unsigned int x = 0; size_t name_len = 0; int i, success; #ifndef NO_SYMLINKS unsigned int symsrc; char rel_path[JC_PATHBUF_SIZE + 4]; #endif #if defined ON_WINDOWS || defined ENABLE_CLONEFILE_LINK struct JC_STAT s; #endif #ifdef ENABLE_CLONEFILE_LINK unsigned int srcfile_preserved_flags = 0; unsigned int dupfile_preserved_flags = 0; unsigned int dupfile_original_flags = 0; struct timeval dupfile_original_tval[2]; #endif for (i = 1; i < count; count++) { /* Link every file to the first file */ if (linktype != 0) { #ifndef NO_HARDLINKS srcfile = 0; x = 2; #else return -1; #endif } else { #ifndef NO_SYMLINKS x = 1; /* Symlinks should target a normal file if one exists */ srcfile = NULL; for (symsrc = 0; symsrc <= count; symsrc++) { if (!ISFLAG(dupelist[symsrc]->flags, FF_IS_SYMLINK)) { srcfile = symsrc; break; } } /* If no normal file exists, abort */ if (srcfile == NULL) goto linkfile_loop; #else linkfiles_nosupport("soft", "symlink"); #endif } if (linktype == 2) { #ifdef ENABLE_CLONEFILE_LINK if (jc_stat(srcfile->d_name, &s) != 0) { fprintf(stderr, "warning: stat() on source file failed, skipping:\n[SRC] "); jc_fwprint(stderr, srcfile->d_name, 1); exit_status = EXIT_FAILURE; goto linkfile_loop; } /* macOS unexpectedly copies the compressed flag when copying metadata * (which can result in files being unreadable), so we want to retain * the compression flag of srcfile */ srcfile_preserved_flags = s.st_flags & UF_COMPRESSED; #else linkfiles_nosupport("clone", "clonefile"); #endif } for (; x <= count; x++) { if (linktype == 1 || linktype == 2) { /* Can't hard link files on different devices */ if (srcfile->device != dupelist[x]->device) { fprintf(stderr, "warning: hard link target on different device, not linking:\n-//-> "); jc_fwprint(stderr, dupelist[x]->d_name, 1); exit_status = EXIT_FAILURE; continue; } else { /* The devices for the files are the same, but we still need to skip * anything that is already hard linked (-L and -H both set) */ if (srcfile->inode == dupelist[x]->inode) { continue; } } } else { /* Symlink prerequisite check code can go here */ /* Do not attempt to symlink a file to itself or to another symlink */ #ifndef NO_SYMLINKS if (ISFLAG(dupelist[x]->flags, FF_IS_SYMLINK) && ISFLAG(dupelist[symsrc]->flags, FF_IS_SYMLINK)) continue; if (x == symsrc) continue; #endif } /* Do not attempt to hard link files for which we don't have write access */ if ( #ifdef ON_WINDOWS !JC_S_ISRO(dupelist[x]->mode) && #endif (jc_access(dupelist[x]->d_name, JC_W_OK) != 0)) { fprintf(stderr, "warning: link target is a read-only file, not linking:\n-//-> "); jc_fwprint(stderr, dupelist[x]->d_name, 1); exit_status = EXIT_FAILURE; continue; } /* Check file pairs for modification before linking */ /* Safe linking: don't actually delete until the link succeeds */ i = file_has_changed(srcfile); if (i) { fprintf(stderr, "warning: source file modified since scanned; changing source file:\n[SRC] "); jc_fwprint(stderr, dupelist[x]->d_name, 1); srcfile = dupelist[x]; exit_status = EXIT_FAILURE; continue; } if (file_has_changed(dupelist[x])) { fprintf(stderr, "warning: target file modified since scanned, not linking:\n-//-> "); jc_fwprint(stderr, dupelist[x]->d_name, 1); exit_status = EXIT_FAILURE; continue; } #ifdef ON_WINDOWS /* For Windows, the hard link count maximum is 1023 (+1); work around * by skipping linking or changing the link source file as needed */ if (jc_stat(srcfile->d_name, &s) != 0) { fprintf(stderr, "warning: win_stat() on source file failed, changing source file:\n[SRC] "); jc_fwprint(stderr, dupelist[x]->d_name, 1); srcfile = dupelist[x]; exit_status = EXIT_FAILURE; continue; } if (s.st_nlink >= 1024) { fprintf(stderr, "warning: maximum source link count reached, changing source file:\n[SRC] "); srcfile = dupelist[x]; exit_status = EXIT_FAILURE; continue; } if (jc_stat(dupelist[x]->d_name, &s) != 0) continue; if (s.st_nlink >= 1024) { fprintf(stderr, "warning: maximum destination link count reached, skipping:\n-//-> "); jc_fwprint(stderr, dupelist[x]->d_name, 1); exit_status = EXIT_FAILURE; continue; } #endif #ifdef ENABLE_CLONEFILE_LINK if (linktype == 2) { if (jc_stat(dupelist[x]->d_name, &s) != 0) { fprintf(stderr, "warning: stat() on destination file failed, skipping:\n-##-> "); jc_fwprint(stderr, dupelist[x]->d_name, 1); exit_status = EXIT_FAILURE; continue; } /* macOS unexpectedly copies the compressed flag when copying metadata * (which can result in files being unreadable), so we want to ignore * the compression flag on dstfile in favor of the one from srcfile */ dupfile_preserved_flags = s.st_flags & ~(unsigned int)UF_COMPRESSED; dupfile_original_flags = s.st_flags; dupfile_original_tval[0].tv_sec = s.st_atim.tv_sec; dupfile_original_tval[0].tv_usec = s.st_atim.tv_usec; dupfile_original_tval[1].tv_sec = s.st_mtim.tv_sec; dupfile_original_tval[1].tv_usec = s.st_mtim.tv_usec; } #endif /* Make sure the name will fit in the buffer before trying */ name_len = strlen(dupelist[x]->d_name) + 11; if (name_len > JC_PATHBUF_SIZE) continue; /* Assemble a temporary file name */ if (i != 0) { fprintf(stderr, "warning: cannot move link target to a temporary name, not linking:\n-//-> "); jc_fwprint(stderr, dupelist[x]->d_name, 1); exit_status = EXIT_FAILURE; /* Just in case the rename succeeded yet still returned an error, roll back the rename */ jc_rename(tempname, dupelist[x]->d_name); continue; } /* Create the desired hard link with the original file's name */ errno = 0; success = 0; if (linktype == 1) { if (jc_link(srcfile->d_name, dupelist[x]->d_name) == 0) success = 1; #ifdef ENABLE_CLONEFILE_LINK } else if (linktype == 2) { if (clonefile(srcfile->d_name, dupelist[x]->d_name, 0) == 0) { if (copyfile(tempname, dupelist[x]->d_name, NULL, COPYFILE_METADATA) == 0) { /* If the preserved flags match what we just copied from the original dupfile, we're done. * Otherwise, we need to update the flags to avoid data loss due to differing compression flags */ if (dupfile_original_flags == (srcfile_preserved_flags | dupfile_preserved_flags)) { success = 1; } else if (chflags(dupelist[x]->d_name, srcfile_preserved_flags | dupfile_preserved_flags) == 0) { /* chflags overrides the timestamps that were restored by copyfile, so we need to reapply those as well */ if (utimes(dupelist[x]->d_name, dupfile_original_tval) == 0) { success = 1; } else clonefile_error("utimes", dupelist[x]->d_name); } else clonefile_error("chflags", dupelist[x]->d_name); } else clonefile_error("copyfile", dupelist[x]->d_name); } else clonefile_error("clonefile", dupelist[x]->d_name); #endif /* ENABLE_CLONEFILE_LINK */ } #ifndef NO_SYMLINKS else { i = jc_make_relative_link_name(srcfile->d_name, dupelist[x]->d_name, rel_path); if (i < 0) { fprintf(stderr, "warning: make_relative_link_name() failed (%d)\n", i); } else if (i == 1) { fprintf(stderr, "warning: files to be linked have the same canonical path; not linking\n"); } else if (symlink(rel_path, dupelist[x]->d_name) == 0) success = 1; } #endif /* NO_SYMLINKS */ if (!success) { /* The link failed. Warn the user and put the link target back */ exit_status = EXIT_FAILURE; i = jc_rename(tempname, dupelist[x]->d_name); if (i != 0) revert_failed(dupelist[x]->d_name, tempname); continue; } /* Remove temporary file to clean up; if we can't, reverse the linking */ i = jc_remove(tempname); if (i != 0) { /* If the temp file can't be deleted, there may be a permissions problem * so reverse the process and warn the user */ fprintf(stderr, "\nwarning: can't delete temp file, reverting: "); jc_fwprint(stderr, tempname, 1); exit_status = EXIT_FAILURE; i = jc_remove(dupelist[x]->d_name); /* This last error really should not happen, but we can't assume it won't */ if (i != 0) fprintf(stderr, "\nwarning: couldn't remove link to restore original file\n"); else { i = jc_rename(tempname, dupelist[x]->d_name); if (i != 0) revert_failed(dupelist[x]->d_name, tempname); } } } #if !defined NO_SYMLINKS || defined ENABLE_CLONEFILE_LINK linkfile_loop: #endif } return; } #endif // 0 libjodycode/numstrcmp.c000066400000000000000000000055541507704630200155560ustar00rootroot00000000000000/* libjodycode: numerically correct string comparison * that "sorts" symbols and spaces AFTER alphanumeric characters * * Copyright (C) 2014-2025 by Jody Bruchon * Released under The MIT License */ #include #include #include "likely_unlikely.h" #include "libjodycode.h" #define IS_NUM(a) (((a >= '0') && (a <= '9')) ? 1 : 0) #define IS_LOWER(a) (((a >= 'a') && (a <= 'z')) ? 1 : 0) /* sensitive: 0 = case-sensitive, 1 = case-insensitive */ int jc_numeric_strcmp(const char * restrict c1, const char * restrict c2, const int insensitive) { int precompare; uintptr_t len1, len2; const char *rewind1, *rewind2; const char *start1, *start2; if (unlikely(c1 == NULL || c2 == NULL)) return JC_ENUMSTRCMP; start1 = c1; start2 = c2; while (unlikely(*c1 != '\0' && *c2 != '\0')) { /* Reset string rewind points */ rewind1 = c1; rewind2 = c2; /* Skip all sequences of zeroes */ while (*c1 == '0') c1++; while (*c2 == '0') c2++; /* If both chars are numeric, do a numeric comparison */ if (IS_NUM(*c1) && IS_NUM(*c2)) { precompare = 0; /* Scan numbers and get preliminary results */ while (IS_NUM(*c1) && IS_NUM(*c2)) { if (*c1 < *c2) precompare = -1; if (*c1 > *c2) precompare = 1; c1++; c2++; /* Skip remaining digit pairs after any difference is found */ if (precompare != 0) { for (; IS_NUM(*c1) && IS_NUM(*c2); c1++, c2++); break; } } /* One numeric and one non-numeric means the numeric one is larger and sorts later */ if (IS_NUM(*c1) ^ IS_NUM(*c2)) { if (IS_NUM(*c1) != 0) return 1; else return -1; } /* If the last test fell through, numbers are of equal length. Use the precompare result as the result for this number comparison. */ if (precompare != 0) return precompare; } else { /* Zeroes aren't followed by a digit; rewind the streams */ c1 = rewind1; c2 = rewind2; } /* Do normal comparison */ if (likely(*c1 == *c2 && *c1 != '\0' && *c2 != '\0')) { c1++; c2++; /* Put symbols and spaces after everything else */ } else if (*c2 < '.' && *c1 >= '.') { return -1; } else if (*c1 < '.' && *c2 >= '.') { return 1; } else { /* Normal strcasecmp() style compare */ char s1 = *c1, s2 = *c2; /* Convert lowercase into uppercase */ if (insensitive == 1) { if (IS_LOWER(s1)) s1 = (char)(s1 - 32); if (IS_LOWER(s2)) s2 = (char)(s2 - 32); } if (s1 > s2) return 1; else return -1; } } len1 = (uintptr_t)start1 - (uintptr_t)c1; len2 = (uintptr_t)start2 - (uintptr_t)c2; /* Longer strings generally sort later */ if (len1 < len2) return -1; if (len1 > len2) return 1; /* Normal comparison - FIXME? length check should already handle these */ if (unlikely(*c1 == '\0' && *c2 != '\0')) return -1; if (unlikely(*c1 != '\0' && *c2 == '\0')) return 1; /* Fall through: the strings are equal */ return 0; } libjodycode/oom.c000066400000000000000000000012311507704630200143040ustar00rootroot00000000000000/* libjodycoe: out-of-memory and NULL pointer error exits * * Copyright (C) 2021-2025 by Jody Bruchon * Released under The MIT License */ #include #include #include "libjodycode.h" static const char msg_unknown[] = "(unknown)"; /* Out of memory failure */ void jc_oom(const char * restrict msg) { if (msg == NULL) msg = msg_unknown; fprintf(stderr, "\nout of memory: %s\n", msg); exit(EXIT_FAILURE); } /* Null pointer failure */ void jc_nullptr(const char * restrict func) { if (func == NULL) func = msg_unknown; fprintf(stderr, "\ninternal error: NULL pointer caught at %s\n", func); exit(EXIT_FAILURE); } libjodycode/paths.c000066400000000000000000000103221507704630200146320ustar00rootroot00000000000000/* libjodycode: path manipulation * * Copyright (C) 2014-2025 by Jody Bruchon * Released under The MIT License */ #include #include #include #include "likely_unlikely.h" #include "libjodycode.h" /* Collapse dot-dot and single dot path components * This code MUST be passed a full file pathname (starting with '/') */ int jc_collapse_dotdot(char * const path) { char *p; /* string copy input */ char *out; /* string copy output */ unsigned int i = 0; if (unlikely(*path != '/')) return JC_ENULL; p = path; out = path; while (*p != '\0') { /* Abort if we're too close to the end of the buffer */ if (unlikely(i >= (JC_PATHBUF_SIZE - 3))) return JC_ECDOTDOT; /* Skip repeated slashes */ while (*p == '/' && *(p + 1) == '/') { p++; i++; } /* Scan for '/./', '/..', '/.\0' combinations */ if (*p == '/' && *(p + 1) == '.' && (*(p + 2) == '.' || *(p + 2) == '/' || *(p + 2) == '\0')) { /* Check for '../' or terminal '..' */ if (*(p + 2) == '.' && (*(p + 3) == '/' || *(p + 3) == '\0')) { /* Found a dot-dot; pull everything back to the previous directory */ p += 3; i += 3; /* If already at root, skip over the dot-dot */ if (i == 0) continue; /* Don't seek back past the first character */ if ((uintptr_t)out == (uintptr_t)path) continue; out--; while (*out != '/') out--; if (*p == '\0') break; continue; } else if (*(p + 2) == '/' || *(p + 2) == '\0') { /* Found a single dot; seek input ptr past it */ p += 2; i += 2; if (*p == '\0') break; continue; } /* Fall through: not a dot or dot-dot, just a slash */ } /* Copy all remaining text */ *out = *p; p++; out++; i++; } /* If only a root slash remains, be sure to keep it */ if ((uintptr_t)out == (uintptr_t)path) { *out = '/'; out++; } /* Output must always be terminated properly */ *out = '\0'; return 0; } /* Create a relative symbolic link path for a destination file */ int jc_make_relative_link_name(const char * const src, const char * const dest, char * rel_path) { char *p1, *p2; char *sp, *dp, *ss; int retval; if (unlikely(!src || !dest)) return JC_ENULL; p1 = (char *)malloc(JC_PATHBUF_SIZE + 8); if (unlikely(p1 == NULL)) return JC_ENOMEM; p2 = (char *)malloc(JC_PATHBUF_SIZE + 8); if (unlikely(p2 == NULL)) { free(p1); return JC_ENOMEM; } /* Get working directory path and prefix to pathnames if needed */ if (*src != '/' || *dest != '/') { jc_errno = 0; if (!jc_getcwd(p1, JC_PATHBUF_SIZE)) { retval = jc_errno; goto finish; } *(p1 + JC_PATHBUF_SIZE - 1) = '\0'; #ifdef ON_WINDOWS strncat_s(p1, JC_PATHBUF_SIZE, "/", JC_PATHBUF_SIZE - 1); strncpy_s(p2, JC_PATHBUF_SIZE, p1, JC_PATHBUF_SIZE); #else strncat(p1, "/", JC_PATHBUF_SIZE - 1); strncpy(p2, p1, JC_PATHBUF_SIZE); #endif } /* If an absolute path is provided, use it as-is */ if (*src == '/') *p1 = '\0'; if (*dest == '/') *p2 = '\0'; /* Concatenate working directory to relative paths */ #ifdef ON_WINDOWS strncat_s(p1, JC_PATHBUF_SIZE, src, JC_PATHBUF_SIZE); strncat_s(p2, JC_PATHBUF_SIZE, dest, JC_PATHBUF_SIZE); #else strncat(p1, src, JC_PATHBUF_SIZE); strncat(p2, dest, JC_PATHBUF_SIZE); #endif /* Collapse . and .. path components */ if (unlikely(jc_collapse_dotdot(p1) != 0 || jc_collapse_dotdot(p2) != 0)) { retval = JC_ECDOTDOT; goto finish; } /* Find where paths differ, remembering each slash along the way */ sp = p1; dp = p2; ss = p1; while (*sp == *dp && *sp != '\0' && *dp != '\0') { if (*sp == '/') ss = sp; sp++; dp++; } /* If paths are 100% identical then the files are the same file */ if (*sp == '\0' && *dp == '\0') { retval = 1; goto finish; } /* Replace dirs in destination path with dot-dot */ while (*dp != '\0') { if (*dp == '/') { *rel_path++ = '.'; *rel_path++ = '.'; *rel_path++ = '/'; } dp++; } /* Copy the file name into rel_path and return */ ss++; while (unlikely(*ss != '\0')) *rel_path++ = *ss++; /* . and .. dirs at end are invalid */ if (*(rel_path - 1) == '.') if (*(rel_path - 2) == '/' || (*(rel_path - 2) == '.' && *(rel_path - 3) == '/') || unlikely(*(rel_path - 1) == '/')) { retval = JC_EGRNEND; } *rel_path = '\0'; finish: free(p1); free(p2); return retval; } libjodycode/remove.c000066400000000000000000000015201507704630200150100ustar00rootroot00000000000000/* libjodycode: remove() stdio call * * Copyright (C) 2014-2025 by Jody Bruchon * Released under The MIT License */ #include #include #include "likely_unlikely.h" #include "libjodycode.h" #ifdef UNICODE #define WIN32_LEAN_AND_MEAN #include #endif /* Delete a file, converting for Windows if necessary */ int jc_remove(const char *pathname) { int retval; #ifdef UNICODE JC_WCHAR_T *widename; #endif if (unlikely(pathname == NULL)) { jc_errno = EFAULT; return -1; } #ifdef UNICODE if (jc_string_to_wstring(pathname, &widename) != 0) { jc_errno = ENOMEM; return -1; } retval = DeleteFileW(widename) ? 0 : -1; free(widename); if (retval != 0) jc_errno = jc_GetLastError(); #else retval = remove(pathname); if (retval != 0) jc_errno = errno; #endif return retval; } libjodycode/rename.c000066400000000000000000000017411507704630200147670ustar00rootroot00000000000000/* libjodycode: rename() stdio call * * Copyright (C) 2014-2025 by Jody Bruchon * Released under The MIT License */ #include #include #include "likely_unlikely.h" #include "libjodycode.h" #ifdef UNICODE #define WIN32_LEAN_AND_MEAN #include #include #endif /* Rename a file, converting for Windows if necessary */ int jc_rename(const char *oldpath, const char *newpath) { int retval; #ifdef UNICODE JC_WCHAR_T *wideold, *widenew; #endif if (unlikely(oldpath == NULL || newpath == NULL)) { jc_errno = EFAULT; return -1; } #ifdef UNICODE if (unlikely(jc_string_to_wstring(oldpath, &wideold) != 0 || jc_string_to_wstring(newpath, &widenew) != 0)) { jc_errno = ENOMEM; return -1; } retval = MoveFileW(wideold, widenew) ? 0 : -1; free(wideold); free(widenew); if (retval != 0) jc_errno = jc_GetLastError(); #else retval = rename(oldpath, newpath); if (retval != 0) jc_errno = errno; #endif return retval; } libjodycode/size_suffix.c000066400000000000000000000017711507704630200160610ustar00rootroot00000000000000/* libjodycode: list of data size suffixes and multipliers/binary shifts * * Copyright (C) 2023-2025 by Jody Bruchon * Released under The MIT License */ #include #include #include "libjodycode.h" const struct jc_size_suffix jc_size_suffix[] = { /* Binary suffixes */ { "B", 1, 0 }, { "K", 1024, 10 }, { "KiB", 1024, 10 }, { "M", 1048576, 20 }, { "MiB", 1048576, 20 }, { "G", (uint64_t)1048576 * 1024, 30 }, { "GiB", (uint64_t)1048576 * 1024, 30 }, { "T", (uint64_t)1048576 * 1048576, 40 }, { "TiB", (uint64_t)1048576 * 1048576, 40 }, { "P", (uint64_t)1048576 * 1048576 * 1024, 50}, { "PiB", (uint64_t)1048576 * 1048576 * 1024, 50}, { "E", (uint64_t)1048576 * 1048576 * 1048576, 60}, { "EiB", (uint64_t)1048576 * 1048576 * 1048576, 60}, /* Decimal suffixes */ { "KB", 1000, -1 }, { "MB", 1000000, -1 }, { "GB", 1000000000, -1 }, { "TB", 1000000000000, -1 }, { "PB", 1000000000000000, -1 }, { "EB", 1000000000000000000, -1 }, { NULL, 0, -1 }, }; libjodycode/skeleton/000077500000000000000000000000001507704630200151755ustar00rootroot00000000000000libjodycode/skeleton/.gitignore000066400000000000000000000006001507704630200171610ustar00rootroot00000000000000# # Build ignores # #.* *.o *.o.* *.obj *.exe *.a *.so *.so.* *.1.gz # # Never ignore these # !.gitignore # # Normal output and testing dirs # /a_ljc_program /a_ljc_program*.exe /*.pkg.tar.* /ljc_vercheck* # # Backups / patches # *~ *.orig *.rej /*.patch # # debugging and editor stuff # core .gdb_history .gdbinit .*.swp *.gcda *.gcno *.gcov cachegrind.out.* # Mac OS .DS_Store libjodycode/skeleton/Makefile000066400000000000000000000164061507704630200166440ustar00rootroot00000000000000# a_ljc_program Makefile # PROGRAM_NAME determines the installation name and manual page name PROGRAM_NAME = a_ljc_program # Default flags to pass to the C compiler (can be overridden) CFLAGS ?= -O2 -g #CFLAGS ?= -Og -g3 # PREFIX determines where files will be installed. Common examples # include "/usr" or "/usr/local". PREFIX = /usr/local # BIN_DIR indicates directory where program is to be installed. # Suggested value is "$(PREFIX)/bin" BIN_DIR = $(PREFIX)/bin # MAN_DIR indicates directory where the man page is to be # installed. Suggested value is "$(PREFIX)/man/man1" MAN_BASE_DIR = $(PREFIX)/share/man MAN_DIR = $(MAN_BASE_DIR)/man1 MAN_EXT = 1 # Required external tools CC ?= gcc INSTALL = install RM = rm -f RMDIR = rmdir -p MKDIR = mkdir -p INSTALL_PROGRAM = $(INSTALL) -m 0755 INSTALL_DATA = $(INSTALL) -m 0644 # Main object files OBJS += $(PROGRAM_NAME).o libjodycode_check.o # Configuration section COMPILER_OPTIONS = -Wall -Wwrite-strings -Wcast-align -Wstrict-aliasing -Wstrict-prototypes -Wpointer-arith -Wundef COMPILER_OPTIONS += -Wshadow -Wfloat-equal -Waggregate-return -Wcast-qual -Wswitch-default -Wswitch-enum -Wunreachable-code -Wformat=2 COMPILER_OPTIONS += -std=gnu11 -D_FILE_OFFSET_BITS=64 -fstrict-aliasing -pipe COMPILER_OPTIONS += -DNO_ATIME ifdef USE_LTO COMPILER_OPTIONS += -flto LINK_OPTIONS += -flto endif # Remove unused code if requested ifdef GC_SECTIONS COMPILER_OPTIONS += -fdata-sections -ffunction-sections LINK_OPTIONS += -Wl,--gc-sections endif UNAME_S=$(shell uname -s) # Are we running on a Windows OS? ifeq ($(OS), Windows_NT) ifndef NO_WINDOWS ON_WINDOWS=1 endif endif # Debugging code inclusion ifdef DEBUG COMPILER_OPTIONS += -DDEBUG else COMPILER_OPTIONS += -DNDEBUG endif ifdef HARDEN COMPILER_OPTIONS += -Wformat -Wformat-security -D_FORTIFY_SOURCE=2 -fstack-protector-strong -fPIE -fpie -Wl,-z,relro -Wl,-z,now else #COMPILER_OPTIONS += -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 endif # MinGW needs this for printf() conversions to work ifdef ON_WINDOWS ifndef NO_UNICODE UNICODE=1 COMPILER_OPTIONS += -municode endif SUFFIX=.exe SO_EXT=.dll LIB_EXT=.lib COMPILER_OPTIONS += -D__USE_MINGW_ANSI_STDIO=1 -DON_WINDOWS=1 ifeq ($(UNAME_S), MINGW32_NT-5.1) OBJS += winres_xp.o else OBJS += winres.o endif else SO_EXT=.so LIB_EXT=.a endif # Don't use unsupported compiler options on gcc 3/4 (Mac OS X 10.5.8 Xcode) # ENABLE_DEDUPE by default - macOS Sierra 10.12 and up required ifeq ($(UNAME_S), Darwin) GCCVERSION = $(shell expr `LC_ALL=C gcc -v 2>&1 | grep '[cn][cg] version' | sed 's/[^0-9]*//;s/[ .].*//'` \>= 5) else GCCVERSION = 1 BDYNAMIC = -Wl,-Bdynamic BSTATIC = -Wl,-Bstatic endif ifeq ($(GCCVERSION), 1) COMPILER_OPTIONS += -Wextra -Wstrict-overflow=5 -Winit-self endif # Stack size limit can be too small for deep directory trees, so set to 16 MiB # The ld syntax for Windows is the same for both Cygwin and MinGW ifndef LOW_MEMORY ifeq ($(OS), Windows_NT) COMPILER_OPTIONS += -Wl,--stack=16777216 else ifeq ($(UNAME_S), Darwin) COMPILER_OPTIONS += -Wl,-stack_size -Wl,0x1000000 else COMPILER_OPTIONS += -Wl,-z,stack-size=16777216 endif endif ### Find and use nearby libjodycode by default ifndef IGNORE_NEARBY_JC ifneq ("$(wildcard ../libjodycode/libjodycode.h)","") $(info Found and using nearby libjodycode at ../libjodycode) COMPILER_OPTIONS += -I../libjodycode -L../libjodycode ifeq ("$(wildcard ../libjodycode/version.o)","") $(error You must build libjodycode before building $(PROGRAM_NAME)) endif ifneq ("$(wildcard ../libjodycode/libjodycode.a)","") $(info Overriding static library extension .lib for found extension .a) LIB_EXT=.a endif STATIC_LDFLAGS += ../libjodycode/libjodycode$(LIB_EXT) DYN_LDFLAGS += -l:../libjodycode/libjodycode$(SO_EXT) else STATIC_LDFLAGS += -ljodycode DYN_LDFLAGS += -ljodycode endif else STATIC_LDFLAGS += -ljodycode DYN_LDFLAGS += -ljodycode endif # Don't build ljc_vercheck dynamically on Windows ifdef ON_WINDOWS LJC_DYNAMIC = $(BSTATIC) $(STATIC_LDFLAGS) $(info ) $(info Notice: Windows dynamic builds will build but silently fail) $(info without libjodycode.dll in the same directory.) $(info To avoid this, link statically: 'make static_jc') $(info ) else LJC_DYNAMIC = $(BDYNAMIC) $(DYN_LDFLAGS) endif CFLAGS += $(COMPILER_OPTIONS) $(CFLAGS_EXTRA) LDFLAGS += $(LINK_OPTIONS) $(LDFLAGS_EXTRA) all: libjodycode_hint $(PROGRAM_NAME) dynamic_jc ljc_vercheck_dynamic dynamic_jc: $(PROGRAM_NAME) ljc_vercheck_dynamic $(CC) $(CFLAGS) $(OBJS) $(LDFLAGS) $(BDYNAMIC) $(DYN_LDFLAGS) -o $(PROGRAM_NAME)$(SUFFIX) static_jc: $(PROGRAM_NAME) ljc_vercheck_static $(CC) $(CFLAGS) $(OBJS) $(LDFLAGS) $(BSTATIC) $(STATIC_LDFLAGS) $(BDYNAMIC) -o $(PROGRAM_NAME)$(SUFFIX) static: $(PROGRAM_NAME) ljc_vercheck_static $(CC) $(CFLAGS) $(OBJS) -static $(LDFLAGS) $(BSTATIC) $(STATIC_LDFLAGS) -o $(PROGRAM_NAME)$(SUFFIX) static_stripped: $(PROGRAM_NAME) static_jc -strip $(PROGRAM_NAME)$(SUFFIX) $(PROGRAM_NAME): $(OBJS) @: winres.o: winres.rc winres.manifest.xml ./tune_winres.sh windres winres.rc winres.o winres_xp.o: winres_xp.rc ./tune_winres.sh windres winres_xp.rc winres_xp.o installdirs: test -e $(DESTDIR)$(BIN_DIR) || $(MKDIR) $(DESTDIR)$(BIN_DIR) test -e $(DESTDIR)$(MAN_DIR) || $(MKDIR) $(DESTDIR)$(MAN_DIR) install: $(PROGRAM_NAME) installdirs $(INSTALL_PROGRAM) $(PROGRAM_NAME)$(SUFFIX) $(DESTDIR)$(BIN_DIR)/$(PROGRAM_NAME)$(SUFFIX) $(INSTALL_DATA) $(PROGRAM_NAME).1 $(DESTDIR)$(MAN_DIR)/$(PROGRAM_NAME).$(MAN_EXT) uninstalldirs: -test -e $(DESTDIR)$(BIN_DIR) && $(RMDIR) $(DESTDIR)$(BIN_DIR) -test -e $(DESTDIR)$(MAN_DIR) && $(RMDIR) $(DESTDIR)$(MAN_DIR) uninstall: uninstalldirs $(RM) $(DESTDIR)$(BIN_DIR)/$(PROGRAM_NAME)$(SUFFIX) $(RM) $(DESTDIR)$(MAN_DIR)/$(PROGRAM_NAME).$(MAN_EXT) test: ./test.sh stripped: $(PROGRAM_NAME) dynamic_jc strip $(PROGRAM_NAME)$(SUFFIX) clean: $(RM) $(OBJS) $(OBJS_CLEAN) $(PROGRAM_NAME)$(SUFFIX) ljc_vercheck$(SUFFIX) \ *~ .*.un~ *.gcno *.gcda *.gcov *.obj distclean: clean $(RM) -rf *.pkg.tar* $(PROGRAM_NAME)-*-*/ $(PROGRAM_NAME)-*-*.zip .PHONY: ljc_vercheck_static ljc_vercheck_dynamic ljc_vercheck_dynamic: $(CC) $(CFLAGS) $(CPPFLAGS) libjodycode_check.c -DSTANDALONE $(LDFLAGS) $(LJC_DYNAMIC) -o ljc_vercheck$(SUFFIX) @echo @./ljc_vercheck 2>/dev/null || echo "Version check failed. Install libjodycode on the system and try again." @if [ `(./ljc_vercheck$(SUFFIX) 2>/dev/null || echo ":0:0:0:0") | cut -d: -f4` -lt `grep MY_FEATURELEVEL_REQ libjodycode_check.h | cut -d" " -f3` ]; \ then echo "The linked libjodycode feature level is too old. Get the latest libjodycode and try again."; \ else echo "The linked libjodycode feature level is OK."; \ fi @echo ljc_vercheck_static: $(CC) $(CFLAGS) $(CPPFLAGS) libjodycode_check.c -DSTANDALONE $(LDFLAGS) $(BSTATIC) $(STATIC_LDFLAGS) $(BDYNAMIC) -o ljc_vercheck$(SUFFIX) @echo @./ljc_vercheck 2>/dev/null @if [ `(./ljc_vercheck$(SUFFIX) 2>/dev/null || echo ":0:0:0:0") | cut -d: -f4` -lt `grep MY_FEATURELEVEL_REQ libjodycode_check.h | cut -d" " -f3` ]; \ then echo "The linked libjodycode feature level is too old. Get the latest libjodycode and try again."; \ else echo "The linked libjodycode feature level is OK."; \ fi @echo libjodycode_hint: $(info hint: if ../libjodycode is built but the program won't run, try doing 'make static_jc') libjodycode/skeleton/README.txt000066400000000000000000000006301507704630200166720ustar00rootroot00000000000000This is a "skeleton" for building new projects against libjodycode. You can copy the entire directory somewhere, make a few modifications, and start coding immediately. You'll need to also do the following: - Rename every instance of "a_ljc_program" to your actual program name - Set the version number and date you actually want in version.h - Keep a built copy of libjodycode beside your project's folder libjodycode/skeleton/a_ljc_program.c000066400000000000000000000033351507704630200201440ustar00rootroot00000000000000/* a_ljc_program - insert a description here * Copyright (C) 2025 by Some Developer * Distributed under The MIT License */ #include #include #include "libjodycode_check.h" #include "version.h" /* Detect Windows and modify as needed */ #if defined _WIN32 || defined __CYGWIN__ #ifndef ON_WINDOWS #define ON_WINDOWS 1 #endif #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #include #else /* Not Windows */ #ifdef UNICODE #error Do not define UNICODE on non-Windows platforms. #undef UNICODE #endif #endif /* _WIN32 || __CYGWIN__ */ #ifdef UNICODE int wmain(int argc, wchar_t **wargv) #else int main(int argc, char **argv) #endif { int stdout_tty = 0; /* Insert main()-local variables here */ #ifdef ON_WINDOWS /* Windows buffers our std output; don't let it do that */ if (setvbuf(stdout, NULL, _IONBF, 0) != 0 || setvbuf(stderr, NULL, _IONBF, 0) != 0) fprintf(stderr, "warning: setvbuf() failed\n"); if (_isatty(_fileno(stdout))) stdout_tty = 1; #else if (isatty(fileno(stdout))) stdout_tty = 1; #endif /* ON_WINDOWS */ /* Verify libjodycode compatibility before going further */ if (libjodycode_version_check(1, 0) != 0) { /*** Insert version check failure actions here ***/ exit(EXIT_FAILURE); } /* On Windows, set up the terminal and un-wide argv */ #ifdef ON_WINDOWS #ifdef UNICODE static char **argv; int ut_err = jc_setup_unicode_terminal(argc, wargv, &argv, NULL); #else int ut_err = jc_setup_unicode_terminal(0, NULL, NULL, NULL); #endif /* UNICODE */ if (ut_err != 0) { jc_print_error(ut_err); exit(EXIT_FAILURE); } #endif /* ON_WINDOWS */ /***** INSERT YOUR PROGRAM HERE *****/ exit(EXIT_SUCCESS); } libjodycode/skeleton/libjodycode_check.c000066400000000000000000000076201507704630200207720ustar00rootroot00000000000000/* libjodycode version checks * * Code to embed the libjodycode version info and check against the currently * linked libjodycode to check for and report incompatibilities * Can also be compiled with -DSTANDALONE for version checking purposes * * Copyright (C) 2023-2025 by Jody Bruchon * Licensed under The MIT License */ #include #include #include #include "libjodycode_check.h" /* Set your required feature level in libjodycode_check.h */ const int jc_build_min_featurelevel = MY_FEATURELEVEL_REQ; #ifndef LIBJODYCODE_API_VERSION #error Your libjodycode version cannot be detected or is ancient. #error Compilation will fail. Update libjodycode and try again. #endif #if LIBJODYCODE_API_VERSION < 3 #define LIBJODYCODE_API_FEATURE_LEVEL LIBJODYCODE_API_VERSION #define jc_api_featurelevel LIBJODYCODE_API_VERSION #endif /* libjodycode < 3 is missing some Unicode stuff */ #ifndef LIBJODYCODE_WINDOWS_UNICODE #if PATHBUF_SIZE == 8192 /* PATHBUF_SIZE can be used as a proxy for Windows Unicode */ #define LIBJODYCODE_WINDOWS_UNICODE 1 #define jc_windows_unicode 1 #elif PATHBUF_SIZE == 4096 #define LIBJODYCODE_WINDOWS_UNICODE 0 #define jc_windows_unicode 0 #endif #endif const char *jc_build_version = LIBJODYCODE_VER; const int jc_build_api_version = LIBJODYCODE_API_VERSION; const int jc_build_api_featurelevel = LIBJODYCODE_API_FEATURE_LEVEL; #ifdef LIBJODYCODE_WINDOWS_UNICODE const int jc_build_windows_unicode = LIBJODYCODE_WINDOWS_UNICODE; #else #define jc_build_windows_unicode 1 #endif #ifdef JC_TEST #define JC_TEST_ONLY(a) a #else #define JC_TEST_ONLY(a) #endif int libjodycode_version_check(int verbose, int bail) { JC_TEST_ONLY(if (verbose > 1) fprintf(stderr, "libjodycode version check test code\n\n");) JC_TEST_ONLY(if (verbose > 1) goto incompatible_version;) if (jc_build_api_version != jc_api_version) goto incompatible_version; if (jc_build_min_featurelevel > jc_api_featurelevel) goto incompatible_version; #ifdef LIBJODYCODE_WINDOWS_UNICODE if (jc_build_windows_unicode != jc_windows_unicode) goto incompatible_version; #endif return 0; incompatible_version: if (verbose) { fprintf(stderr, "\n==============================================================================\n"); fprintf(stderr, "internal error: libjodycode on this system is an incompatible version\n\n"); fprintf(stderr, "Currently using libjodycode v%s, API %d, feature level %d\n", jc_version, jc_api_version, jc_api_featurelevel); fprintf(stderr, " Built against libjodycode v%s, API %d, feature level %d\n\n", jc_build_version, jc_build_api_version, jc_build_api_featurelevel); if (jc_windows_unicode != jc_build_windows_unicode) fprintf(stderr, "libjodycode was built with%s Windows Unicode but %sUnicode is required.\n\n", jc_windows_unicode == 1 ? "" : "out", jc_build_windows_unicode == 1 ? "" : "non-"); if (jc_build_min_featurelevel > jc_build_api_featurelevel) fprintf(stderr, "libjodycode feature level >= %d is required but linked library is level %d\n\n", jc_build_min_featurelevel, jc_build_api_featurelevel); fprintf(stderr, "==============================================================================\n\n"); fprintf(stderr, "\nUpdate libjodycode on your system and try again. If you continue to get this\n"); fprintf(stderr, "error, contact the package or distribution maintainer. If all else fails, send\n"); fprintf(stderr, "an email to jody@jodybruchon.com for help (but only as a last resort, please.)\n\n"); } if (bail) exit(EXIT_FAILURE); return 1; } #if defined (JC_TEST) || defined (STANDALONE) #ifdef UNICODE int wmain(void) #else int main(void) #endif { #ifdef JC_TEST libjodycode_version_check(2, 0); #else printf("libjodycode ver,api,featurelevel,reqlevel:%s:%d:%d:%d\n", jc_build_version, jc_build_api_version, jc_build_api_featurelevel, jc_build_min_featurelevel); #endif return 0; } #endif libjodycode/skeleton/libjodycode_check.h000066400000000000000000000011571507704630200207760ustar00rootroot00000000000000/* libjodycode version check headear * See libjodycode_check.c for license information */ #ifndef LIBJODYCODE_CHECK_H #define LIBJODYCODE_CHECK_H #ifdef __cplusplus extern "C" { #endif /* Set this to the minimum feature level required by your program */ #define MY_FEATURELEVEL_REQ 4 extern const int jc_build_api_major; extern const int jc_build_api_minor; extern const char *jc_build_version; extern const char *jc_build_featurelevel; extern const unsigned char jc_build_api_versiontable[]; extern int libjodycode_version_check(int verbose, int bail); #ifdef __cplusplus } #endif #endif /* LIBJODYCODE_CHECK_H */ libjodycode/skeleton/tune_winres.sh000066400000000000000000000021641507704630200200760ustar00rootroot00000000000000#!/bin/sh # Applies version.h version information to winres*.rc WINRES="winres.rc" WINRES_XP="winres_xp.rc" WINRES_MAN="winres.manifest.xml" # Get version number components VER="$(grep '^#define VER "' version.h | cut -d\" -f2)" V1="$(echo "$VER" | cut -d. -f1)"; test -z "$V1" && V1=0 V2="$(echo "$VER" | cut -d. -f2)"; test -z "$V2" && V2=0 V3="$(echo "$VER" | cut -d. -f3)"; test -z "$V3" && V3=0 V4="$(echo "$VER" | cut -d. -f4)"; test -z "$V4" && V4=0 # Build VS_VERSION_INFO product version string with commas PRODVER="$V1,$V2,$V3,$V4" # Extend version to include four discrete numbers XVER="$V1.$V2.$V3.$V4" echo "$VER = $PRODVER ($XVER)" # Actually change the manifest version information sed -i 's/\([A-Z]*\)VERSION [0-9],.*/\1VERSION '"$PRODVER/"';s/"\([A-Za-z]*\)Version", "[0-9],.*"/"\1Version", '"\"$PRODVER\"/" "$WINRES" sed -i 's/\([A-Z]*\)VERSION [0-9],.*/\1VERSION '"$PRODVER/"';s/"\([A-Za-z]*\)Version", "[0-9],.*"/"\1Version", '"\"$PRODVER\"/" "$WINRES_XP" sed -i 's/assemblyIdentity type="win32" name="a_ljc_program" version="[^"]*/assemblyIdentity type="win32" name="a_ljc_program" version="'$XVER/ "$WINRES_MAN" libjodycode/skeleton/version.h000066400000000000000000000004401507704630200170310ustar00rootroot00000000000000/* VERSION determines the program's version number * This file is part of a_ljc_program; see a_ljc_program.c for license information */ #ifndef A_LJC_PROGRAM_VERSION_H #define A_LJC_PROGRAM_VERSION_H #define VER "0.1" #define VERDATE "2025-11-01" #endif /* A_LJC_PROGRAM_VERSION_H */ libjodycode/skeleton/winres.manifest.xml000066400000000000000000000007031507704630200210330ustar00rootroot00000000000000 true libjodycode/skeleton/winres.rc000066400000000000000000000016501507704630200170340ustar00rootroot00000000000000#include "winver.h" 1 24 winres.manifest.xml //2 ICON icon/icon_a_ljc_program_all.ico VS_VERSION_INFO VERSIONINFO FILEVERSION 0,1,0,0 PRODUCTVERSION 0,1,0,0 FILEFLAGSMASK 0x3fL FILEFLAGS 0x0L FILEOS 0x40004L FILETYPE 0x1L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904b0" BEGIN VALUE "Comments", "(C) 2015-2025 Jody Bruchon , published under The MIT License" VALUE "CompanyName", "Jody Bruchon" VALUE "FileDescription", "a_ljc_program Duplicate File Finder Tool" VALUE "FileVersion", "0,1,0,0" VALUE "InternalName", "a_ljc_program" VALUE "LegalCopyright", "(C) 2015-2025 Jody Bruchon " VALUE "OriginalFilename", "a_ljc_program.exe" VALUE "ProductName", "a_ljc_program" VALUE "ProductVersion", "0,1,0,0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1200 END END libjodycode/skeleton/winres_xp.rc000066400000000000000000000016171507704630200175460ustar00rootroot00000000000000#include "winver.h" //2 ICON icon/icon_a_ljc_program_all.ico VS_VERSION_INFO VERSIONINFO FILEVERSION 0,1,0,0 PRODUCTVERSION 0,1,0,0 FILEFLAGSMASK 0x3fL FILEFLAGS 0x0L FILEOS 0x40004L FILETYPE 0x1L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904b0" BEGIN VALUE "Comments", "(C) 2015-2025 Jody Bruchon , published under The MIT License" VALUE "CompanyName", "Jody Bruchon" VALUE "FileDescription", "a_ljc_program Duplicate File Finder Tool" VALUE "FileVersion", "0,1,0,0" VALUE "InternalName", "a_ljc_program" VALUE "LegalCopyright", "(C) 2015-2025 Jody Bruchon " VALUE "OriginalFilename", "a_ljc_program.exe" VALUE "ProductName", "a_ljc_program" VALUE "ProductVersion", "0,1,0,0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1200 END END libjodycode/stat.c000066400000000000000000000043231507704630200144720ustar00rootroot00000000000000/* libjodycode: Windows-native code for getting stat()-like information * * Copyright (C) 2016-2025 by Jody Bruchon * Released under The MIT License */ #include #include #include #ifndef ON_WINDOWS #include #endif #include "likely_unlikely.h" #include "libjodycode.h" #ifdef ON_WINDOWS #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #endif /* Get stat()-like extra information for a file on Windows */ int jc_stat(const char *filename, struct JC_STAT *buf) { int retval; #ifdef ON_WINDOWS HANDLE hFile = INVALID_HANDLE_VALUE; BY_HANDLE_FILE_INFORMATION bhfi; #endif if (unlikely(!buf)) goto error_null_buffer; #ifdef ON_WINDOWS #ifdef UNICODE JC_WCHAR_T *widename; if (jc_string_to_wstring(filename, &widename) != 0) goto error_string; hFile = CreateFileW(widename, 0, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); free(widename); #else hFile = CreateFileA(filename, 0, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); #endif if (unlikely(hFile == INVALID_HANDLE_VALUE)) goto error_file; if (unlikely(GetFileInformationByHandle(hFile, &bhfi) == 0)) goto error_file; buf->st_ino = ((uint64_t)(bhfi.nFileIndexHigh) << 32) + (uint64_t)bhfi.nFileIndexLow; buf->st_size = ((int64_t)(bhfi.nFileSizeHigh) << 32) + (int64_t)bhfi.nFileSizeLow; if (unlikely(jc_nttime_to_unixtime(&(bhfi.ftCreationTime), &(buf->st_ctim)) != 0)) goto error_nttime; if (unlikely(jc_nttime_to_unixtime(&(bhfi.ftLastWriteTime), &(buf->st_mtim)) != 0)) goto error_nttime; if (unlikely(jc_nttime_to_unixtime(&(bhfi.ftLastAccessTime), &(buf->st_atim)) != 0)) goto error_nttime; buf->st_dev = (uint32_t)bhfi.dwVolumeSerialNumber; buf->st_nlink = (uint32_t)bhfi.nNumberOfLinks; buf->st_mode = (uint32_t)bhfi.dwFileAttributes; CloseHandle(hFile); retval = 0; #else retval = stat(filename, buf); if (retval != 0) jc_errno = errno; #endif return retval; #ifdef UNICODE error_string: #endif error_null_buffer: jc_errno = EFAULT; return -1; #ifdef ON_WINDOWS error_file: error_nttime: jc_errno = jc_GetLastError(); if (hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile); return -1; #endif } libjodycode/str_t.c000066400000000000000000000051321507704630200146510ustar00rootroot00000000000000/* libjodycode: JC_STR_T string functions * Copyright (C) 2015-2025 by Jody Bruchon * Released under The MIT License */ #include #include #include #include #include "libjodycode.h" #include "likely_unlikely.h" const char *jc_emptystring = '\0'; /* Convert a plain C string into a jc_str with size prefix */ JC_STR_T *jc_str_init(const char *string, uint32_t len) { JC_STR_T *jstr; if (unlikely(string == NULL || len == 0)) { len = 0; string = jc_emptystring; } else { if (len == 0) len = (uint32_t)strlen(string); if (len > UINT32_MAX) return NULL; } jstr = (JC_STR_T *)malloc(sizeof(JC_STR_T) + len + 1); if (jstr == NULL) return NULL; jstr->len = len; memcpy(jstr->str, string, len + 1); return jstr; } /* jc_streq() for JC_STR_T strings */ int jc_strteq(const JC_STR_T *s1, const JC_STR_T *s2) { if (s1->len == s2->len) return jc_streq(s1->str, s2->str); return 1; } /* jc_strneq() for JC_STR_T strings */ int jc_strtneq(const JC_STR_T *s1, const JC_STR_T *s2, const size_t len) { if (len >= s1->len && len >= s2->len) return jc_strteq(s1, s2); return jc_strneq(s1->str, s2->str, len); } /* jc_strtcaseeq() for JC_STR_T strings */ int jc_strtcaseeq(const JC_STR_T *s1, const JC_STR_T *s2) { if (s1->len == s2->len) return jc_strcaseeq(s1->str, s2->str); return 1; } /* jc_strncaseeq() for JC_STR_T strings */ int jc_strtncaseeq(const JC_STR_T *s1, const JC_STR_T *s2, const size_t len) { if (len >= s1->len && len >= s2->len) return jc_strtcaseeq(s1, s2); return jc_strncaseeq(s1->str, s2->str, len); } /* strcmp() for JC_STR_T strings */ int jc_strtcmp(const JC_STR_T *s1, const JC_STR_T *s2) { int cmp; size_t max = s1->len; if (s2->len < max) max = s2->len; cmp = memcmp(s1->str, s2->str, max); if (cmp == 0 && s1->len != s2->len) return s1->len > s2->len ? 1 : -1; return cmp; } int jc_strtncmp(const JC_STR_T *s1, const JC_STR_T *s2, size_t len) { int cmp; int limited = 0; size_t max = len; if (s1->len < max) { max = s1->len; limited = 1; } if (s2->len < max) { max = s2->len; limited = 1; } cmp = memcmp(s1->str, s2->str, max); if (cmp == 0 && limited == 1 && s1->len != s2->len) return s1->len > s2->len ? 1 : -1; return cmp; } int jc_strtcasecmp(const JC_STR_T *s1, const JC_STR_T *s2) { return strcasecmp(s1->str, s2->str); } int jc_strtncasecmp(const JC_STR_T *s1, const JC_STR_T *s2, size_t len) { return strncasecmp(s1->str, s2->str, len); } int jc_numeric_strtcmp(const JC_STR_T *c1, const JC_STR_T *c2, const int insensitive) { return jc_numeric_strcmp(c1->str, c2->str, insensitive); } libjodycode/string.c000066400000000000000000000035451507704630200150320ustar00rootroot00000000000000/* libjodycode: non-standard string functions * Copyright (C) 2015-2025 by Jody Bruchon * Released under The MIT License */ #include #include #include #include #include "libjodycode.h" #include "likely_unlikely.h" /* Like strcmp() but only tests for equality */ int jc_streq(const char *s1, const char *s2) { while (likely(*s1 != '\0' && *s2 != '\0')) { if (*s1 != *s2) return 1; s1++; s2++; } if (*s1 != *s2) return 1; return 0; } /* Like strncmp() but only tests for equality */ int jc_strneq(const char *s1, const char *s2, const size_t len) { size_t i = 0; while (likely(*s1 != '\0' && *s2 != '\0')) { if (*s1 != *s2) return 1; s1++; s2++; i++; if (i == len) return 0; } if (*s1 != *s2) return 1; return 0; } /* Like strcasecmp() but only tests for equality */ int jc_strcaseeq(const char *s1, const char *s2) { while (1) { if (likely(*s1 != *s2)) { unsigned char c1, c2; c1 = *(const unsigned char *)s1; c2 = *(const unsigned char *)s2; /* Transform upper case to lower case */ if (c1 == '\0' || c2 == '\0') return 1; if (c1 >= 'A' && c1 <= 'Z') c1 |= 0x20; if (c2 >= 'A' && c2 <= 'Z') c2 |= 0x20; if (c1 != c2) return 1; } else { if (*s1 == '\0') return 0; } s1++; s2++; } return 1; } /* Like strncasecmp() but only tests for equality */ int jc_strncaseeq(const char *s1, const char *s2, const size_t len) { size_t i = 0; while (i < len) { if (likely(*s1 != *s2)) { unsigned char c1, c2; c1 = *(const unsigned char *)s1; c2 = *(const unsigned char *)s2; /* Transform upper case to lower case */ if (c1 == '\0' || c2 == '\0') return 1; if (c1 >= 'A' && c1 <= 'Z') c1 |= 0x20; if (c2 >= 'A' && c2 <= 'Z') c2 |= 0x20; if (c1 != c2) return 1; } else { if (*s1 == '\0') return 0; } s1++; s2++; i++; } return 0; } libjodycode/test.sh000077500000000000000000000001321507704630200146630ustar00rootroot00000000000000#!/bin/sh # This is a dummy test script meant for automated builds to succeed. echo "OK" libjodycode/tests/000077500000000000000000000000001507704630200145135ustar00rootroot00000000000000libjodycode/tests/kernel_version.c000066400000000000000000000003121507704630200177000ustar00rootroot00000000000000#include #include "libjodycode.h" int main(void) { int ver = jc_get_kernel_version(); printf("Kernel version: "); if (ver < 0) printf("failed\n"); else printf("%d\n", ver); return 0; } libjodycode/tests/linkfiles_dedupe.c000066400000000000000000000034361507704630200201730ustar00rootroot00000000000000#include #include #include #include #include "libjodycode.h" int main(void) { struct jc_fileinfo_batch *batch; const int filecnt = 28; int i; batch = jc_fileinfo_batch_alloc(filecnt, 0, 3); if (batch == NULL) return -1; strcpy(batch->files[0].dirent->d_name, "a"); strcpy(batch->files[1].dirent->d_name, "b"); strcpy(batch->files[2].dirent->d_name, "c"); strcpy(batch->files[3].dirent->d_name, "d"); strcpy(batch->files[4].dirent->d_name, "e"); strcpy(batch->files[5].dirent->d_name, "f"); strcpy(batch->files[6].dirent->d_name, "g"); strcpy(batch->files[7].dirent->d_name, "h"); strcpy(batch->files[8].dirent->d_name, "i"); strcpy(batch->files[9].dirent->d_name, "j"); strcpy(batch->files[10].dirent->d_name, "k"); strcpy(batch->files[11].dirent->d_name, "l"); strcpy(batch->files[12].dirent->d_name, "m"); strcpy(batch->files[13].dirent->d_name, "n"); strcpy(batch->files[14].dirent->d_name, "o"); strcpy(batch->files[15].dirent->d_name, "p"); strcpy(batch->files[16].dirent->d_name, "q"); strcpy(batch->files[17].dirent->d_name, "r"); strcpy(batch->files[18].dirent->d_name, "s"); strcpy(batch->files[19].dirent->d_name, "t"); strcpy(batch->files[20].dirent->d_name, "u"); strcpy(batch->files[21].dirent->d_name, "v"); strcpy(batch->files[22].dirent->d_name, "w"); strcpy(batch->files[23].dirent->d_name, "x"); strcpy(batch->files[24].dirent->d_name, "y"); strcpy(batch->files[25].dirent->d_name, "z"); strcpy(batch->files[26].dirent->d_name, "0"); strcpy(batch->files[27].dirent->d_name, "1"); i = jc_linkfiles(batch, REFLINK); if (i != 0) { printf("failure: %d\n", i); for (int j = 0; j < filecnt; j++) printf("file %d: %d\n", j, batch->files[j].status); } else printf("success\n"); jc_fileinfo_batch_free(batch); return 0; } libjodycode/time.c000066400000000000000000000072221507704630200144560ustar00rootroot00000000000000/* libjodycode: datetime string to UNIX epoch conversion * * Copyright (C) 2020-2025 by Jody Bruchon * Released under The MIT License */ #include #include #include #include "likely_unlikely.h" #include "libjodycode.h" #define NTTIME_CONSTANT 116444736000000000 #define NTTIME_NSEC 10000000 #define ATONUM(a,b) (a = b - '0') static int twodigit_atoi(const char * restrict p) { int i, val; if (unlikely(*p < '0' || *p > '9')) return -1; ATONUM(i, *p); val = i * 10; p++; if (unlikely(*p < '0' || *p > '9')) return -1; ATONUM(i, *p); val += i; p++; return val; } /* Accepts date[time] strings "YYYY-MM-DD" or "YYYY-MM-DD HH:MM:SS" * and returns the number of seconds since the Unix Epoch a la mktime() * or returns -1 on any error */ time_t jc_strtoepoch(const char * const datetime) { time_t secs = 0; /* 1970-01-01 00:00:00 */ const char * restrict p = datetime; int i; struct tm tm; if (unlikely(datetime == NULL || *datetime == '\0')) goto error_null; memset(&tm, 0, sizeof(struct tm)); /* Process year */ i = twodigit_atoi(p); if (unlikely(i < 19)) goto error_datetime; tm.tm_year += i * 100; p += 2; i = twodigit_atoi(p); if (unlikely(i < 0)) goto error_datetime; tm.tm_year += i; tm.tm_year -= 1900; /* struct tm year is since 1900 */ p += 2; if (unlikely(*p != '-')) goto error_datetime; p++; /* Process month (0-11, not 1-12) */ i = twodigit_atoi(p); if (unlikely(i < 0 || i > 11)) goto error_datetime; tm.tm_mon = i - 1; p += 2; if (unlikely(*p != '-')) goto error_datetime; p++; /* Process day */ i = twodigit_atoi(p); if (unlikely(i < 1 || i > 31)) goto error_datetime; tm.tm_mday = i; p += 2; /* If YYYY-MM-DD is specified only, skip the time part */ if (*p == '\0') goto skip_time; if (unlikely(*p != ' ')) goto error_datetime; else p++; /* Process hours */ i = twodigit_atoi(p); if (unlikely(i < 0 || i > 23)) goto error_datetime; tm.tm_hour = i; p += 2; if (unlikely(*p != ':')) goto error_datetime; p++; /* Process minutes */ i = twodigit_atoi(p); if (unlikely(i < 0 || i > 59)) goto error_datetime; tm.tm_min = i; p += 2; if (unlikely(*p != ':')) goto error_datetime; p++; /* Process seconds */ i = twodigit_atoi(p); if (unlikely(i < 0 || i > 59)) goto error_datetime; tm.tm_sec = i; p += 2; /* Junk after datetime string should cause an error */ if (unlikely(*p != '\0')) goto error_datetime; skip_time: tm.tm_isdst = -1; /* Let the host library decide if DST is in effect */ errno = 0; secs = mktime(&tm); if (secs == -1) jc_errno = errno; return secs; error_null: jc_errno = JC_ENULL; return -1; error_datetime: jc_errno = JC_EDATETIME; return -1; } #ifdef ON_WINDOWS int jc_nttime_to_unixtime(const FILETIME * const restrict filetime, struct JC_TIMESPEC * const restrict unixtime) { uint64_t nttime; if (unlikely(filetime == NULL || unixtime == NULL)) return -1; nttime = ((uint64_t)(filetime->dwHighDateTime) << 32) + filetime->dwLowDateTime; if (unlikely(nttime <= NTTIME_CONSTANT)) return -1; unixtime->tv_sec = (time_t)((nttime - NTTIME_CONSTANT) / NTTIME_NSEC); unixtime->tv_nsec = (long)(((nttime - NTTIME_CONSTANT) % NTTIME_NSEC) * 100); return 0; } int jc_unixtime_to_nttime(const struct JC_TIMESPEC * const restrict unixtime, FILETIME * const restrict filetime) { uint64_t nttime; if (unlikely(filetime == NULL || unixtime == NULL)) return -1; nttime = (uint64_t)((unixtime->tv_sec * NTTIME_NSEC) + (unixtime->tv_nsec / 100) + NTTIME_CONSTANT); if (unlikely(nttime <= NTTIME_CONSTANT)) return -1; filetime->dwHighDateTime = (DWORD)(nttime >> 32); filetime->dwLowDateTime = (DWORD)nttime - filetime->dwHighDateTime; return 0; } #endif /* ON_WINDOWS */ libjodycode/tune_winres.sh000077500000000000000000000021131507704630200162470ustar00rootroot00000000000000#!/bin/sh WINRES="winres.rc" WINRES_XP="winres_xp.rc" WINRES_MAN="winres.manifest.xml" # Get version number components VER="$(grep '^#define LIBJODYCODE_VER *"' libjodycode.h | cut -d\" -f2)" V1="$(echo "$VER" | cut -d. -f1)"; test -z "$V1" && V1=0 V2="$(echo "$VER" | cut -d. -f2)"; test -z "$V2" && V2=0 V3="$(echo "$VER" | cut -d. -f3)"; test -z "$V3" && V3=0 V4="$(echo "$VER" | cut -d. -f4)"; test -z "$V4" && V4=0 # Build VS_VERSION_INFO product version string with commas PRODVER="$V1,$V2,$V3,$V4" # Extend version to include four discrete numbers XVER="$V1.$V2.$V3.$V4" echo "$VER = $PRODVER ($XVER)" # Actually change the manifest version information sed -i 's/\([A-Z]*\)VERSION [0-9],.*/\1VERSION '"$PRODVER/"';s/"\([A-Za-z]*\)Version", "[0-9],.*"/"\1Version", '"\"$PRODVER\"/" "$WINRES" sed -i 's/\([A-Z]*\)VERSION [0-9],.*/\1VERSION '"$PRODVER/"';s/"\([A-Za-z]*\)Version", "[0-9],.*"/"\1Version", '"\"$PRODVER\"/" "$WINRES_XP" sed -i 's/assemblyIdentity type="win32" name="libjodycode" version="[^"]*/assemblyIdentity type="win32" name="libjodycode" version="'$XVER/ "$WINRES_MAN" libjodycode/version.c000066400000000000000000000023731507704630200152070ustar00rootroot00000000000000/* libjodycode: versioning information * * Copyright (C) 2023-2025 by Jody Bruchon * Released under The MIT License */ #include "libjodycode.h" #ifdef __linux__ #include #include #include #endif /* Static version info constants embedded in the library */ const char *jc_version = LIBJODYCODE_VER; const char *jc_verdate = LIBJODYCODE_VERDATE; const int jc_api_version = LIBJODYCODE_API_VERSION; const int jc_api_featurelevel = LIBJODYCODE_API_FEATURE_LEVEL; const int jc_jodyhash_version = JODY_HASH_VERSION; const int jc_windows_unicode = LIBJODYCODE_WINDOWS_UNICODE; #ifdef __linux__ int jc_get_kernel_version(void) { const int mult[3] = { 1000000, 1000, 1 }; struct utsname utsname; char *p, *endptr; int i; static int kernel_version = -1; int m; if (kernel_version != -1) return kernel_version; if (uname(&utsname)) return -1; kernel_version = 0; i = 0; endptr = utsname.release; for (m = 0; m < 3; m++) { for (p = endptr; (*p < '0' || *p > '9') && *p != '\0'; p++) {} i = (int)strtol(p, &endptr, 10); if (i < 0 || i > 999) goto error_failed; kernel_version += i * mult[m]; } return kernel_version; error_failed: kernel_version = -1; return -1; } #endif /* __linux__ */ libjodycode/win_unicode.c000066400000000000000000000116531507704630200160260ustar00rootroot00000000000000/* libjodycode: Windows Unicode support utility code * * Copyright (C) 2014-2025 by Jody Bruchon * Released under The MIT License */ #include #include #include #include #include #include "likely_unlikely.h" #include "libjodycode.h" #ifdef ON_WINDOWS #define WIN32_LEAN_AND_MEAN #include #include #endif /* ON_WINDOWS */ #ifdef ON_WINDOWS /* Convert slashes to backslashes in a file path */ void jc_slash_convert(char *path) { while (*path != '\0') { if (*path == '/') *path = '\\'; path++; } return; } #endif #ifdef UNICODE /* Copy a string to a wide string - wstring must be freed by the caller */ int jc_string_to_wstring(const char * const restrict string, JC_WCHAR_T **wstring) { if (unlikely(wstring == NULL)) return JC_ENULL; *wstring = (JC_WCHAR_T *)malloc(JC_PATHBUF_SIZE + 4); if (unlikely(*wstring == NULL)) return JC_EALLOC; if (unlikely(!M2W(string, *wstring))) { free(*wstring); return JC_EMBWC; } return 0; } /* Copy Windows wide character arguments to UTF-8 */ int jc_widearg_to_argv(int argc, JC_WCHAR_T **wargv, char ***cargv) { char *temp; int len; char **new_argv; if (unlikely(cargv == NULL || wargv == NULL)) return JC_ENULL; new_argv = (char **)calloc(1, sizeof(char *) * (size_t)argc); if (new_argv == NULL) return JC_ENOMEM; temp = (char *)calloc(1, JC_PATHBUF_SIZE + 2); if (temp == NULL) return JC_ENOMEM; for (int counter = 0; counter < argc; counter++) { len = W2M_SIZED(wargv[counter], temp, JC_PATHBUF_SIZE); if (unlikely(len < 1)) { jc_errno = jc_GetLastError(); free(temp); return JC_EBADARGV; } new_argv[counter] = (char *)malloc((size_t)len + 2); if (unlikely(new_argv[counter] == NULL)) { free(temp); return JC_EALLOC; } strncpy_s(new_argv[counter], (size_t)len + 1, temp, _TRUNCATE); } /* fix up __argv so getopt etc. don't crash */ __argv = new_argv; *cargv = new_argv; return 0; } #endif /* UNICODE */ /* Set up a Unicode terminal on Windows and detect if stdout is a terminal * On non-Windows, only detects if stdout is TTY, other arguments ignored */ int jc_setup_unicode_terminal(int argc, JC_WCHAR_T **wargv, char ***argv, int *stdout_tty) { #ifdef ON_WINDOWS #ifndef UNICODE (void)argc; (void)wargv; (void)argv; #endif /* Windows buffers stderr; don't let it do that */ if (setvbuf(stderr, NULL, _IONBF, 0) != 0) return JC_ESETVBUF; if (stdout_tty != NULL &&_isatty(_fileno(stdout))) *stdout_tty = 1; #ifdef UNICODE int wa_err; if (wargv == NULL || argv == NULL) return JC_ENULL; /* Create a UTF-8 **argv from the wide version */ *argv = (char **)malloc(sizeof(char *) * (size_t)argc); if (!*argv) return JC_ENOMEM; wa_err = jc_widearg_to_argv(argc, wargv, argv); if (wa_err != 0) return wa_err; jc_set_output_modes(JC_MODE_UTF16_TTY, JC_MODE_UTF16_TTY); #endif /* UNICODE */ #else (void)argc; (void)wargv; (void)argv; if (stdout_tty != NULL && isatty(fileno(stdout))) *stdout_tty = 1; #endif /* ON_WINDOWS */ return 0; } #ifdef ON_WINDOWS /* Copy WIN32_FIND_FILE data to DIR data for a JC_DIR * The first call will allocate a JC_DIR and copy into it * Set hFind/ffd to NULL for subsequent calls on the same dirp */ int jc_ffd_to_dirent(JC_DIR **dirp, HANDLE hFind, WIN32_FIND_DATA *ffd) { #ifdef UNICODE char *tempname; #endif size_t len; if (unlikely(dirp == NULL)) goto error_null; if (hFind == NULL) { if (unlikely(*dirp == NULL)) goto error_null; ffd = &((*dirp)->ffd); } #ifdef UNICODE /* Must count bytes after conversion to allocate correct size */ tempname = (char *)malloc(JC_PATHBUF_SIZE + 4); if (unlikely(tempname == NULL)) goto error_nomem; if (unlikely(!W2M(ffd->cFileName, tempname))) goto error_name; len = strlen(tempname) + 1; *dirp = (JC_DIR *)calloc(1, sizeof(JC_DIR) + len); if (unlikely(*dirp == NULL)) goto error_nomem; strcpy_s((*dirp)->dirent.d_name, len, tempname); free(tempname); #else len = strlen(ffd->cFileName) + 1; *dirp = (JC_DIR *)calloc(1, sizeof(JC_DIR) + len); if (unlikely(*dirp == NULL)) goto error_nomem; strcpy_s((*dirp)->dirent.d_name, len, ffd->cFileName); #endif /* (*dirp)->dirent.ino = 0; // implicit via calloc() - Windows Find*File() doesn't return inode numbers */ (*dirp)->dirent.d_namlen = (uint32_t)len; if (unlikely(ffd->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) (*dirp)->dirent.d_type = JC_DT_LNK; else if (ffd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) (*dirp)->dirent.d_type = JC_DT_DIR; else (*dirp)->dirent.d_type = JC_DT_REG; /* First call: init ffd/hFind + mark cached FindFirstFile() dirent */ if (hFind != NULL) { memcpy(&((*dirp)->ffd), ffd, sizeof(WIN32_FIND_DATA)); (*dirp)->hFind = hFind; (*dirp)->cached = 1; } return 0; #ifdef UNICODE error_name: jc_errno = jc_GetLastError(); #endif error_nomem: #ifdef UNICODE if (tempname != NULL) free(tempname); #endif jc_errno = ENOMEM; return -1; error_null: jc_errno = EFAULT; return -1; } #endif /* ON_WINDOWS */ libjodycode/winres.manifest.xml000066400000000000000000000007011507704630200172050ustar00rootroot00000000000000 true libjodycode/winres.rc000066400000000000000000000015531507704630200152120ustar00rootroot00000000000000#include "winver.h" 1 24 winres.manifest.xml VS_VERSION_INFO VERSIONINFO FILEVERSION 4,1,1,0 PRODUCTVERSION 4,1,1,0 FILEFLAGSMASK 0x3fL FILEFLAGS 0x0L FILEOS 0x40004L FILETYPE 0x1L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904b0" BEGIN VALUE "Comments", "(C) 2014-2025 Jody Bruchon , published under The MIT License" VALUE "CompanyName", "Jody Bruchon" VALUE "FileDescription", "libjodycode C Code Library" VALUE "FileVersion", "4,1,1,0" VALUE "InternalName", "libjodycode" VALUE "LegalCopyright", "(C) 2014-2025 Jody Bruchon " VALUE "OriginalFilename", "libjodycode.dll" VALUE "ProductName", "libjodycode" VALUE "ProductVersion", "4,1,1,0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1200 END END libjodycode/winres_xp.rc000066400000000000000000000015211507704630200157140ustar00rootroot00000000000000#include "winver.h" VS_VERSION_INFO VERSIONINFO FILEVERSION 4,1,1,0 PRODUCTVERSION 4,1,1,0 FILEFLAGSMASK 0x3fL FILEFLAGS 0x0L FILEOS 0x40004L FILETYPE 0x1L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904b0" BEGIN VALUE "Comments", "(C) 2014-2025 Jody Bruchon , published under The MIT License" VALUE "CompanyName", "Jody Bruchon" VALUE "FileDescription", "libjodycode C Code Library" VALUE "FileVersion", "4,1,1,0" VALUE "InternalName", "libjodycode" VALUE "LegalCopyright", "(C) 2014-2025 Jody Bruchon " VALUE "OriginalFilename", "libjodycode.dll" VALUE "ProductName", "libjodycode" VALUE "ProductVersion", "4,1,1,0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1200 END END