From 5e97eeceb9dec727fe1bce20660069b5f69a5d8a Mon Sep 17 00:00:00 2001 From: hiddenalpha.ch Date: Sun, 30 Aug 2026 00:01:23 +0200 Subject: de-clutter whole project --- README.md | 41 ++- configure | 147 ----------- contrib/build-env-setup/README.md | 60 ----- contrib/build-env-setup/setup | 27 -- src/bulk_ln.c | 514 ++++++++++++++++++++++++++++++++++++++ src/bulk_ln/bulk_ln.c | 429 ------------------------------- src/bulk_ln/bulk_ln.h | 10 - src/bulk_ln/bulk_ln_main.c | 10 - src/common/commonbase.h | 23 -- 9 files changed, 529 insertions(+), 732 deletions(-) delete mode 100755 configure delete mode 100644 contrib/build-env-setup/README.md delete mode 100644 contrib/build-env-setup/setup create mode 100644 src/bulk_ln.c delete mode 100644 src/bulk_ln/bulk_ln.c delete mode 100644 src/bulk_ln/bulk_ln.h delete mode 100644 src/bulk_ln/bulk_ln_main.c delete mode 100644 src/common/commonbase.h diff --git a/README.md b/README.md index 286ca54..0dd9bc0 100644 --- a/README.md +++ b/README.md @@ -6,41 +6,30 @@ BulkLn create many links. -## How to build / install - -Classic way: +## How to build ```sh -curl -sSL http://git.hiddenalpha.ch/bulk-ln.git/snapshot/bulk-ln-master.tar.gz | tar xz -cd bulk-ln-master -./configure -make -make install +git init -b none +git fetch --depth=1 http://git.hiddenalpha.ch/bulk-ln.git master:master +git checkout master +lua src/bulk_ln.c +./bulk-ln --help ``` -In case you prefer more sustainable build environments, -"./contrib/build-env-setup/README.md" -potentially is interesting for you. - - ## Stats For Nerds ```text github.com/AlDanial/cloc -------------------------------------------------- -Language files blank comment code -------------------------------------------------- -C 2 66 79 294 -Bourne Shell 1 23 7 117 -Markdown 1 16 0 29 -C/C++ Header 2 13 5 15 -------------------------------------------------- -SUM: 6 118 91 455 -------------------------------------------------- +------------------------------------- +Language files blank comment code +------------------------------------- +C 1 71 87 356 +Markdown 1 13 0 24 +Text 1 5 0 16 +------------------------------------- +SUM: 3 89 87 396 +------------------------------------- ``` - - - diff --git a/configure b/configure deleted file mode 100755 index ba4317f..0000000 --- a/configure +++ /dev/null @@ -1,147 +0,0 @@ -#!/bin/sh -set -e -if test -n "$BASH_VERSION"; then set -o posix; fi - -# See https://www.gnu.org/prep/standards/html_node/Directory-Variables.html -prefix=/usr/local -exec_prefix='$(PREFIX)' -exec_suffix= -bindir='$(EXEC_PREFIX)/bin' -verbose=0 - - -main() { - parseArgs "$@" - printMakefile > Makefile -} - - -printHelp() { - echo '' - echo "Example usage: $0 --prefix /usr/local" - echo '' - echo 'Options:' - echo ' --prefix "/usr/local"' - echo ' --exec-prefix "$(PREFIX)"' - echo ' --exec-suffix ""' - echo ' --bindir "$(EXEC_PREFIX)/bin"' - echo ' --verbose 0' - echo '' -} - - -parseArgs() { - # See: https://stackoverflow.com/a/14203146/4415884 - while test $# -gt 0 ; do - case $1 in - --help) printHelp; exit 1; ;; - --prefix) prefix="$2" shift; shift; ;; - --exec-prefix) exec_prefix="$2" shift; shift; ;; - --exec-suffix) exec_suffix="$2" shift; shift; ;; - --bindir) bindir="$2" shift; shift; ;; - --verbose) verbose="$2" shift; shift; ;; - *) echo "[ERROR] Unexpected argument: $1"; exit 1 ;; - esac - done - if test "$verbose" != "0" -a "$verbose" != "1"; then - echo "[ERROR] verbose expected to be 0 or 1. But is: $verbose"; - false; - fi -} - - -printMakefile () { - printf '\n' - printf '%s\n' '# This file is generated by configure script. Manual changes may be lost' - printf '%s\n' '# when calling configure again.' - printf '\n' - printf '%s\n' 'CC=gcc' - printf '%s\n' 'LD=gcc' - printf '%s\n' 'TAR=tar' - printf '%s\n' 'MKDIRS=mkdir -p' - printf '%s\n' 'RIMRAF=rm -rf' - printf '%s\n' 'COPYTO=cp -t' - printf '%s\n' "PREFIX=$prefix" - printf '%s\n' "EXEC_PREFIX=$exec_prefix" - printf '%s\n' "EXEC_SUFFIX=$exec_suffix" - printf '%s\n' "BINDIR=$bindir" - - # If PROJECT_VERSION set, use it. - # If not, try generate via git describe. - # If fails (eg git not avail), create pseudo version using date. - printf '%s' 'PROJECT_VERSION=$(shell' - printf '%s' ' ret=$$(git describe --tags 2>/dev/null |sed -E "s;^v;;");' - printf '%s' ' if test -n "$$ret" -a -n "$$(git status --porcelain 2>/dev/null)"; then' - printf '%s' ' ret="$$ret+dirt";' - printf '%s' ' fi;' - printf '%s' ' if test -z "$$ret"; then ret=$$(date -u +0.0.0-%Y%m%d.%H%M%S); fi;' - printf '%s\n' ' echo "$$ret")' - - printf '\n' - printf '%s' 'CFLAGS= --std=c99 -Wall -Wextra -Werror -Isrc/bulk_ln -Isrc/common -Os -DPROJECT_VERSION=$(PROJECT_VERSION)' - printf '%s\n' '' - printf '%s\n' 'LDFLAGS= -Wl,--fatal-warnings,--gc-sections,--as-needed -s' - - if test "$verbose" = "0"; then - printf '\n' - printf '%s\n' '.SILENT:'; - fi - - printf '\n' - printf '%s\n' 'default: link dist' - - printf '\n' - printf '%s\n' '.PHONY: clean' - printf '%s\n' 'clean:' - printf '%s\n' ' echo $(RIMRAF) build dist' - printf '%s\n' ' $(RIMRAF) build dist' - - printf '\n' - printf '%s\n' '.PHONY: link' - printf '%s\n' 'link: build/bin/bulk-ln$(EXEC_SUFFIX)' - - printf '\n' - printf '%s\n' 'build/obj/%.o: src/%.c' - printf '%s\n' ' echo " CC $@"' - printf '%s\n' ' $(MKDIRS) "$(shell T=$@; echo $${T%/*})"' - printf '%s\n' ' $(CC) -c -o $@ $^ $(CFLAGS)' - - printf '\n' - printf '%s' 'build/bin/bulk-ln$(EXEC_SUFFIX):' - # dependencies - printf '%s' ' build/obj/bulk_ln/bulk_ln.o' - printf '%s' ' build/obj/bulk_ln/bulk_ln_main.o' - printf '\n' # commands - printf '%s\n' ' echo " LN $@"' - printf '%s\n' ' $(MKDIRS) "$(shell T=$@; echo $${T%/*})"' - printf '%s\n' ' $(LD) -o $@ $(LDFLAGS) $^' - - printf '\n' - printf '%s\n' '.PHONY: dist' - printf '%s\n' 'dist: link' - printf '%s\n' ' echo " Package"' - printf '%s\n' ' $(RIMRAF) build/dist-* dist' - printf '%s\n' ' $(MKDIRS) dist' - # Create Executable bundle. - printf '%s\n' ' $(MKDIRS) build/dist-bin && $(COPYTO) build/dist-bin \' - printf '%s\n' ' README*' - printf '%s\n' ' $(MKDIRS) build/dist-bin/bin && $(COPYTO) build/dist-bin/bin \' - printf '%s\n' ' build/bin/*' - - printf '%s\n' ' (cd build/dist-bin && find . -type f -not -name MD5SUM -exec md5sum -b {} +) > build/dist-bin/MD5SUM' - - printf '%s\n' ' (cd build/dist-bin && $(TAR) --owner=0 --group=0 -czf "../../dist/BulkLn-$(PROJECT_VERSION).tgz" *)' - printf '%s\n' ' echo "" && echo "See '\''./dist/'\'' for result." && echo ""' - printf '%s' ' if test -n "$$(git status --porcelain 2>/dev/null)"; then' - printf '%s' ' echo "[WARN ] Worktree dirty!"; sleep 2;' - printf '%s\n' ' fi' - - printf '\n' - printf '%s\n' '.PHONY: install' - printf '%s\n' 'install:' - printf '%s\n' ' $(MKDIRS) "$(BINDIR)"' - printf '%s\n' ' $(TAR) --strip-components=1 -C "$(BINDIR)" -xzf "$(shell ls dist/BulkLn-*.tgz)" -- bin' -} - - -main "$@" diff --git a/contrib/build-env-setup/README.md b/contrib/build-env-setup/README.md deleted file mode 100644 index 90322f7..0000000 --- a/contrib/build-env-setup/README.md +++ /dev/null @@ -1,60 +0,0 @@ - -Showcase how to build and install -================================= - -WARN: Do NOT perform any of these steps on your host system! This script - MUST only be run on a system which is a - just-throw-it-away-if-broken system. - -Sometimes happy developers (like me) have no choice but using terribly -restricted systems where setting up tools to run even something as -trivial as configure/make/install becomes a nightmare if not impossible. -I found it to be very handy to have some independent qemu VM at hand -which lets me install whatever I need, neither with any special software -nor any annoying privileges on a host machine. Qemu runs portable and in -user mode even doesn't need any annoying permissions at all. - - -## Setup a minimal system in your qemu VM - -This setup mainly targets debian. Nevertheless it tries to stay POSIX -compatible as far as possible. So setup a minimal install of your system -of choice and then as soon you've SSH access to a (posix) shell, you're -ready for the next step. - -Still not sure which system to use? Link below provides some candidates. -HINT: Windows IMHO is a terrible choice. So stop complaining if you go -this route. - -https://en.wikipedia.org/wiki/POSIX#POSIX-oriented_operating_systems - - -## Start VM with SSH access - -Easiest way to work with your machine is via SSH. Therefore if you've -chosen to use a qemu VM, make sure you've setup and configured sshd -properly inside the VM. Then just pass args like those to qemu: - - --device e1000,netdev=n0 --netdev user,id=n0,hostfwd=tcp:127.0.0.1:2222-:22 - -Started this way, the SSHDaemon inside the VM is accessible from your -host via "localhost" at port "2222": - - ssh localhost -p2222 - - -## Finalize by build and install whole project - -Run the "./setup" script (which is a posix shell script btw) inside the -freshly setup system. This script does all the work. Like installing -required packages, configure and build the whole project and finally -installing it into the VM so it can be tried out right away from your -VMs shell. -BTW: The script is constructed so it can be copy-pasted into a terminal. -There is no need to transfer a file to the machine beforehand. - - - - - - diff --git a/contrib/build-env-setup/setup b/contrib/build-env-setup/setup deleted file mode 100644 index f5f51ce..0000000 --- a/contrib/build-env-setup/setup +++ /dev/null @@ -1,27 +0,0 @@ - -echo "WARN: read (AND UNDERSTAND) the README before running this script!" -sleep 3 -exit 1 - - -true \ - && GIT_TAG="master" \ - && CONFIGURE_OPTS= \ - && WORKDIR="${HOME:?}/work" \ - && PKGS_TO_ADD="ca-certificates curl gcc git make libc-dev tar" \ - && SUDO=sudo \ - && PKGINIT="$SUDO apt update" \ - && PKGADD="$SUDO apt install -y --no-install-recommends" \ - && `# prepare` \ - && mkdir -p "${WORKDIR:?}" && cd "${WORKDIR:?}" \ - && ${PKGINIT:?} && ${PKGADD:?} $PKGS_TO_ADD \ - && git clone --depth 42 --branch "${GIT_TAG:?}" https://github.com/hiddenalpha/bulk-ln.git . \ - && git config advice.detachedHead false \ - && git checkout "${GIT_TAG:?}" \ - && ./configure $CONFIGURE_OPTS \ - && make clean && make -j$(nproc) \ - && $SUDO make install \ - && dirOfDistBundle="$(realpath dist)" \ - && printf '\n SUCCESS :) Distribution bundle is ready in:\n\n %s\n\n Tip: Before pulling out your hair about how to get that archive out of\n your qemu VM. STOP kluding around with silly tools and learn how\n basic tools do the job perfectly fine:\n\n ssh %s@localhost -p%d -T '\''true && cd "%s" && tar c *'\'' | tar x\n\n BTW: In addition bulk-ln got installed and is ready-to-use.\n\n' "${dirOfDistBundle:?}" "$USER" "$(env|grep SSH_CLIENT|cut -d' ' -f3)" "${dirOfDistBundle:?}" \ - && true - diff --git a/src/bulk_ln.c b/src/bulk_ln.c new file mode 100644 index 0000000..f3cda22 --- /dev/null +++ b/src/bulk_ln.c @@ -0,0 +1,514 @@ +#if 0 +--[===========================================================================[ + + How to build: + + lua src/bulk_ln.c + + How to use: + + ./bulk-ln --help + + ]===========================================================================] +function shEsc(s)return "'".. s:gsub("'", "'\"'\"'") .."'" end +do + local thisFile = debug.getinfo(1,"S").source:sub(2) + local sh = io.popen("sh", "w") + assert(sh, "Failed to launch a shell") + sh:write([=[set -e \ + \ + && CC="${CC:-gcc}" \ + && LD="${LD:-gcc}" \ + && OBJDUMP="${OBJDUMP:-objdump}" \ + && RIMRAF="${RIMRAF:-rm -rf}" \ + && BINEXT="${BINEXT:-}" \ + && CFLAGS="${CFLAGS:- -std=c99 -Wall -Werror -pedantic -nostdlib -nodefaultlibs}" \ + && LDFLAGS="${LDFLAGS:-}" \ + && TMPDIR="${TMPDIR:-/tmp}" \ + && OUTFILE="bulk-ln${BINEXT?}" \ + \ + && if test -z "$PROJECT_VERSION" ;then true \ + && tag="$(git tag|grep -E '^v'|sed -E 's,^v,,'|sort -V|tail -n1)" \ + && cnt="$(git rev-list --count "v${tag:?}"..HEAD)" \ + && hsh="$(git rev-parse --short HEAD)" \ + && if test -n "$tag" -a -n "$cnt" -a -n "$hsh" ;then true \ + && PROJECT_VERSION="${tag:?}-${cnt:?}-g${hsh:?}" \ + ;else true \ + && PROJECT_VERSION="$(date -u +0.0.0-%Y%m%d.%H%M%S)" \ + ;fi \ + ;fi \ + \ + && ${CC:?} -c -o "${TMPDIR:?}"/YC71NcBIJMwpwUfL ]=].. shEsc(thisFile) ..[=[ ${CFLAGS?} -DPROJECT_VERSION="${PROJECT_VERSION:?}" -DEXPOSE_MAIN=1 \ + && ${LD:?} -o ./"${OUTFILE:?}" "${TMPDIR:?}"/YC71NcBIJMwpwUfL ${LDFLAGS?} \ + && ${RIMRAF:?} "${TMPDIR:?}"/YC71NcBIJMwpwUfL \ + && bullshit=$(${OBJDUMP?} -p "${OUTFILE:?}"|grep DLL\ Name|egrep -v ' (KERNEL32.dll|SHELL32.dll|WS2_32.dll|ADVAPI32.dll|msvcrt.dll|GDI32.dll|USER32.dll)$'||true) \ + && if test -n "$bullshit"; then printf '\n ERROR: Bullshit has sneaked in:\n\n%s\n\n' "$bullshit"; rm "${OUTFILE:?}"; false; fi \ +]=]) + -- Shitty systems maybe need adaptions like: + -- && LDFLAGS="-Wl,-lws2_32,-l:libwinpthread.a" \ + local ok, how, code = sh:close() + if not ok then os.exit(code) end +end +--[===========================================================================[ +#endif + +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include +#include +#include +#include + +#define STR_QUOT_IAHGEWIH(s) #s +#define STR_QUOT(s) STR_QUOT_IAHGEWIH(s) + +#ifndef PRIVATE +# define PRIVATE(T) static T +#endif + +#ifndef DATA_FILE_FIELD_SEP_CHR +# define DATA_FILE_FIELD_SEP_CHR '\t' +#endif + + +typedef struct BulkLn { + /* Root context + */ + + char *dataFilePath; + /* File (path) where we read the paths-to-link from. The special + * value - (dash) means to use the stdin stream. + */ + + FILE *dataFd; + + int createdLinksCount; + /* Count of links we created + */ + + int createdDirsCount; + /* Count of direcrories we created + */ + + int dryRun; + /* If a dry-run got requested + */ + + int isPrintEachCreateLink; + /* If true, we will print to stdout for every link we create (like we do + * in dry run) + 0*/ + + int isPrintEachMkdir; + /* print every call we make to mkdir to stdout. Usually only useful for + * debugging + */ + + int isPrintStatus; + /* if true, we periodically print some kind of progress to stderr + */ + + int isPrintSummary; + /* if true, we print a summary what we did at the end of the run to stderr + */ + + int isRelinkExistingFiles; + /* if true, we will override existing files. This will be enabled for + * example if --force got specified. + */ +} BulkLn; + + + +PRIVATE(void) +printHelp(){ + printf(" \n %s @ " STR_QUOT(PROJECT_VERSION) "\n" + " \n" + " Takes paths (pairwise) from stdin (see --stdin for details) and\n" + " creates a hardlink for each pair from the 1st path to the 2nd.\n" + " \n" + " Writing a custom implementation of 'ln' got necessary as we found no\n" + " way to instruct 'ln' to create a few thousand links in an acceptable\n" + " amount of time. Remind that wheels that do not fit should be\n" + " re-invented (yes, no matter how much they already exist!). So now we\n" + " have it. Our re-invented wheel that finally fits our use-case ;) \n" + " \n" + " See also\n" + " https://pubs.opengroup.org/onlinepubs/009695399/utilities/ln.html\n" + " for info about the original 'ln'.\n" + " \n" + " Options:\n" + " \n" + " --stdin\n" + " Read the path pairs to link from stdin. The format is like:\n" + " \n" + " \n" + " \n" + " Example:\n" + " \n" + " origin/foo.txt\tnew/gugg.txt\n" + " origin/bar.txt\tnew/da.txt\n" + " \n" + " HINT: Preferred is LF. But CRLF should work too.\n" + " \n" + " --quiet\n" + " Don't print status or similar stuff. Errors will still be\n" + " printed to stderr.\n" + " \n" + " --verbose\n" + " Print stupid amount of logs. Usually only helpful for debugging.\n" + " Should NOT be combined with --quiet as this would be nonsense\n" + " anyway.\n" + " \n" + " --dry-run\n" + " Will print the actions to stdout instead executing them.\n" + " HINT: Directory count in summary will be inaccurate in this\n" + " mode.\n" + " \n" + " --force\n" + " Same meaning as in original 'ln' command.\n" + " \n", strrchr(__FILE__,'/')+1 ); +} + + +/** returns non-zero on errors. Error messages will already be printed + * internally. */ +PRIVATE(int) +parseArgs( int argc, char**argv, BulkLn*bulkLn ){ + /* init (aka set defaults) */ + bulkLn->dataFilePath = NULL; + bulkLn->dryRun = 0; + bulkLn->isPrintEachCreateLink = 0; + bulkLn->isPrintEachMkdir = 0; + bulkLn->isPrintStatus = !0; + bulkLn->isPrintSummary = !0; + bulkLn->isRelinkExistingFiles = 0; + + /* Parse args */ + for( int i = 1 ; i < argc ; ++i ){ + char *arg = argv[i]; + if( !strcmp(arg, "--help") ){ + printHelp(); + return -1; + } + else if( !strcmp(arg, "--dry-run") ){ + bulkLn->dryRun = !0; + } + else if( !strcmp(arg, "--force") ){ + bulkLn->isRelinkExistingFiles = !0; + } + else if( !strcmp(arg, "--quiet") ){ + bulkLn->isPrintStatus = 0; + bulkLn->isPrintSummary = 0; + } + else if( !strcmp(arg, "--stdin") ){ + bulkLn->dataFilePath = "-"; + } + else if( !strcmp(arg, "--verbose") ){ + bulkLn->isPrintStatus = !0; + bulkLn->isPrintSummary = !0; + bulkLn->isPrintEachCreateLink = !0; + bulkLn->isPrintEachMkdir = !0; + } + else{ + fprintf(stderr, "EINVAL: '%s'\n", arg); + return -1; + } + } + + /* MUST specify input method. Yes there is only one input method. But + * requiring args is the simplest way to prevent damage in case someone (eg + * accidentally) invokes the utility wihout args. Further this also makes + * the utility easier to extend wihout breaking everything. */ + if( bulkLn->dataFilePath == NULL ){ + fprintf(stderr, "EINVAL: '--stdin' missing.\n"); + return -1; + } + + return 0; +} + + +/** + * Like: mkdirs -p path + * + * WARN: Passed 'path' might be temporarily changed during execution. + * Nevertheless will be in original state after return. + */ +PRIVATE(int) +mkdirs( char*path, BulkLn*bulkLn ){ + int err; + char *tmpEnd = path; + /* Backup original length so we later still can recognize the original end + * as we are going to place zeroes in the path during the run. */ + char *pathEnd = path + strlen(path); + + for(;;){ + /* Loop for each slash in the path beginning from the topmost dir toward the + * innermost dir. + * This way our path gets longer by one segment each time we call 'mkdir' below. + * This is to have the same effect as we would call 'mkdir --parents' from cli. + * In other words we make sure every parent dir exists before creating the next + * one */ + + tmpEnd = strchr(tmpEnd + 1, '/'); + if( tmpEnd == NULL ){ + /* The last (innermost) segment to create a dir for */ + tmpEnd = pathEnd; + } + + /* Temporarily zero-terminate the path so we can create the parent dir + * up to there */ + tmpEnd[0] = '\0'; + + /* Print if requested */ + if( bulkLn->dryRun || bulkLn->isPrintEachMkdir ){ + printf("mkdir(\"%s\")\n", path); + } + + /* Create dir up to that found path */ + if( ! bulkLn->dryRun ){ + /* Perform the real action */ + /* mode gets masked by umask and after that we have the defaults + * (aka what we want) */ + err = mkdir(path, 0777); + if( err ){ + if( errno == EEXIST ){ + /* Fine :) So just continue with the next one. */ + }else{ + fprintf(stderr, "mkdir(\"%s\"): %s\n", path, strerror(errno)); + err = -1; goto resolveWithErr; + } + }else{ + /* Only increment the directory counter if we really created + * the dir. Eg if it did NOT already exist */ + bulkLn->createdDirsCount += 1; + } + }else{ + /* We cannot really count how many dirs we created. Because maybe + * we are (hypothetically) creating this directory the 100th time + * now. But we have no way to tell as we do not really create em. + * So just increment to have some value at all */ + bulkLn->createdDirsCount += 1; + } + + if( tmpEnd == pathEnd ){ + /* Nothing to restore as we point to end-of-string. + * This also means we're done. So end the loop */ + break; + }else{ + /* Restore where we did cut-off the path */ + tmpEnd[0] = '/'; + /* Then loop to add and process one more segment */ + } + } + + err = 0; +resolveWithErr: + return err; +} + + +/** Like: ln srcPath dstPath */ +PRIVATE(int) +createHardlink( char*srcPath, char*dstPath, BulkLn*bulkLn ){ + int err; + + if( bulkLn->dryRun || bulkLn->isPrintEachCreateLink ){ + printf("link(\"%s\", \"%s\")\n", srcPath, dstPath); + } + + if( ! bulkLn->dryRun ){ + /* Perform the real action */ + if( bulkLn->isRelinkExistingFiles ){ + /* Delete beforehand so we can be sure 'link' does not fail due + * already existing file */ + err = unlink(dstPath); + if( err ){ + if( errno == ENOENT ){ + /* There is no such entry we could delete. So we're already + * fine :) */ + }else{ + /* Some other (unepxected) error */ + fprintf(stderr, "unlink(%s): %s\n", dstPath, strerror(errno)); + err = -1; goto resolveWithErr; + } + } + } + err = link(srcPath, dstPath); + if( err ){ + fprintf(stderr, "link(\"%s\", \"%s\"): %s\n", srcPath, dstPath, strerror(errno)); + err = -1; goto resolveWithErr; + } + } + + bulkLn->createdLinksCount += 1; + + err = 0; +resolveWithErr: + return err; +} + + +PRIVATE(int) +onPathPair( char*srcPath, char*dstPath, BulkLn*bulkLn ){ + assert(srcPath != NULL); + assert(dstPath != NULL); + assert(bulkLn != NULL); + int err; + + if( bulkLn->isPrintStatus && bulkLn->createdLinksCount % 10000 == 0 ){ + fprintf(stderr, "Created %7d links so far.\n", bulkLn->createdLinksCount); + } + + /* Search end of parent dir path */ + char *tmpEnd = strrchr(dstPath, '/'); + if( tmpEnd != NULL ){ + /* Temporarily cut-off the last segment (filename) to create the + * parent-dirs */ + tmpEnd[0] = '\0'; + /* Create missing parent dirs */ + err = mkdirs(dstPath, bulkLn); + /* Restore path */ + tmpEnd[0] = '/'; + if( err ){ err = -1; goto resolveWithErr; } + } + + err = createHardlink(srcPath, dstPath, bulkLn); + if( err ){ err = -1; goto resolveWithErr; } + + err = 0; +resolveWithErr: + return err; +} + + +PRIVATE(int) +parseDataFileAsPairPerLine( BulkLn*bulkLn ){ + int err; + size_t buf_cap = 0; + size_t buf_len = 0; + char *buf = NULL; + size_t lineNum = 0; + + for(;;){ + lineNum += 1; + + /* Read input line-by-line. Not the most elegant way to parse stuff, + * but should suffice for our use-case */ + err = getline(&buf, &buf_cap, bulkLn->dataFd); + if( err < 0 ){ + /* Error handling */ + if( feof(bulkLn->dataFd) ){ + break; /* End-Of-File. Just break off the loop */ + }else if( ferror(bulkLn->dataFd) ){ + fprintf(stderr, "getline(%s): %s\n", bulkLn->dataFilePath, strerror(errno)); + err = -1; goto resolveWithErr; + }else{ + abort(); /* I don't know how this could happen */ + } + } + buf_len = err; + + /* Extract the two paths from our line */ + char *srcPath = buf; + char *tab = memchr(buf, DATA_FILE_FIELD_SEP_CHR, buf_len); + if( tab == NULL ){ + fprintf(stderr, "Too few field separators (tab) in '%s' @ %lu\n", + bulkLn->dataFilePath, lineNum); + err = -1; goto resolveWithErr; + } + char *unwantedTab = memchr(tab + 1, DATA_FILE_FIELD_SEP_CHR, buf_len - (tab + 1 - buf)); + if( unwantedTab != NULL ){ + fprintf(stderr, "Too many field separators (tab) in '%s' line %lu\n", + bulkLn->dataFilePath, lineNum); + err = -1; goto resolveWithErr; + } + char *dstPath = tab + 1; /* <- path starts one char after the separator */ + char *dstPath_end = buf + buf_len; + for(;; --dstPath_end ){ + if( dstPath_end < buf ){ + fprintf(stderr, "IMHO cannot happen %s:%d\n", __FILE__, __LINE__); + err = -1; goto resolveWithErr; + } + if( dstPath_end[0]=='\n' || dstPath_end[0]=='\0' || dstPath_end[0]=='\r' ){ + continue; /* last char not found yet */ + } + /* 'dstPath_end' now points to the last char of our line. So add + * one to point to the 'end' */ + dstPath_end += 1; + break; + } + + /* Zero-Terminate the two strings */ + tab[0] = '\0'; + dstPath_end[0] = '\0'; + + /* Publish this pair for processing */ + err = onPathPair(srcPath, dstPath, bulkLn); + if( err ){ err = -1; goto resolveWithErr; } + } + + if( bulkLn->isPrintStatus ){ + fprintf(stderr, "Parsed %lu records from '%s'\n", lineNum - 1, bulkLn->dataFilePath); + } + + err = 0; +resolveWithErr: + free(buf); + return err; +} + + +int +bulk_ln_main( int argc, char**argv ){ + int err; + BulkLn bulkLn = {0}; + #define bulkLn (&bulkLn) + + err = parseArgs(argc, argv, bulkLn); + if( err ){ err = -1; goto resolveWithErr; } + + /* Open data source */ + if( !strcmp(bulkLn->dataFilePath, "-") ){ + bulkLn->dataFd = stdin; + }else{ + bulkLn->dataFd = fopen(bulkLn->dataFilePath, "rb"); + if( bulkLn->dataFd == NULL ){ + fprintf(stderr, "fopen(%s): %s\n", bulkLn->dataFilePath, strerror(errno)); + err = -1; goto resolveWithErr; + } + } + + err = parseDataFileAsPairPerLine(bulkLn); + if( err ){ err = -1; goto resolveWithErr; } + + if( bulkLn->isPrintSummary ){ + fprintf(stderr, "Created %d directories and linked %d files.\n", + bulkLn->createdDirsCount, bulkLn->createdLinksCount); + } + + err = 0; +resolveWithErr: + if( bulkLn->dataFd != NULL && bulkLn->dataFd != stdin ){ + fclose(bulkLn->dataFd); + } + return err; + #undef bulkLn +} + + +#if EXPOSE_MAIN +int +main( int argc, char**argv ){ + argc = bulk_ln_main(argc, argv); + if( argc < 0 ) argc = -argc; + return (argc >= 0 && argc <= 0x7F) ? argc : 1; +} +#endif + + +/*]===========================================================================]--*/ diff --git a/src/bulk_ln/bulk_ln.c b/src/bulk_ln/bulk_ln.c deleted file mode 100644 index ffd360a..0000000 --- a/src/bulk_ln/bulk_ln.c +++ /dev/null @@ -1,429 +0,0 @@ - -/* This */ -#include "bulk_ln.h" - -/* System */ -#include -#include -#include -#include -#include -#include -#include - - -#define DATA_FILE_FIELD_SEP_CHR '\t' - -typedef int bool; -typedef struct BulkLn BulkLn; - - -/* Root context */ -struct BulkLn { - - /** File (path) where we read the paths-to-link from. The special - * value - (dash) means to use the stdin stream. */ - char *dataFilePath; - - FILE *dataFd; - - /** Count of links we created */ - int createdLinksCount; - - /** Count of direcrories we created */ - int createdDirsCount; - - /** If a dry-run got requested */ - bool dryRun; - - /** If true, we will print to stdout for every link we create (like we do - * in dry run) */ - bool isPrintEachCreateLink; - - /** print every call we make to mkdir to stdout. Usually only useful for - * debugging */ - bool isPrintEachMkdir; - - /** if true, we periodically print some kind of progress to stderr */ - bool isPrintStatus; - - /** if true, we print a summary what we did at the end of the run to stderr */ - bool isPrintSummary; - - /** if true, we will override existing files. This will be enabled for - * example if --force got specified. */ - bool isRelinkExistingFiles; -}; - - - -static void printHelp(){ - printf(" \n %s @ " STR_QUOT(PROJECT_VERSION) "\n" - " \n" - " Takes paths (pairwise) from stdin (see --stdin for details) and\n" - " creates a hardlink for each pair from the 1st path to the 2nd.\n" - " \n" - " Writing a custom implementation of 'ln' got necessary as we found no\n" - " way to instruct 'ln' to create a few thousand links in an acceptable\n" - " amount of time. Remind that wheels that do not fit should be\n" - " re-invented (yes, no matter how much they already exist!). So now we\n" - " have it. Our re-invented wheel that finally fits our use-case ;) \n" - " \n" - " See also\n" - " https://pubs.opengroup.org/onlinepubs/009695399/utilities/ln.html\n" - " for info about the original 'ln'.\n" - " \n" - " Options:\n" - " \n" - " --stdin\n" - " Read the path pairs to link from stdin. The format is like:\n" - " \n" - " \n" - " \n" - " Example:\n" - " \n" - " origin/foo.txt\tnew/gugg.txt\n" - " origin/bar.txt\tnew/da.txt\n" - " \n" - " HINT: Preferred is LF. But CRLF should work too.\n" - " \n" - " --quiet\n" - " Don't print status or similar stuff. Errors will still be\n" - " printed to stderr.\n" - " \n" - " --verbose\n" - " Print stupid amount of logs. Usually only helpful for debugging.\n" - " Should NOT be combined with --quiet as this would be nonsense\n" - " anyway.\n" - " \n" - " --dry-run\n" - " Will print the actions to stdout instead executing them.\n" - " HINT: Directory count in summary will be inaccurate in this\n" - " mode.\n" - " \n" - " --force\n" - " Same meaning as in original 'ln' command.\n" - " \n", strrchr(__FILE__,'/')+1 ); -} - - -/** returns non-zero on errors. Error messages will already be printed - * internally. */ -static int parseArgs( int argc, char**argv, BulkLn*bulkLn ){ - /* init (aka set defaults) */ - bulkLn->dataFilePath = NULL; - bulkLn->dryRun = 0; - bulkLn->isPrintEachCreateLink = 0; - bulkLn->isPrintEachMkdir = 0; - bulkLn->isPrintStatus = !0; - bulkLn->isPrintSummary = !0; - bulkLn->isRelinkExistingFiles = 0; - - // Parse args - for( int i = 1 ; i < argc ; ++i ){ - char *arg = argv[i]; - if( !strcmp(arg, "--help") ){ - printHelp(); - return -1; - } - else if( !strcmp(arg, "--dry-run") ){ - bulkLn->dryRun = !0; - } - else if( !strcmp(arg, "--force") ){ - bulkLn->isRelinkExistingFiles = !0; - } - else if( !strcmp(arg, "--quiet") ){ - bulkLn->isPrintStatus = 0; - bulkLn->isPrintSummary = 0; - } - else if( !strcmp(arg, "--stdin") ){ - bulkLn->dataFilePath = "-"; - } - else if( !strcmp(arg, "--verbose") ){ - bulkLn->isPrintStatus = !0; - bulkLn->isPrintSummary = !0; - bulkLn->isPrintEachCreateLink = !0; - bulkLn->isPrintEachMkdir = !0; - } - else{ - fprintf(stderr, "EINVAL: '%s'\n", arg); - return -1; - } - } - - /* MUST specify input method. Yes there is only one input method. But - * requiring args is the simplest way to prevent damage in case someone (eg - * accidentally) invokes the utility wihout args. Further this also makes - * the utility easier to extend wihout breaking everything. */ - if( bulkLn->dataFilePath == NULL ){ - fprintf(stderr, "EINVAL: '--stdin' missing.\n"); - return -1; - } - - return 0; -} - - -/** - * Like: mkdirs -p path - * - * WARN: Passed 'path' might be temporarily changed during execution. - * Nevertheless will be in original state after return. - */ -static int mkdirs( char*path, BulkLn*bulkLn ){ - int err; - char *tmpEnd = path; - /* Backup original length so we later still can recognize the original end - * as we are going to place zeroes in the path during the run. */ - char *pathEnd = path + strlen(path); - - for(;;){ - /* Loop for each slash in the path beginning from the topmost dir toward the - * innermost dir. - * This way our path gets longer by one segment each time we call 'mkdir' below. - * This is to have the same effect as we would call 'mkdir --parents' from cli. - * In other words we make sure every parent dir exists before creating the next - * one */ - - tmpEnd = strchr(tmpEnd + 1, '/'); - if( tmpEnd == NULL ){ - /* The last (innermost) segment to create a dir for */ - tmpEnd = pathEnd; - } - - /* Temporarily zero-terminate the path so we can create the parent dir - * up to there */ - tmpEnd[0] = '\0'; - - /* Print if requested */ - if( bulkLn->dryRun || bulkLn->isPrintEachMkdir ){ - printf("mkdir(\"%s\")\n", path); - } - - /* Create dir up to that found path */ - if( ! bulkLn->dryRun ){ - /* Perform the real action */ - /* mode gets masked by umask and after that we have the defaults - * (aka what we want) */ - err = mkdir(path, 0777); - if( err ){ - if( errno == EEXIST ){ - /* Fine :) So just continue with the next one. */ - }else{ - fprintf(stderr, "mkdir(\"%s\"): %s\n", path, strerror(errno)); - err = -1; goto finally; - } - }else{ - /* Only increment the directory counter if we really created - * the dir. Eg if it did NOT already exist */ - bulkLn->createdDirsCount += 1; - } - }else{ - /* We cannot really count how many dirs we created. Because maybe - * we are (hypothetically) creating this directory the 100th time - * now. But we have no way to tell as we do not really create em. - * So just increment to have some value at all */ - bulkLn->createdDirsCount += 1; - } - - if( tmpEnd == pathEnd ){ - /* Nothing to restore as we point to end-of-string. - * This also means we're done. So end the loop */ - break; - }else{ - /* Restore where we did cut-off the path */ - tmpEnd[0] = '/'; - /* Then loop to add and process one more segment */ - } - } - - err = 0; -finally: - return err; -} - - -/** Like: ln srcPath dstPath */ -static int createHardlink( char*srcPath, char*dstPath, BulkLn*bulkLn ){ - int err; - - if( bulkLn->dryRun || bulkLn->isPrintEachCreateLink ){ - printf("link(\"%s\", \"%s\")\n", srcPath, dstPath); - } - - if( ! bulkLn->dryRun ){ - /* Perform the real action */ - if( bulkLn->isRelinkExistingFiles ){ - /* Delete beforehand so we can be sure 'link' does not fail due - * already existing file */ - err = unlink(dstPath); - if( err ){ - if( errno == ENOENT ){ - /* There is no such entry we could delete. So we're already - * fine :) */ - }else{ - /* Some other (unepxected) error */ - fprintf(stderr, "unlink(%s): %s\n", dstPath, strerror(errno)); - err = -1; goto finally; - } - } - } - err = link(srcPath, dstPath); - if( err ){ - fprintf(stderr, "link(\"%s\", \"%s\"): %s\n", srcPath, dstPath, strerror(errno)); - err = -1; goto finally; - } - } - - bulkLn->createdLinksCount += 1; - - err = 0; -finally: - return err; -} - - -static int onPathPair( char*srcPath, char*dstPath, BulkLn*bulkLn ){ - assert(srcPath != NULL); - assert(dstPath != NULL); - assert(bulkLn != NULL); - int err; - - if( bulkLn->isPrintStatus && bulkLn->createdLinksCount % 10000 == 0 ){ - fprintf(stderr, "Created %7d links so far.\n", bulkLn->createdLinksCount); - } - - /* Search end of parent dir path */ - char *tmpEnd = strrchr(dstPath, '/'); - if( tmpEnd != NULL ){ - /* Temporarily cut-off the last segment (filename) to create the - * parent-dirs */ - tmpEnd[0] = '\0'; - /* Create missing parent dirs */ - err = mkdirs(dstPath, bulkLn); - /* Restore path */ - tmpEnd[0] = '/'; - if( err ){ err = -1; goto finally; } - } - - err = createHardlink(srcPath, dstPath, bulkLn); - if( err ){ err = -1; goto finally; } - - err = 0; -finally: - return err; -} - - -static int parseDataFileAsPairPerLine( BulkLn*bulkLn ){ - int err; - size_t buf_cap = 0; - size_t buf_len = 0; - char *buf = NULL; - size_t lineNum = 0; - - for(;;){ - lineNum += 1; - - /* Read input line-by-line. Not the most elegant way to parse stuff, - * but should suffice for our use-case */ - err = getline(&buf, &buf_cap, bulkLn->dataFd); - if(unlikely( err < 0 )){ - /* Error handling */ - if( feof(bulkLn->dataFd) ){ - break; /* End-Of-File. Just break off the loop */ - }else if( ferror(bulkLn->dataFd) ){ - fprintf(stderr, "getline(%s): %s\n", bulkLn->dataFilePath, strerror(errno)); - err = -1; goto finally; - }else{ - abort(); /* I don't know how this could happen */ - } - } - buf_len = err; - - /* Extract the two paths from our line */ - char *srcPath = buf; - char *tab = memchr(buf, DATA_FILE_FIELD_SEP_CHR, buf_len); - if( tab == NULL ){ - fprintf(stderr, "Too few field separators (tab) in '%s' @ %lu\n", - bulkLn->dataFilePath, lineNum); - err = -1; goto finally; - } - char *unwantedTab = memchr(tab + 1, DATA_FILE_FIELD_SEP_CHR, buf_len - (tab + 1 - buf)); - if( unwantedTab != NULL ){ - fprintf(stderr, "Too many field separators (tab) in '%s' line %lu\n", - bulkLn->dataFilePath, lineNum); - err = -1; goto finally; - } - char *dstPath = tab + 1; /* <- path starts one char after the separator */ - char *dstPath_end = buf + buf_len; - for(;; --dstPath_end ){ - if(unlikely( dstPath_end < buf )){ - fprintf(stderr, "IMHO cannot happen %s:%d\n", __FILE__, __LINE__); - err = -1; goto finally; - } - if( dstPath_end[0]=='\n' || dstPath_end[0]=='\0' || dstPath_end[0]=='\r' ){ - continue; /* last char not found yet */ - } - /* 'dstPath_end' now points to the last char of our line. So add - * one to point to the 'end' */ - dstPath_end += 1; - break; - } - - /* Zero-Terminate the two strings */ - tab[0] = '\0'; - dstPath_end[0] = '\0'; - - /* Publish this pair for processing */ - err = onPathPair(srcPath, dstPath, bulkLn); - if( err ){ err = -1; goto finally; } - } - - if( bulkLn->isPrintStatus ){ - fprintf(stderr, "Parsed %lu records from '%s'\n", lineNum - 1, bulkLn->dataFilePath); - } - - err = 0; -finally: - free(buf); - return err; -} - - -int bulk_ln_main( int argc, char**argv ){ - int err; - BulkLn bulkLn = {0}; - #define bulkLn (&bulkLn) - - err = parseArgs(argc, argv, bulkLn); - if( err ){ err = -1; goto finally; } - - /* Open data source */ - if( !strcmp(bulkLn->dataFilePath, "-") ){ - bulkLn->dataFd = stdin; - }else{ - bulkLn->dataFd = fopen(bulkLn->dataFilePath, "rb"); - if( bulkLn->dataFd == NULL ){ - fprintf(stderr, "fopen(%s): %s\n", bulkLn->dataFilePath, strerror(errno)); - err = -1; goto finally; - } - } - - err = parseDataFileAsPairPerLine(bulkLn); - if( err ){ err = -1; goto finally; } - - if( bulkLn->isPrintSummary ){ - fprintf(stderr, "Created %d directories and linked %d files.\n", - bulkLn->createdDirsCount, bulkLn->createdLinksCount); - } - - err = 0; -finally: - if( bulkLn->dataFd != NULL && bulkLn->dataFd != stdin ){ - fclose(bulkLn->dataFd); - } - return -err; - #undef bulkLn -} - diff --git a/src/bulk_ln/bulk_ln.h b/src/bulk_ln/bulk_ln.h deleted file mode 100644 index 6054386..0000000 --- a/src/bulk_ln/bulk_ln.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef INCGUARD_87768d11a9f7d831f2a8b9d0ab7efa9c -#define INCGUARD_87768d11a9f7d831f2a8b9d0ab7efa9c - -#include "commonbase.h" - - -int bulk_ln_main( int argc, char**argv ); - - -#endif /* INCGUARD_87768d11a9f7d831f2a8b9d0ab7efa9c */ diff --git a/src/bulk_ln/bulk_ln_main.c b/src/bulk_ln/bulk_ln_main.c deleted file mode 100644 index a3ac228..0000000 --- a/src/bulk_ln/bulk_ln_main.c +++ /dev/null @@ -1,10 +0,0 @@ - -#include "bulk_ln.h" - -int main( int argc, char**argv ){ - int err = bulk_ln_main(argc, argv); - /* Ensure to return 7-bit value only (See POSIX) */ - if( err < 0 ) err = -err; - return err > 127 ? 1 : err; -} - diff --git a/src/common/commonbase.h b/src/common/commonbase.h deleted file mode 100644 index 497d3fa..0000000 --- a/src/common/commonbase.h +++ /dev/null @@ -1,23 +0,0 @@ - -/* - * common config for project. Here goes stuff like feature-test-macros etc. - * - * Every header file MUST include this file AS THE 1ST include. - */ - - -#define _POSIX_C_SOURCE 200809L - - -#define STR_QUOT_IAHGEWIH(s) #s -#define STR_QUOT(s) STR_QUOT_IAHGEWIH(s) - -#define STR_CAT(a, b) a ## b - -#ifndef likely -# define likely(a) (a) -#endif -#ifndef unlikely -# define unlikely(a) (a) -#endif - -- cgit v1.3.1