pax_global_header00006660000000000000000000000064147664446240014533gustar00rootroot0000000000000052 comment=f09960ba8dd92286703daae02c4ab9d3f319e0c5
libjodycode/000077500000000000000000000000001476644462400133665ustar00rootroot00000000000000libjodycode/.gitignore000066400000000000000000000006411476644462400153570ustar00rootroot00000000000000#
# Build ignores
#
#.*
*.o
*.o.*
*.a
*.so*
*.dll*
*.exe
apiver
cacheinfo
vercheck
*.7.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.txt000066400000000000000000000026551476644462400152070ustar00rootroot00000000000000libjodycode 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/FEATURELEVELS.txt000066400000000000000000000012541476644462400161370ustar00rootroot00000000000000# alarm
jc_alarm_ring:2
jc_start_alarm:2
jc_stop_alarm:2
# cacheinfo
struct jc_proc_cacheinfo:1
jc_get_proc_cacheinfo:1
# error
jc_get_errdesc:1
jc_get_errname:1
jc_print_error:1
# jody_hash
jc_block_hash:1
# oom
jc_nullptr:1
jc_oom:1
# paths
jc_collapse_dotdot:1
jc_slash_convert:1
# size_suffix
struct jc_size_suffix:1
# string
jc_strcaseeq:1
jc_streq:1
jc_strncaseeq:1
jc_strneq:1
# strtoepoch
jc_strtoepoch:1
# version
jc_version:1
jc_verdate:1
jc_api_versiontable:1
jc_api_version:1
jc_jodyhash_version:1
# win_stat
struct jc_winstat:1
jc_win_stat:1
jc_nttime_to_unixtime:1
jc_unixtime_to_nttime:1
# win_unicode
jc_set_output_modes:1
jc_fwprint:1
jc_widearg_to_argv:1
libjodycode/LICENSE.txt000066400000000000000000000021141476644462400152070ustar00rootroot00000000000000MIT 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/Makefile000066400000000000000000000164461476644462400150410ustar00rootroot00000000000000# 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
MAN7_DIR ?= $(MAN_BASE_DIR)/man7
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
API_VERSION = $(shell grep -m 1 '^.define LIBJODYCODE_VER ' libjodycode.h | sed 's/[^"]*"//;s/\..*//')
# 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 += -std=gnu11 -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/\..*//')
CROSS_DETECT = $(shell true | $(CC) -dM -E - | grep -m 1 __x86_64 || echo "cross")
ifeq ($(UNAME_S), Darwin)
LINK_OPTIONS += -Wl,-install_name,$(PROGRAM_NAME).$(SO_SUFFIX).$(API_VERSION)
# 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_SUFFIX).$(API_VERSION)
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
# Are we running on a Windows OS?
ifeq ($(OS), Windows_NT)
ifndef NO_WINDOWS
ON_WINDOWS=1
SO_SUFFIX=dll
endif
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 -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 += alarm.o cacheinfo.o error.o jc_block_hash.o jody_hash.o
OBJS += oom.o paths.o size_suffix.o sort.o string.o
OBJS += strtoepoch.o version.o win_stat.o win_unicode.o
OBJS += $(ADDITIONAL_OBJECTS)
all: 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_SUFFIX).$(VERSION) $(OBJS) $(SIMD_OBJS) $(LDFLAGS) $(CFLAGS) $(CFLAGS_EXTRA)
$(LN) $(PROGRAM_NAME).$(SO_SUFFIX).$(VERSION) $(PROGRAM_NAME).$(SO_SUFFIX).$(VERSION_MAJOR)
$(LN) $(PROGRAM_NAME).$(SO_SUFFIX).$(VERSION_MAJOR) $(PROGRAM_NAME).$(SO_SUFFIX)
staticlib: $(OBJS) $(SIMD_OBJS)
$(AR) rcs libjodycode.a $(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:
$(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
.c.o:
$(CC) -c $(COMPILER_OPTIONS) $(CFLAGS) $(CPPFLAGS) $< -o $@
#manual:
# gzip -9 < jodycode.8 > jodycode.8.gz
$(PROGRAM_NAME): jodyhash $(OBJS)
# $(CC) $(CFLAGS) $(LDFLAGS) -o $(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)$(LIB_DIR) || $(MKDIR) $(DESTDIR)$(LIB_DIR)
test -e $(DESTDIR)$(INC_DIR) || $(MKDIR) $(DESTDIR)$(INC_DIR)
test -e $(DESTDIR)$(MAN7_DIR) || $(MKDIR) $(DESTDIR)$(MAN7_DIR)
installfiles: installdirs
$(INSTALL_PROGRAM) $(PROGRAM_NAME).$(SO_SUFFIX).$(VERSION) $(DESTDIR)$(LIB_DIR)/$(PROGRAM_NAME).$(SO_SUFFIX).$(VERSION)
$(LN) $(PROGRAM_NAME).$(SO_SUFFIX).$(VERSION) $(DESTDIR)$(LIB_DIR)/$(PROGRAM_NAME).$(SO_SUFFIX).$(VERSION_MAJOR)
$(LN) $(PROGRAM_NAME).$(SO_SUFFIX).$(VERSION_MAJOR) $(DESTDIR)$(LIB_DIR)/$(PROGRAM_NAME).$(SO_SUFFIX)
$(INSTALL_DATA) $(PROGRAM_NAME).a $(DESTDIR)$(LIB_DIR)/$(PROGRAM_NAME).a
$(INSTALL_DATA) $(PROGRAM_NAME).h $(DESTDIR)$(INC_DIR)/$(PROGRAM_NAME).h
$(INSTALL_DATA) $(PROGRAM_NAME).7 $(DESTDIR)$(MAN7_DIR)/$(PROGRAM_NAME).7
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)$(MAN7_DIR) && $(RMDIR) $(DESTDIR)$(MAN7_DIR)
uninstallfiles:
$(RM) $(DESTDIR)$(LIB_DIR)/$(PROGRAM_NAME).$(SO_SUFFIX).$(VERSION)
$(RM) $(DESTDIR)$(LIB_DIR)/$(PROGRAM_NAME).$(SO_SUFFIX).$(VERSION_MAJOR)
$(RM) $(DESTDIR)$(LIB_DIR)/$(PROGRAM_NAME).$(SO_SUFFIX)
$(RM) $(DESTDIR)$(LIB_DIR)/$(PROGRAM_NAME).a
$(RM) $(DESTDIR)$(INC_DIR)/$(PROGRAM_NAME).h
$(RM) $(DESTDIR)$(MAN7_DIR)/$(PROGRAM_NAME).7
uninstall: uninstallfiles uninstalldirs
test:
./test.sh
stripped: sharedlib staticlib
$(STRIP_UNNEEDED) libjodycode.$(SO_SUFFIX)
$(STRIP_DEBUG) libjodycode.a
objsclean:
$(RM) $(OBJS) $(SIMD_OBJS) vercheck.o
clean: objsclean
$(RM) $(PROGRAM_NAME).$(SO_SUFFIX) $(PROGRAM_NAME).$(SO_SUFFIX).$(VERSION_MAJOR) $(PROGRAM_NAME).$(SO_SUFFIX).$(VERSION)
$(RM) apiver cacheinfo vercheck
$(RM) *.a *~ helper_code/*~ 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.md000066400000000000000000000071161476644462400146520ustar00rootroot00000000000000Introduction
-------------------------------------------------------------------------------
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
-------------------------------------------------------------------------------
In libjodycode 2.0 a new version table was introduced that maintains a separate
version number for each logical section of the library. If something in the
library changes in a way that's no longer compatible with previous versions,
this version table paired with the provided "libjodycode_check.c/.h" files will
allow the linked program to verify that the sections of the library it actually
uses have not changed, ignoring the rest. This has a significant advantage over
the simpler whole-API version system because the program itself can detect if
the library it's linked to is still compatible enough to safely continue.
The provided version check code reports detailed information about the problem
in a way that is both understandable by users and informative to maintainers.
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.
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/alarm.c000066400000000000000000000033711476644462400146320ustar00rootroot00000000000000/* Cross-platform alarms
*
* Copyright (C) 2025 by Jody Bruchon
* Released under The MIT License
*/
#ifdef ON_WINDOWS
#define _WIN32_WINNT 0x0500
#define WIN32_LEAN_AND_MEAN
#include
#endif
#include
#include
#include
#include
#include
#include "libjodycode.h"
#include "likely_unlikely.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;
}
extern 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))
return -8;
jc_alarm_ring++;
return 0;
}
extern int jc_stop_alarm(void)
{
if (CloseHandle(hTimer) == 0) return -8;
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;
}
extern 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) return -8;
alarm(seconds);
return 0;
}
extern 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) return -8;
return 0;
}
#endif /* ON_WINDOWS */
libjodycode/cacheinfo.c000066400000000000000000000056441476644462400154620ustar00rootroot00000000000000/* Detect and report size of CPU caches
*
* 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 "likely_unlikely.h"
#include "libjodycode.h"
/* 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);
return i;
}
extern void jc_get_proc_cacheinfo(struct jc_proc_cacheinfo *pci)
{
char *idx;
size_t i;
size_t size;
int level;
char type;
char index;
if (unlikely(pci == NULL)) return;
memset(pci, 0, sizeof(struct jc_proc_cacheinfo));
i = strlen(path);
if (i > 48) return;
idx = path + i;
pathidx = idx + 1;
*pathidx = '/'; pathidx++;
for (index = '0'; index < '9'; index++) {
*idx = index;
/* Get the level for this index */
if (read_procfile("level") == 0) break;
if (*buf < '1' || *buf > '3') break;
else level = (*buf) + 1 - '1';
/* Get the size */
if (read_procfile("size") == 0) break;
size = (size_t)atoi(buf) * 1024;
if (size == 0) break;
/* Get the type */
if (read_procfile("type") == 0) break;
if (*buf != 'U' && *buf != 'I' && *buf != 'D') break;
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: return;
};
break;
case 'I':
switch (level) {
case 1: pci->l1i = size; break;
case 2: pci->l2i = size; break;
case 3: pci->l3i = size; break;
default: return;
};
break;
case 'U':
switch (level) {
case 1: pci->l1 = size; break;
case 2: pci->l2 = size; break;
case 3: pci->l3 = size; break;
default: return;
};
break;
default: return;
}
/* Continue to next index */
}
return;
}
#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.sh000077500000000000000000000045231476644462400164060ustar00rootroot00000000000000#!/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/error.c000066400000000000000000000032051476644462400146630ustar00rootroot00000000000000/* libjodycode error strings and printing functions
*
* Copyright (C) 2025 by Jody Bruchon
* Released under The MIT License
*/
#include
#include "libjodycode.h"
struct jc_error {
const char *name;
const char *desc;
};
#define JC_ERRCNT 9
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_param", "get_relative_name has NULL parameter" }, // 1
{ "getcwd", "couldn't get the current directory" }, // 2
{ "cdotdot", "jc_collapse_dotdot() call failed" }, // 3
{ "grn_dir_end", "get_relative_name() result has directory at end" }, // 4
{ "bad_errnum", "invalid error number" }, // 5
{ "bad_argv", "bad argv pointer" }, // 6
{ "wc2mb_fail", "WideCharToMultiByte() failed" }, // 7
{ "alarm_fail", "alarm call failed" }, // 8
{ NULL, NULL }, // 9
};
extern const char *jc_get_errname(int errnum)
{
if (errnum > errcnt) return NULL;
if (errnum < 0) errnum = -errnum;
return jc_error_list[errnum].name;
}
extern const char *jc_get_errdesc(int errnum)
{
if (errnum > errcnt) return NULL;
if (errnum < 0) errnum = -errnum;
return jc_error_list[errnum].desc;
}
extern int jc_print_error(int errnum)
{
if (errnum > errcnt) return -5;
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)
{
static 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/generate_packages.sh000077500000000000000000000050221476644462400173540ustar00rootroot00000000000000#!/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/helper_code/000077500000000000000000000000001476644462400156375ustar00rootroot00000000000000libjodycode/helper_code/libjodycode_apiver.c000066400000000000000000000021061476644462400216370ustar00rootroot00000000000000/* 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("API: %d\n", LIBJODYCODE_API_VERSION);
printf("FEATURE: %d\n", LIBJODYCODE_API_FEATURE_LEVEL);
printf("VER: %s\n", LIBJODYCODE_VER);
printf("VERDATE: %s\n", LIBJODYCODE_VERDATE);
printf("CACHEINFO: %d\n", LIBJODYCODE_CACHEINFO_VER);
printf("JODY_HASH: %d\n", LIBJODYCODE_JODY_HASH_VER);
printf("OOM: %d\n", LIBJODYCODE_OOM_VER);
printf("PATHS: %d\n", LIBJODYCODE_PATHS_VER);
printf("SIZE_SUFFIX: %d\n", LIBJODYCODE_SIZE_SUFFIX_VER);
printf("SORT: %d\n", LIBJODYCODE_SORT_VER);
printf("STRING: %d\n", LIBJODYCODE_STRING_VER);
printf("STRTOEPOCH: %d\n", LIBJODYCODE_STRTOEPOCH_VER);
printf("WIN_STAT: %d\n", LIBJODYCODE_WIN_STAT_VER);
printf("WIN_UNICODE: %d\n", LIBJODYCODE_WIN_UNICODE_VER);
printf("ERROR: %d\n", LIBJODYCODE_ERROR_VER);
printf("ALARM: %d\n", LIBJODYCODE_ALARM_VER);
return 0;
}
libjodycode/helper_code/libjodycode_check.c000066400000000000000000000114561476644462400214360ustar00rootroot00000000000000/* libjodycode version checks
*
* Code to embed the libjodycode version info and check against the currently
* linked libjodycode to check for and report incompatibilities
*
* Copyright (C) 2023 by Jody Bruchon
* Licensed under The MIT License */
#include
#include
#include
#include "libjodycode_check.h"
#include "libjodycode_check_defs.h"
#ifdef JC_TEST
#define JC_TEST_ONLY(a) a
#else
#define JC_TEST_ONLY(a)
#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;
const int jc_build_min_featurelevel = MY_FEATURELEVEL_REQ;
/* API sub-version info array, terminated with 255
* The user-defined part has moved to libjodycode_check_defs.h
* Do not edit this file, edit that one instead! */
/* Build the array data using user definitions */
#if MY_CACHEINFO_REQ == 255
#undef MY_CACHEINFO_REQ
#define MY_CACHEINFO_REQ LIBJODYCODE_CACHEINFO_VER
#endif
#if MY_JODY_HASH_REQ == 255
#undef MY_JODY_HASH_REQ
#define MY_JODY_HASH_REQ LIBJODYCODE_JODY_HASH_VER
#endif
#if MY_OOM_REQ == 255
#undef MY_OOM_REQ
#define MY_OOM_REQ LIBJODYCODE_OOM_VER
#endif
#if MY_PATHS_REQ == 255
#undef MY_PATHS_REQ
#define MY_PATHS_REQ LIBJODYCODE_PATHS_VER
#endif
#if MY_SIZE_SUFFIX_REQ == 255
#undef MY_SIZE_SUFFIX_REQ
#define MY_SIZE_SUFFIX_REQ LIBJODYCODE_SIZE_SUFFIX_VER
#endif
#if MY_SORT_REQ == 255
#undef MY_SORT_REQ
#define MY_SORT_REQ LIBJODYCODE_SORT_VER
#endif
#if MY_STRING_REQ == 255
#undef MY_STRING_REQ
#define MY_STRING_REQ LIBJODYCODE_STRING_VER
#endif
#if MY_STRTOEPOCH_REQ == 255
#undef MY_STRTOEPOCH_REQ
#define MY_STRTOEPOCH_REQ LIBJODYCODE_STRTOEPOCH_VER
#endif
#if MY_WIN_STAT_REQ == 255
#undef MY_WIN_STAT_REQ
#define MY_WIN_STAT_REQ LIBJODYCODE_WIN_STAT_VER
#endif
#if MY_WIN_UNICODE_REQ == 255
#undef MY_WIN_UNICODE_REQ
#define MY_WIN_UNICODE_REQ LIBJODYCODE_WIN_UNICODE_VER
#endif
#if MY_ERROR_REQ == 255
#undef MY_ERROR_REQ
#define MY_ERROR_REQ LIBJODYCODE_ERROR_VER
#endif
#if MY_ALARM_REQ == 255
#undef MY_ALARM_REQ
#define MY_ALARM_REQ LIBJODYCODE_ALARM_VER
#endif
const unsigned char jc_build_api_versiontable[] = {
MY_CACHEINFO_REQ,
MY_JODY_HASH_REQ,
MY_OOM_REQ,
MY_PATHS_REQ,
MY_SIZE_SUFFIX_REQ,
MY_SORT_REQ,
MY_STRING_REQ,
MY_STRTOEPOCH_REQ,
MY_WIN_STAT_REQ,
MY_WIN_UNICODE_REQ,
MY_ERROR_REQ,
MY_ALARM_REQ,
255
};
const char *jc_versiontable_section[] = {
"cacheinfo",
"jody_hash",
"oom",
"paths",
"size_suffix",
"sort",
"string",
"strtoepoch",
"win_stat",
"win_unicode",
"error",
"alarm",
NULL
};
int libjodycode_version_check(int verbose, int bail)
{
const unsigned char * const restrict build = jc_build_api_versiontable;
const unsigned char * const restrict lib = jc_api_versiontable;
int i = 0;
JC_TEST_ONLY(if (verbose > 1) fprintf(stderr, "libjodycode version check test code\n\n");)
/* Force a version dump if requested */
while (build[i] != 255) {
JC_TEST_ONLY(if (verbose > 1) fprintf(stderr, "API %d: %s: builtin ver %u, lib ver %u\n", i, jc_versiontable_section[i], build[i], lib[i]);)
if (build[i] != 0 && (lib[i] == 0 || build[i] != lib[i])) goto incompatible_versiontable;
i++;
}
JC_TEST_ONLY(if (verbose > 1) goto incompatible_versiontable;)
return 0;
incompatible_versiontable:
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_build_min_featurelevel > jc_build_api_featurelevel)
fprintf(stderr, "libjodycode feature level %d is required but linked library is level %d\n",
jc_build_min_featurelevel, jc_build_api_featurelevel);
if (lib[i] == 0) fprintf(stderr, "API sections are missing in libjodycode; it's probably too old.\n");
else fprintf(stderr, "The first incompatible API section found is '%s' (want v%d, got v%d).\n",
jc_versiontable_section[i], build[i], lib[i]);
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;
}
#ifdef JC_TEST
int main(void)
{
libjodycode_version_check(2, 0);
return 0;
}
#endif
libjodycode/helper_code/libjodycode_check.h000066400000000000000000000010131476644462400214270ustar00rootroot00000000000000/* libjodycode version check headear
* See libjodycode_check.c for license information */
#ifndef LIBJODYCODE_CHECK_H
#define LIBJODYCODE_CHECK_H
#ifdef __cplusplus
extern "C" {
#endif
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/libjodycode_check_defs.h000066400000000000000000000030031476644462400224310ustar00rootroot00000000000000/* libjodycode version checks - user-defined requirements
*
* Edit this file to match your libjodycode API/feature level requirements
*
* Copyright (C) 2023 by Jody Bruchon
* Licensed under The MIT License */
/* Minimum libjodycode feature level required
* You can copy this from the current libjodycode feature level; however,
* the ideal number is the lowest number that is still compatible. For
* example: in level 1 the alarm API sets the "ring" to 1 like a flag
* while the level 2 alarm API increments "ring" when triggered to tell
* the application how many alarms have been triggered since reset. For
* applications that don't care about the number of alarms or that work
* properly without that info, level 1 minimum is fine; those that rely
* on the newer behavior must specify a minimum level of 2. */
#define MY_FEATURELEVEL_REQ 1
/* API sub-version requirements
* For any libjodycode API you use, copy its number from libjodycode.h to
* this list.
* To indicate you don't use an API, set it to 0.
* To auto-fill the API numbers you're building against, set it to 255.
* Any number not matching libjodycode will cause an error exit. */
#define MY_CACHEINFO_REQ 0
#define MY_JODY_HASH_REQ 0
#define MY_OOM_REQ 0
#define MY_PATHS_REQ 0
#define MY_SIZE_SUFFIX_REQ 0
#define MY_SORT_REQ 0
#define MY_STRING_REQ 0
#define MY_STRTOEPOCH_REQ 0
#define MY_WIN_STAT_REQ 0
#define MY_WIN_UNICODE_REQ 0
#define MY_ERROR_REQ 0
#define MY_ALARM_REQ 0
libjodycode/helper_code/makefile_nearby.txt000066400000000000000000000025061476644462400215200ustar00rootroot00000000000000For 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_block_hash.c000066400000000000000000000005161476644462400163050ustar00rootroot00000000000000/* Wrapper stub for jody_hash
*
* Copyright (C) 2025 by Jody Bruchon
* Released under The MIT License
*/
#include
#include "libjodycode.h"
#include "jody_hash.h"
extern int jc_block_hash(jodyhash_t *data, jodyhash_t *hash, const size_t count)
{
return jody_block_hash(data, hash, count);
}
libjodycode/jody_hash.c000066400000000000000000000073351476644462400155120ustar00rootroot00000000000000/* 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.h000066400000000000000000000063001476644462400155060ustar00rootroot00000000000000/* 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.c000066400000000000000000000054121476644462400164440ustar00rootroot00000000000000/* 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.c000066400000000000000000000015201476644462400165140ustar00rootroot00000000000000/* 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.h000066400000000000000000000031641476644462400165270ustar00rootroot00000000000000/* 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 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.c000066400000000000000000000071451476644462400164450ustar00rootroot00000000000000/* 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.7000066400000000000000000000112311476644462400157430ustar00rootroot00000000000000.\" Copyright (C) 2025 by Jody Bruchon
.TH "LIBJODYCODE" "7" "2025-03-08" "3.1" "libjodycode"
.SH NAME
libjodycode \- shared code used by several tools written by Jody Bruchon
.SH SYNOPSIS
.B #include
.SS "Alarm API"
.nf
.BI "int jc_alarm_ring"
.BI "int jc_start_alarm(const unsigned int " seconds ", const int " repeat ")"
.BI "int jc_stop_alarm(void)"
.SS "Cacheinfo API"
.nf
.BI "void jc_get_proc_cacheinfo(struct jc_proc_cacheinfo *" pci ")"
.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 ")"
.SS "jodyhash API"
.nf
.BI "int jc_block_hash(jodyhash_t *" data ", jodyhash_t *" hash ", const size_t " count ")"
.SS "OOM (out-of-memory) API"
.nf
.BI "void jc_oom(const char * const restrict " msg ")"
.BI "void jc_nullptr(const char * restrict " func ")"a
.SS "Paths 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 "Sort API"
.nf
.BI "int jc_numeric_sort(char * restrict " c1 ", char * restrict " c2 ", int " sort_direction ")"
.SS "String-to-epoch API"
.nf
.BI "time_t jc_strtoepoch(const char * const " datetime ")"
.SS "String API"
.nf
.BI "int jc_strncaseeq(const char *" s1 ", const char *" s2 ", size_t " len ")"
.BI "int jc_strcaseeq(const char *" s1 ", const char *" s2 ")"
.BI "int jc_strneq(const char *" s1 ", const char *" s2 ", size_t " len ")"
.BI "int jc_streq(const char *" s1 ", const char *" s2 ")"
.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[]"
.SS "Windows stat() API"
.nf
.BI "time_t jc_nttime_to_unixtime(const uint64_t * const restrict " timestamp ")"
.BI "time_t jc_unixtime_to_nttime(const uint64_t * const restrict " timestamp ")"
.BI "int jc_win_stat(const char * const " filename ", struct jc_winstat * const restrict " buf ")"
.SS "Windows Unicode API"
.nf
.BI "int jc_fwprint(FILE * const restrict " stream ", const char * const restrict " str ", const int " cr ")"
.BI "void jc_slash_convert(char *" path ")"
.BI "void jc_set_output_modes(unsigned int " modes ")"
.BI "int jc_widearg_to_argv(int " argc ", wchar_t **" wargv ", char **" argv ")"
.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()
.PP
.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 NOTES
libjodycode is created and maintained by Jody Bruchon
General information, help, and tech info: https://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.h000066400000000000000000000160061476644462400160310ustar00rootroot00000000000000/* 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 3
#define LIBJODYCODE_API_FEATURE_LEVEL 2
#define LIBJODYCODE_VER "3.1.2"
#define LIBJODYCODE_VERDATE "2025-03-08"
/* API sub-version table
* This table tells programs about API changes so that programs can detect
* an incompatible change and warn gracefully instead of crashing or risking
* damage to user data.
*
* REMINDER: additions must be added to version.c and libjodycode_check.c */
#define LIBJODYCODE_CACHEINFO_VER 1
#define LIBJODYCODE_JODY_HASH_VER 2
#define LIBJODYCODE_OOM_VER 1
#define LIBJODYCODE_PATHS_VER 1
#define LIBJODYCODE_SIZE_SUFFIX_VER 1
#define LIBJODYCODE_SORT_VER 1
#define LIBJODYCODE_STRING_VER 1
#define LIBJODYCODE_STRTOEPOCH_VER 1
#define LIBJODYCODE_WIN_STAT_VER 1
#define LIBJODYCODE_WIN_UNICODE_VER 2
#define LIBJODYCODE_ERROR_VER 1
#define LIBJODYCODE_ALARM_VER 1
#include
#include
#include
#include
#define JC_PATHBUF_SIZE 4096
#ifdef ON_WINDOWS
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MAN
#endif
#include
/* Unicode conversion on Windows */
#ifndef M2W
#define M2W(a,b) MultiByteToWideChar(CP_UTF8, 0, a, -1, (LPWSTR)b, JC_PATHBUF_SIZE)
#endif
#ifndef W2M
#define W2M(a,b) WideCharToMultiByte(CP_UTF8, 0, a, -1, (LPSTR)b, JC_PATHBUF_SIZE, NULL, NULL)
#endif
#else
#include
#endif /* ON_WINDOWS */
/*** 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 ***/
/* Don't use cacheinfo on anything but Linux for now */
#ifdef __linux__
/* 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 void jc_get_proc_cacheinfo(struct jc_proc_cacheinfo *pci);
#else
#define jc_get_proc_cacheinfo(a)
#endif /* __linux__ */
/*** error ***/
extern const char *jc_get_errname(int errnum);
extern const char *jc_get_errdesc(int errnum);
extern int jc_print_error(int errnum);
/*** jody_hash ***/
/* Version increments when algorithm changes incompatibly */
#ifndef JODY_HASH_VERSION
#define JODY_HASH_VERSION 7
#endif
/* Width of a jody_hash */
#define JODY_HASH_WIDTH 64
typedef uint64_t jodyhash_t;
extern int jc_block_hash(jodyhash_t *data, jodyhash_t *hash, const size_t count);
/*** 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);
/*** 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 */
extern int jc_numeric_sort(char * restrict c1, char * restrict c2, int sort_direction);
/*** string ***/
/* Same as str[n]cmp/str[n]casecmp but only checks for equality */
extern int jc_strncaseeq(const char *s1, const char *s2, size_t len);
extern int jc_strcaseeq(const char *s1, const char *s2);
extern int jc_strneq(const char *s1, const char *s2, size_t len);
extern int jc_streq(const char *s1, const char *s2);
/*** strtoepoch ***/
/* Convert a date/time string to seconds since the epoch
* Format must be "YYYY-MM-DD" or "YYYY-MM-DD HH:MM:SS" */
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;
/* This table is used for API compatibility checks */
extern const unsigned char jc_api_versiontable[];
/*** win_stat ***/
/* For Windows: provide stat-style functionality */
#ifdef ON_WINDOWS
struct jc_winstat {
uint64_t st_ino;
int64_t st_size;
uint32_t st_dev;
uint32_t st_nlink;
uint32_t st_mode;
time_t st_ctime;
time_t st_mtime;
time_t st_atime;
};
/* stat() macros for Windows "mode" flags (file attributes) */
#define S_ISARCHIVE(st_mode) ((st_mode & FILE_ATTRIBUTE_ARCHIVE) ? 1 : 0)
#define S_ISRO(st_mode) ((st_mode & FILE_ATTRIBUTE_READONLY) ? 1 : 0)
#define S_ISHIDDEN(st_mode) ((st_mode & FILE_ATTRIBUTE_HIDDEN) ? 1 : 0)
#define S_ISSYSTEM(st_mode) ((st_mode & FILE_ATTRIBUTE_SYSTEM) ? 1 : 0)
#define S_ISCRYPT(st_mode) ((st_mode & FILE_ATTRIBUTE_ENCRYPTED) ? 1 : 0)
#define S_ISDIR(st_mode) ((st_mode & FILE_ATTRIBUTE_DIRECTORY) ? 1 : 0)
#define S_ISCOMPR(st_mode) ((st_mode & FILE_ATTRIBUTE_COMPRESSED) ? 1 : 0)
#define S_ISREPARSE(st_mode) ((st_mode & FILE_ATTRIBUTE_REPARSE_POINT) ? 1 : 0)
#define S_ISSPARSE(st_mode) ((st_mode & FILE_ATTRIBUTE_SPARSE) ? 1 : 0)
#define S_ISTEMP(st_mode) ((st_mode & FILE_ATTRIBUTE_TEMPORARY) ? 1 : 0)
#define S_ISREG(st_mode) ((st_mode & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT)) ? 0 : 1)
extern time_t jc_nttime_to_unixtime(const uint64_t * const restrict timestamp);
extern time_t jc_unixtime_to_nttime(const uint64_t * const restrict timestamp);
extern int jc_win_stat(const char * const filename, struct jc_winstat * const restrict buf);
#endif /* ON_WINDOWS */
/*** win_unicode ***/
/* Print strings in Unicode mode on Windows */
extern int jc_fwprint(FILE * const restrict stream, const char * const restrict str, const int cr);
#ifdef UNICODE
extern void jc_slash_convert(char *path);
extern void jc_set_output_modes(unsigned int modes);
extern int jc_widearg_to_argv(int argc, wchar_t **wargv, char **argv);
#else
#define jc_slash_convert(a)
#endif /* UNICODE */
#ifdef __cplusplus
}
#endif
#endif /* LIBJODYCODE_H */
libjodycode/likely_unlikely.h000066400000000000000000000011731476644462400167460ustar00rootroot00000000000000/* 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/oom.c000066400000000000000000000012401476644462400143210ustar00rootroot00000000000000/* Out-of-memory and NULL pointer error exit calls
*
* 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 */
extern 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 */
extern 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.c000066400000000000000000000076711476644462400146640ustar00rootroot00000000000000/* Jody Bruchon's path manipulation code library
*
* Copyright (C) 2014-2025 by Jody Bruchon
* Released under The MIT License
*/
#include
#include
#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 '/') */
extern int jc_collapse_dotdot(char * const path)
{
char *p; /* string copy input */
char *out; /* string copy output */
unsigned int i = 0;
/* Fail if not passed an absolute path */
if (unlikely(*path != '/')) return -1;
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 -2;
/* 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 */
extern int jc_make_relative_link_name(const char * const src,
const char * const dest, char * rel_path)
{
static char p1[JC_PATHBUF_SIZE + 4], p2[JC_PATHBUF_SIZE + 4];
static char *sp, *dp, *ss;
if (unlikely(!src || !dest)) return -1;
/* Get working directory path and prefix to pathnames if needed */
if (*src != '/' || *dest != '/') {
if (!getcwd(p1, JC_PATHBUF_SIZE)) return -2;
*(p1 + (JC_PATHBUF_SIZE) - 1) = '\0';
strncat(p1, "/", JC_PATHBUF_SIZE - 1);
strncpy(p2, p1, JC_PATHBUF_SIZE);
}
/* If an absolute path is provided, use it as-is */
if (*src == '/') *p1 = '\0';
if (*dest == '/') *p2 = '\0';
/* Concatenate working directory to relative paths */
strncat(p1, src, JC_PATHBUF_SIZE);
strncat(p2, dest, JC_PATHBUF_SIZE);
/* Collapse . and .. path components */
if (unlikely(jc_collapse_dotdot(p1) != 0)) return -3;
if (unlikely(jc_collapse_dotdot(p2) != 0)) return -3;
/* 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') return 1;
/* 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) == '/'))
return -4;
if (unlikely(*(rel_path - 1) == '/')) return -4;
*rel_path = '\0';
return 0;
}
libjodycode/size_suffix.c000066400000000000000000000017111476644462400160700ustar00rootroot00000000000000/* List of data size suffixes
*
* Copyright (C) 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/sort.c000066400000000000000000000062171476644462400145270ustar00rootroot00000000000000/* Jody Bruchon's sorting code library
*
* Copyright (C) 2014-2025 by Jody Bruchon
* Released under The MIT License
*/
#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)
/* Sort by logical numeric order (10 comes after 2, not before) */
extern int jc_numeric_sort(char * restrict c1,
char * restrict c2, int sort_direction)
{
int len1 = 0, len2 = 0;
int precompare;
char *rewind1, *rewind2;
if (unlikely(c1 == NULL || c2 == NULL)) return -99;
/* Numerically correct sort */
while (unlikely(*c1 != '\0' && *c2 != '\0')) {
/* Reset string length counters and rewind points */
len1 = 0; len2 = 0; rewind1 = c1; rewind2 = c2;
/* Skip all sequences of zeroes */
while (*c1 == '0') {
len1++;
c1++;
}
while (*c2 == '0') {
len2++;
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 = -sort_direction;
if (*c1 > *c2) precompare = sort_direction;
len1++; len2++;
c1++; c2++;
/* Skip remaining digit pairs after any
* difference is found */
if (precompare != 0) {
while (IS_NUM(*c1) && IS_NUM(*c2)) {
len1++; len2++;
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)) return sort_direction;
else return -sort_direction;
}
/* 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;
len1 = 0; len2 = 0;
}
/* Do normal comparison */
if (likely(*c1 == *c2 && *c1 != '\0' && *c2 != '\0')) {
c1++; c2++;
len1++; len2++;
/* Put symbols and spaces after everything else */
} else if (*c2 < '.' && *c1 >= '.') return -sort_direction;
else if (*c1 < '.' && *c2 >= '.') return sort_direction;
/* Normal strcasecmp() style compare */
else {
char s1 = *c1, s2 = *c2;
/* Convert lowercase into uppercase */
if (IS_LOWER(s1)) s1 = (char)(s1 - 32);
if (IS_LOWER(s2)) s2 = (char)(s2 - 32);
if (s1 > s2) return sort_direction;
else return -sort_direction;
}
}
/* Longer strings generally sort later */
if (len1 < len2) return -sort_direction;
if (len1 > len2) return sort_direction;
/* Normal comparison - FIXME? length check should already handle these */
if (unlikely(*c1 == '\0' && *c2 != '\0')) return -sort_direction;
if (unlikely(*c1 != '\0' && *c2 == '\0')) return sort_direction;
/* Fall through: the strings are equal */
return 0;
}
libjodycode/string.c000066400000000000000000000034721476644462400150460ustar00rootroot00000000000000/*
* Jody Bruchon's string function library
* Copyright (C) 2015-2025 by Jody Bruchon
* Released under The MIT License
*/
#include
#include
#include "likely_unlikely.h"
#include "libjodycode.h"
/* Like strncasecmp() but only tests for equality */
extern int jc_strncaseeq(const char *s1, const char *s2, 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;
}
/* Like strcasecmp() but only tests for equality */
extern 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 strncmp() but only tests for equality */
extern int jc_strneq(const char *s1, const char *s2, 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 strcmp() but only tests for equality */
extern 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;
}
libjodycode/strtoepoch.c000066400000000000000000000051601476644462400157260ustar00rootroot00000000000000/* Jody Bruchon's datetime-to-epoch conversion function
*
* Copyright (C) 2020-2025 by Jody Bruchon
* Released under The MIT License
*/
#include
#include
#include
#include "likely_unlikely.h"
#include "libjodycode.h"
#define REQ_NUM(a) { if (a < '0' || a > '9') return -1; }
#define ATONUM(a,b) (a = b - '0')
/* Fast multiplies by 100 (*64 + *32 + *4) and 10 (*8 + *2)
* for platforms where multiply instructions are expensive */
#ifdef STRTOEPOCH_USE_SHIFT_MULTIPLY
#define MUL100(a) ((a << 6) + (a << 5) + (a << 2))
#define MUL10(a) ((a << 3) + a + a)
#else
#define MUL100(a) (a * 100)
#define MUL10(a) (a * 10)
#endif /* STRTOEPOCH_USE_SHIFT_MULTIPLY */
/* 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 */
extern 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')) return -1;
memset(&tm, 0, sizeof(struct tm));
/* This code replaces "*10" with shift<<3 + add + add */
/* Process year */
tm.tm_year = 1000;
REQ_NUM(*p); if (*p == '2') tm.tm_year = 2000; p++;
REQ_NUM(*p); ATONUM(i, *p); tm.tm_year += MUL100(i); p++;
REQ_NUM(*p); ATONUM(i, *p); tm.tm_year += MUL10(i); p++;
REQ_NUM(*p); ATONUM(i, *p); tm.tm_year += i; p++;
tm.tm_year -= 1900; /* struct tm year is since 1900 */
if (*p != '-') return -1;
p++;
/* Process month (0-11, not 1-12) */
REQ_NUM(*p); ATONUM(i, *p); tm.tm_mon = MUL10(i); p++;
REQ_NUM(*p); ATONUM(i, *p); tm.tm_mon += (i - 1); p++;
if (*p != '-') return -1;
p++;
/* Process day */
REQ_NUM(*p); ATONUM(i, *p); tm.tm_mday = MUL10(i); p++;
REQ_NUM(*p); ATONUM(i, *p); tm.tm_mday += i; p++;
/* If YYYY-MM-DD is specified only, skip the time part */
if (*p == '\0') goto skip_time;
if (*p != ' ') return -1; else p++;
/* Process hours */
REQ_NUM(*p); ATONUM(i, *p); tm.tm_hour = MUL10(i); p++;
REQ_NUM(*p); ATONUM(i, *p); tm.tm_hour += i; p++;
if (*p != ':') return -1;
p++;
/* Process minutes */
REQ_NUM(*p); ATONUM(i, *p); tm.tm_min = MUL10(i); p++;
REQ_NUM(*p); ATONUM(i, *p); tm.tm_min += i; p++;
if (*p != ':') return -1;
p++;
/* Process seconds */
REQ_NUM(*p); ATONUM(i, *p); tm.tm_sec = MUL10(i); p++;
REQ_NUM(*p); ATONUM(i, *p); tm.tm_sec += i; p++;
/* Junk after datetime string should cause an error */
if (*p != '\0') return -1;
skip_time:
tm.tm_isdst = -1; /* Let the host library decide if DST is in effect */
secs = mktime(&tm);
return secs;
}
libjodycode/test.sh000077500000000000000000000001321476644462400147000ustar00rootroot00000000000000#!/bin/sh
# This is a dummy test script meant for automated builds to succeed.
echo "OK"
libjodycode/tune_winres.sh000077500000000000000000000021131476644462400162640ustar00rootroot00000000000000#!/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.c000066400000000000000000000017321476644462400152220ustar00rootroot00000000000000/* libjodycode version function
*
* Copyright (C) 2025 by Jody Bruchon
* Released under The MIT License
*/
#include "libjodycode.h"
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;
/* API sub-version info array, terminated with 0
* Valid versions are 1-254. New API sections MUST be added to the end. The
* maximum value of 255 is used by programs to terminate their check array. */
const unsigned char jc_api_versiontable[] = {
LIBJODYCODE_CACHEINFO_VER,
LIBJODYCODE_JODY_HASH_VER,
LIBJODYCODE_OOM_VER,
LIBJODYCODE_PATHS_VER,
LIBJODYCODE_SIZE_SUFFIX_VER,
LIBJODYCODE_SORT_VER,
LIBJODYCODE_STRING_VER,
LIBJODYCODE_STRTOEPOCH_VER,
LIBJODYCODE_WIN_STAT_VER,
LIBJODYCODE_WIN_UNICODE_VER,
LIBJODYCODE_ERROR_VER,
LIBJODYCODE_ALARM_VER,
0
};
libjodycode/win_stat.c000066400000000000000000000053371476644462400153720ustar00rootroot00000000000000/*
* Windows-native code for getting stat()-like information
*
* Copyright (C) 2016-2025 by Jody Bruchon
* Released under The MIT License
*/
/* This code is only useful on Windows */
#ifdef ON_WINDOWS
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include
#include
#include
#include "likely_unlikely.h"
#include "libjodycode.h"
/* Convert NT epoch to UNIX epoch */
extern time_t jc_nttime_to_unixtime(const uint64_t * const restrict timestamp)
{
uint64_t newstamp;
memcpy(&newstamp, timestamp, sizeof(uint64_t));
newstamp /= 10000000LL;
if (unlikely(newstamp <= 11644473600LL)) return 0;
newstamp -= 11644473600LL;
return (time_t)newstamp;
}
/* Convert UNIX epoch to NT epoch */
extern time_t jc_unixtime_to_nttime(const uint64_t * const restrict timestamp)
{
uint64_t newstamp;
memcpy(&newstamp, timestamp, sizeof(uint64_t));
newstamp += 11644473600LL;
newstamp *= 10000000LL;
if (unlikely(newstamp <= 11644473600LL)) return 0;
return (time_t)newstamp;
}
/* Get stat()-like extra information for a file on Windows */
extern int jc_win_stat(const char * const filename, struct jc_winstat * const restrict buf)
{
HANDLE hFile;
BY_HANDLE_FILE_INFORMATION bhfi;
uint64_t timetemp;
#ifdef UNICODE
static wchar_t wname2[JC_PATHBUF_SIZE + 4];
if (unlikely(!buf)) return -127;
if (!M2W(filename,wname2)) return -126;
hFile = CreateFileW(wname2, 0, FILE_SHARE_READ, NULL, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS, NULL);
#else
if (unlikely(!buf)) return -127;
hFile = CreateFile(filename, 0, FILE_SHARE_READ, NULL, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS, NULL);
#endif
if (unlikely(hFile == INVALID_HANDLE_VALUE)) goto failure;
if (unlikely(!GetFileInformationByHandle(hFile, &bhfi))) goto failure2;
buf->st_ino = ((uint64_t)(bhfi.nFileIndexHigh) << 32) + (uint64_t)bhfi.nFileIndexLow;
buf->st_size = ((int64_t)(bhfi.nFileSizeHigh) << 32) + (int64_t)bhfi.nFileSizeLow;
timetemp = ((uint64_t)(bhfi.ftCreationTime.dwHighDateTime) << 32) + bhfi.ftCreationTime.dwLowDateTime;
buf->st_ctime = jc_nttime_to_unixtime(&timetemp);
timetemp = ((uint64_t)(bhfi.ftLastWriteTime.dwHighDateTime) << 32) + bhfi.ftLastWriteTime.dwLowDateTime;
buf->st_mtime = jc_nttime_to_unixtime(&timetemp);
timetemp = ((uint64_t)(bhfi.ftLastAccessTime.dwHighDateTime) << 32) + bhfi.ftLastAccessTime.dwLowDateTime;
buf->st_atime = jc_nttime_to_unixtime(&timetemp);
buf->st_dev = (uint32_t)bhfi.dwVolumeSerialNumber;
buf->st_nlink = (uint32_t)bhfi.nNumberOfLinks;
buf->st_mode = (uint32_t)bhfi.dwFileAttributes;
CloseHandle(hFile);
return 0;
failure:
CloseHandle(hFile);
return -1;
failure2:
CloseHandle(hFile);
return -2;
}
#endif /* ON_WINDOWS */
libjodycode/win_unicode.c000066400000000000000000000057541476644462400160500ustar00rootroot00000000000000/* Jody Bruchon's Windows Unicode helper routines
*
* Copyright (C) 2014-2025 by Jody Bruchon
* Released under The MIT License
*/
#include
#include
#include
#include "likely_unlikely.h"
#include "libjodycode.h"
#ifdef UNICODE
#define WIN32_LEAN_AND_MEAN
#include
#include
static int out_mode = _O_TEXT;
static int err_mode = _O_TEXT;
static wchar_t wstr[JC_PATHBUF_SIZE + 4];
/* Convert slashes to backslashes in a file path */
extern void jc_slash_convert(char *path)
{
while (*path != '\0') {
if (*path == '/') *path = '\\';
path++;
}
return;
}
/* Set output modes to TEXT, BINARY, or UTF-16 */
extern void jc_set_output_modes(unsigned int modes)
{
/* Mode is selected by setting a bit flag:
* 0x01 = 1: set stdout mode to UTF-16
* 0x02 = 1: set stderr mode to UTF-16
* 0x04 = 1: set stdout mode to UTF-16 only if it is a terminal
* 0x08 = 1: set stderr mode to UTF-16 only if it is a terminal
* 0x10 = 1: set flagged outputs to text mode instead
* If not setting UTF-16, all modes default to BINARY without 0x10 set
* */
if (modes & 0x10U) {
out_mode = (modes & 0x01U) ? _O_TEXT : out_mode;
err_mode = (modes & 0x02U) ? _O_TEXT : err_mode;
return;
}
if (modes & 0x04U) {
/* Only use UTF-16 for terminal output, else use UTF-8 */
if (!_isatty(_fileno(stdout))) out_mode = _O_BINARY;
else out_mode = _O_U16TEXT;
} else if (modes & 0x08U) {
if (!_isatty(_fileno(stderr))) err_mode = _O_BINARY;
else err_mode = _O_U16TEXT;
} else {
out_mode = (modes & 0x01U) ? _O_U16TEXT : _O_BINARY;
err_mode = (modes & 0x02U) ? _O_U16TEXT : _O_BINARY;
}
return;
}
/* Copy Windows wide character arguments to UTF-8 */
extern int jc_widearg_to_argv(int argc, wchar_t **wargv, char **argv)
{
static char temp[JC_PATHBUF_SIZE * 2 + 4];
int len;
if (unlikely(!argv)) return -6;
for (int counter = 0; counter < argc; counter++) {
len = W2M(wargv[counter], &temp);
if (unlikely(len < 1)) return -7;
argv[counter] = (char *)malloc((size_t)len + 1);
if (unlikely(!argv[counter])) jc_oom("widearg_to_argv()");
strncpy(argv[counter], temp, (size_t)len + 1);
}
return 0;
}
#endif /* UNICODE */
/* Print a string that is wide on Windows but normal on POSIX */
extern int jc_fwprint(FILE * const restrict stream, const char * const restrict str, const int cr)
{
#ifdef UNICODE
int retval;
int stream_mode = out_mode;
if (stream == stderr) stream_mode = err_mode;
if (stream_mode == _O_U16TEXT) {
/* Convert to wide string and send to wide console output */
if (!M2W(str, wstr)) return -7;
fflush(stream);
_setmode(_fileno(stream), stream_mode);
if (cr == 2) retval = fwprintf(stream, L"%S%C", wstr, 0);
else retval = fwprintf(stream, L"%S%S", wstr, cr == 1 ? L"\n" : L"");
fflush(stream);
_setmode(_fileno(stream), _O_TEXT);
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/winres.manifest.xml000066400000000000000000000007011476644462400172220ustar00rootroot00000000000000
true
libjodycode/winres.rc000066400000000000000000000015531476644462400152270ustar00rootroot00000000000000#include "winver.h"
1 24 winres.manifest.xml
VS_VERSION_INFO VERSIONINFO
FILEVERSION 3,1,2,0
PRODUCTVERSION 3,1,2,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", "3,1,2,0"
VALUE "InternalName", "libjodycode"
VALUE "LegalCopyright", "(C) 2014-2025 Jody Bruchon "
VALUE "OriginalFilename", "libjodycode.dll"
VALUE "ProductName", "libjodycode"
VALUE "ProductVersion", "3,1,2,0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
libjodycode/winres_xp.rc000066400000000000000000000015211476644462400157310ustar00rootroot00000000000000#include "winver.h"
VS_VERSION_INFO VERSIONINFO
FILEVERSION 3,1,2,0
PRODUCTVERSION 3,1,2,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", "3,1,2,0"
VALUE "InternalName", "libjodycode"
VALUE "LegalCopyright", "(C) 2014-2025 Jody Bruchon "
VALUE "OriginalFilename", "libjodycode.dll"
VALUE "ProductName", "libjodycode"
VALUE "ProductVersion", "3,1,2,0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END