[PATCH 1/3] compat/posix: introduce utimensat(2) wrapper
"Alexey Samsonov via GitGitGadget" <[email protected]>
| Newsgroups | org.kernel.vger.git |
|---|---|
| Message-ID | <13aa80bc0167aae05498bac1c59846274ee00e9d.1787322203.git.gitgitgadget@gmail.com> |
From: Alexey Samsonov <[email protected]> In POSIX.1-2008, utime(3p) was marked as obsolescent in favor of utimensat(2) and futimens(2). In the recent POSIX.1-2024 (Issue 8) specification, <utime.h> and utime(3p) were officially removed. utimensat(2) operates on `struct timespec` rather than the second-only `struct utimbuf`, allowing sub-second timestamp updates while also providing support for UTIME_NOW and UTIME_OMIT flags to selectively update or preserve individual access and modification timestamps. Introduce a compatibility layer for utimensat(2): - Provide fallback definitions for AT_FDCWD, UTIME_NOW, and UTIME_OMIT in case the system headers lack them. - Introduce `ST_ATIME_NSEC(st)` to complement `ST_MTIME_NSEC(st)` and `ST_CTIME_NSEC(st)`. - Implement `git_utimensat()` in `compat/utimensat.c` as a fallback using utimes(2) on platforms that define NO_UTIMENSAT. - Implement `mingw_utimensat()` in `compat/mingw.c` converting `struct timespec` to Windows FILETIME with 100ns precision. - Wire up NO_UTIMENSAT support in Makefile, meson.build, contrib/buildsystems/CMakeLists.txt, and configure.ac. Subsequent commits will migrate callers across the codebase to utimensat(2) and drop the legacy <utime.h> header. Signed-off-by: Alexey Samsonov <[email protected]> --- Makefile | 6 ++++ compat/mingw-posix.h | 2 ++ compat/mingw.c | 52 +++++++++++++++++++++++++---- compat/posix.h | 21 ++++++++++++ compat/utimensat.c | 39 ++++++++++++++++++++++ configure.ac | 6 ++++ contrib/buildsystems/CMakeLists.txt | 8 +++-- meson.build | 2 ++ 8 files changed, 127 insertions(+), 9 deletions(-) create mode 100644 compat/utimensat.c diff --git a/Makefile b/Makefile index d4b775953d..64909d48b2 100644 --- a/Makefile +++ b/Makefile @@ -70,6 +70,8 @@ include shared.mak # # Define NO_MKDTEMP if you don't have mkdtemp in the C library. # +# Define NO_UTIMENSAT if you don't have utimensat. +# # Define MKDIR_WO_TRAILING_SLASH if your mkdir() can't deal with trailing slash. # # Define NO_GECOS_IN_PWENT if you don't have pw_gecos in struct passwd @@ -2049,6 +2051,10 @@ ifdef NO_WRITEV COMPAT_CFLAGS += -DNO_WRITEV COMPAT_OBJS += compat/writev.o endif +ifdef NO_UTIMENSAT + COMPAT_CFLAGS += -DNO_UTIMENSAT + COMPAT_OBJS += compat/utimensat.o +endif ifdef NO_FAST_WORKING_DIRECTORY BASIC_CFLAGS += -DNO_FAST_WORKING_DIRECTORY endif diff --git a/compat/mingw-posix.h b/compat/mingw-posix.h index 2d989fd762..aab91d76db 100644 --- a/compat/mingw-posix.h +++ b/compat/mingw-posix.h @@ -386,6 +386,8 @@ int mingw_fstat(int fd, struct stat *buf); int mingw_utime(const char *file_name, const struct utimbuf *times); #define utime mingw_utime +int mingw_utimensat(int fd, const char *path, const struct timespec times[2], int flag); +#define utimensat mingw_utimensat size_t mingw_strftime(char *s, size_t max, const char *format, const struct tm *tm); #define strftime mingw_strftime diff --git a/compat/mingw.c b/compat/mingw.c index 4c2f26d454..d09a976191 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -1391,22 +1391,33 @@ int mingw_fstat(int fd, struct stat *buf) } } -static inline void time_t_to_filetime(time_t t, FILETIME *ft) +static inline void timespec_to_filetime(const struct timespec *ts, FILETIME *ft) { - long long winTime = t * 10000000LL + 116444736000000000LL; + long long winTime = (long long)ts->tv_sec * 10000000LL + (ts->tv_nsec / 100) + 116444736000000000LL; ft->dwLowDateTime = winTime; ft->dwHighDateTime = winTime >> 32; } -int mingw_utime (const char *file_name, const struct utimbuf *times) +int mingw_utimensat(int fd, const char *path, const struct timespec times[2], int flag) { FILETIME mft, aft; + FILETIME *paft = &aft, *pmft = &mft; int rc; DWORD attrs; wchar_t wfilename[MAX_PATH]; HANDLE osfilehandle; - if (xutftowcs_path(wfilename, file_name) < 0) + if (fd != AT_FDCWD) { + errno = ENOSYS; + return -1; + } + + if (flag) { + errno = ENOSYS; + return -1; + } + + if (xutftowcs_path(wfilename, path) < 0) return -1; /* must have write permission */ @@ -1433,14 +1444,25 @@ int mingw_utime (const char *file_name, const struct utimbuf *times) } if (times) { - time_t_to_filetime(times->modtime, &mft); - time_t_to_filetime(times->actime, &aft); + if (times[0].tv_nsec == UTIME_NOW) + GetSystemTimeAsFileTime(&aft); + else if (times[0].tv_nsec == UTIME_OMIT) + paft = NULL; + else + timespec_to_filetime(×[0], &aft); + + if (times[1].tv_nsec == UTIME_NOW) + GetSystemTimeAsFileTime(&mft); + else if (times[1].tv_nsec == UTIME_OMIT) + pmft = NULL; + else + timespec_to_filetime(×[1], &mft); } else { GetSystemTimeAsFileTime(&mft); aft = mft; } - if (!SetFileTime(osfilehandle, NULL, &aft, &mft)) { + if (!SetFileTime(osfilehandle, NULL, paft, pmft)) { errno = EINVAL; rc = -1; } else @@ -1458,6 +1480,22 @@ revert_attrs: return rc; } +int mingw_utime(const char *file_name, const struct utimbuf *times) +{ + struct timespec ts[2]; + struct timespec *tsp = NULL; + + if (times) { + ts[0].tv_sec = times->actime; + ts[0].tv_nsec = 0; + ts[1].tv_sec = times->modtime; + ts[1].tv_nsec = 0; + tsp = ts; + } + + return mingw_utimensat(AT_FDCWD, file_name, tsp, 0); +} + #undef strftime size_t mingw_strftime(char *s, size_t max, const char *format, const struct tm *tm) diff --git a/compat/posix.h b/compat/posix.h index 71cc731620..3cac1751aa 100644 --- a/compat/posix.h +++ b/compat/posix.h @@ -348,6 +348,24 @@ struct git_iovec { ssize_t git_writev(int fd, const struct iovec *iov, int iovcnt); #endif +#ifndef AT_FDCWD +#define AT_FDCWD (-100) +#endif +#ifndef UTIME_NOW +#define UTIME_NOW ((1L << 30) - 1L) +#endif +#ifndef UTIME_OMIT +#define UTIME_OMIT ((1L << 30) - 2L) +#endif + +#ifdef NO_UTIMENSAT +#ifdef utimensat +#undef utimensat +#endif +#define utimensat git_utimensat +int git_utimensat(int fd, const char *path, const struct timespec times[2], int flag); +#endif + #ifdef NO_SETENV #define setenv gitsetenv int gitsetenv(const char *, const char *, int); @@ -502,13 +520,16 @@ int git_qsort_s(void *base, size_t nmemb, size_t size, #ifdef NO_NSEC #undef USE_NSEC +#define ST_ATIME_NSEC(st) 0 #define ST_CTIME_NSEC(st) 0 #define ST_MTIME_NSEC(st) 0 #else #ifdef USE_ST_TIMESPEC +#define ST_ATIME_NSEC(st) ((unsigned int)((st).st_atimespec.tv_nsec)) #define ST_CTIME_NSEC(st) ((unsigned int)((st).st_ctimespec.tv_nsec)) #define ST_MTIME_NSEC(st) ((unsigned int)((st).st_mtimespec.tv_nsec)) #else +#define ST_ATIME_NSEC(st) ((unsigned int)((st).st_atim.tv_nsec)) #define ST_CTIME_NSEC(st) ((unsigned int)((st).st_ctim.tv_nsec)) #define ST_MTIME_NSEC(st) ((unsigned int)((st).st_mtim.tv_nsec)) #endif diff --git a/compat/utimensat.c b/compat/utimensat.c new file mode 100644 index 0000000000..e4c8e8d0b6 --- /dev/null +++ b/compat/utimensat.c @@ -0,0 +1,39 @@ +#include "../git-compat-util.h" + +int git_utimensat(int fd, const char *path, const struct timespec times[2], int flag) +{ + struct timeval tv[2]; + struct timeval *tvp = NULL; + + if (fd != AT_FDCWD) { + errno = ENOSYS; + return -1; + } + + if (flag) { + errno = ENOSYS; + return -1; + } + + if (times) { + for (int i = 0; i < 2; i++) { + if (times[i].tv_nsec == UTIME_NOW) { + struct timeval now; + gettimeofday(&now, NULL); + tv[i] = now; + } else if (times[i].tv_nsec == UTIME_OMIT) { + struct stat st; + if (stat(path, &st) < 0) + return -1; + tv[i].tv_sec = (i == 0) ? st.st_atime : st.st_mtime; + tv[i].tv_usec = (i == 0) ? ST_ATIME_NSEC(st) / 1000 : ST_MTIME_NSEC(st) / 1000; + } else { + tv[i].tv_sec = times[i].tv_sec; + tv[i].tv_usec = times[i].tv_nsec / 1000; + } + } + tvp = tv; + } + + return utimes(path, tvp); +} diff --git a/configure.ac b/configure.ac index cfb50112bf..a37a53f5b5 100644 --- a/configure.ac +++ b/configure.ac @@ -1146,6 +1146,12 @@ GIT_CHECK_FUNC(mkdtemp, [NO_MKDTEMP=YesPlease]) GIT_CONF_SUBST([NO_MKDTEMP]) # +# Define NO_UTIMENSAT if you don't have utimensat in the C library. +GIT_CHECK_FUNC(utimensat, +[NO_UTIMENSAT=], +[NO_UTIMENSAT=YesPlease]) +GIT_CONF_SUBST([NO_UTIMENSAT]) +# # Define NO_INITGROUPS if you don't have initgroups in the C library. GIT_CHECK_FUNC(initgroups, [NO_INITGROUPS=], diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt index 8f56203f34..bb1d96802d 100644 --- a/contrib/buildsystems/CMakeLists.txt +++ b/contrib/buildsystems/CMakeLists.txt @@ -380,9 +380,9 @@ set(function_checks strcasestr memmem strlcpy strtoimax strtoumax strtoull setenv mkdtemp poll pread memmem writev) -#unsetenv,hstrerror are incompatible with windows build +#unsetenv,hstrerror,utimensat are incompatible with windows build (provided by compat/mingw.c) if(NOT WIN32) - list(APPEND function_checks unsetenv hstrerror) + list(APPEND function_checks unsetenv hstrerror utimensat) endif() foreach(f ${function_checks}) @@ -428,6 +428,10 @@ if(NOT HAVE_WRITEV) endif() if(NOT WIN32) + if(NOT HAVE_UTIMENSAT) + list(APPEND compat_SOURCES compat/utimensat.c) + endif() + if(NOT HAVE_UNSETENV) list(APPEND compat_SOURCES compat/unsetenv.c) endif() diff --git a/meson.build b/meson.build index d86f2acd2b..a98f63a46c 100644 --- a/meson.build +++ b/meson.build @@ -1475,6 +1475,8 @@ else 'unsetenv' : ['unsetenv.c'], # provided by compat/mingw.c. 'getpagesize' : [], + # provided by compat/mingw.c. + 'utimensat' : ['utimensat.c'], } if get_option('b_sanitize').contains('address') or get_option('b_sanitize').contains('leak') -- gitgitgadget