compat/posix: introduce utimensat(2) wrapper
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 <vonosmas@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>jch
parent
1a3e64c6c4
commit
be2b0a8f00
6
Makefile
6
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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=],
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
Loading…
Reference in New Issue