diff --git a/Makefile b/Makefile index e11340c1ae..ea1156b683 100644 --- a/Makefile +++ b/Makefile @@ -2210,6 +2210,11 @@ ifndef HAVE_PLATFORM_PROCINFO COMPAT_OBJS += compat/stub/procinfo.o endif +ifdef NO_PATHCONF + COMPAT_CFLAGS += -DNO_PATHCONF + COMPAT_OBJS += compat/pathconf.o +endif + ifdef RUNTIME_PREFIX ifdef HAVE_BSD_KERN_PROC_SYSCTL diff --git a/compat/pathconf.c b/compat/pathconf.c new file mode 100644 index 0000000000..37500cfa0d --- /dev/null +++ b/compat/pathconf.c @@ -0,0 +1,10 @@ +#include "git-compat-util.h" + +/* + * Minimal stub for platforms without pathconf() (e.g. Windows), + * to fall back to NAME_MAX from limits.h or compat/posix.h. + */ +long git_pathconf(const char *path UNUSED, int name UNUSED) +{ + return -1; +} diff --git a/compat/posix.h b/compat/posix.h index 067a00f33b..aa050fd58c 100644 --- a/compat/posix.h +++ b/compat/posix.h @@ -250,6 +250,14 @@ char *gitdirname(char *); #define NAME_MAX 255 #endif +#ifdef NO_PATHCONF +#ifndef _PC_NAME_MAX +#define _PC_NAME_MAX 1 /* dummy value, only used for git_pathconf */ +#endif +#define pathconf(a,b) git_pathconf(a,b) +long git_pathconf(const char *path, int name); +#endif + typedef uintmax_t timestamp_t; #define PRItime PRIuMAX #define parse_timestamp strtoumax diff --git a/config.mak.uname b/config.mak.uname index 1691c6ae6e..49ba3de39d 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -473,6 +473,7 @@ ifeq ($(uname_S),Windows) NEEDS_CRYPTO_WITH_SSL = YesPlease NO_LIBGEN_H = YesPlease NO_POLL = YesPlease + NO_PATHCONF = YesPlease NO_SYMLINK_HEAD = YesPlease NO_IPV6 = YesPlease NO_SETENV = YesPlease @@ -688,6 +689,7 @@ ifeq ($(uname_S),MINGW) NEEDS_CRYPTO_WITH_SSL = YesPlease NO_LIBGEN_H = YesPlease NO_POLL = YesPlease + NO_PATHCONF = YesPlease NO_SYMLINK_HEAD = YesPlease NO_SETENV = YesPlease NO_STRCASESTR = YesPlease diff --git a/meson.build b/meson.build index 5dd299b496..3ae5c391a9 100644 --- a/meson.build +++ b/meson.build @@ -1392,6 +1392,7 @@ checkfuncs = { 'initgroups' : [], 'strtoumax' : ['strtoumax.c', 'strtoimax.c'], 'pread' : ['pread.c'], + 'pathconf' : ['pathconf.c'], } if host_machine.system() == 'windows' diff --git a/submodule.c b/submodule.c index f8eb760215..01d6416ff2 100644 --- a/submodule.c +++ b/submodule.c @@ -2625,13 +2625,29 @@ void submodule_name_to_gitdir(struct strbuf *buf, struct repository *r, if (the_repository->repository_format_submodule_encoding) { struct strbuf tmp = STRBUF_INIT; + size_t base_len; + long name_max; strbuf_reset(buf); repo_git_path_append(r, buf, "modules/"); + base_len = buf->len; strbuf_addstr_urlencode(&tmp, submodule_name, is_rfc3986_unreserved); strbuf_addstr_case_encode(buf, tmp.buf); + /* Ensure final path length is below NAME_MAX after encoding */ + name_max = pathconf(buf->buf, _PC_NAME_MAX); + if (name_max == -1) + name_max = NAME_MAX; + + if (buf->len - base_len > name_max) + /* + * TODO: make this smarter; instead of erroring out, maybe we could trim or + * shard the gitdir names to make them fit under NAME_MAX. + */ + die(_("submodule name %s is too long (%"PRIuMAX" bytes, limit %"PRIuMAX")"), + buf->buf, (uintmax_t)buf->len - base_len, (uintmax_t)name_max); + strbuf_release(&tmp); } } diff --git a/t/t7425-submodule-encoding.sh b/t/t7425-submodule-encoding.sh index 4ea385d882..8041781491 100755 --- a/t/t7425-submodule-encoding.sh +++ b/t/t7425-submodule-encoding.sh @@ -143,4 +143,20 @@ test_expect_success 'submodule git dir nesting detection must work with parallel verify_submodule_gitdir_path clone_parallel hippo/hooks modules/hippo%2fhooks ' +test_expect_success 'submodule encoded name exceeds max name limit' ' + ( + cd main && + + # find the system NAME_MAX (fall back to 255 if unknown) + name_max=$(getconf NAME_MAX . 2>/dev/null || echo 255) && + + # each "%" char encodes to "%25" (3 chars), ensure we exceed NAME_MAX + count=$((name_max + 10)) && + longname=$(test_seq -f "%%%0.s" 1 $count | tr -d "\n") && + + test_must_fail git submodule add ../new-sub "$longname" 2>err && + test_grep "fatal: submodule name .* is too long" err + ) +' + test_done