(original) (raw)
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt index eccd222fa123e..1ac3a781d5279 100644 --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -288,6 +288,9 @@ set(TARGET_LIBC_ENTRYPOINTS libc.src.sys.statvfs.fstatvfs libc.src.sys.statvfs.statvfs + # sys/utimes.h entrypoints + libc.src.sys.time.utimes + # sys/utsname.h entrypoints libc.src.sys.utsname.uname diff --git a/libc/include/sys/time.yaml b/libc/include/sys/time.yaml index ca497bbe92995..92ab9a467f33a 100644 --- a/libc/include/sys/time.yaml +++ b/libc/include/sys/time.yaml @@ -5,5 +5,10 @@ macros: [] types: - type_name: struct_timeval enums: [] -functions: [] objects: [] +functions: + - name: utimes + return_type: int + arguments: + - type: const char* + - type: const struct timeval* diff --git a/libc/src/sys/CMakeLists.txt b/libc/src/sys/CMakeLists.txt index bb177f11c6d62..9a73b80d35d2f 100644 --- a/libc/src/sys/CMakeLists.txt +++ b/libc/src/sys/CMakeLists.txt @@ -8,6 +8,7 @@ add_subdirectory(socket) add_subdirectory(sendfile) add_subdirectory(stat) add_subdirectory(statvfs) +add_subdirectory(time) add_subdirectory(utsname) add_subdirectory(wait) add_subdirectory(prctl) diff --git a/libc/src/sys/time/CMakeLists.txt b/libc/src/sys/time/CMakeLists.txt new file mode 100644 index 0000000000000..f599cddaaeeb3 --- /dev/null +++ b/libc/src/sys/time/CMakeLists.txt @@ -0,0 +1,10 @@ +if(EXISTS CMAKECURRENTSOURCEDIR/{CMAKE_CURRENT_SOURCE_DIR}/CMAKECURRENTSOURCEDIR/{LIBC_TARGET_OS}) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS}) +endif() + +add_entrypoint_object( + utimes + ALIAS + DEPENDS + .${LIBC_TARGET_OS}.utimes +) diff --git a/libc/src/sys/time/linux/CMakeLists.txt b/libc/src/sys/time/linux/CMakeLists.txt new file mode 100644 index 0000000000000..506001e5c9fd2 --- /dev/null +++ b/libc/src/sys/time/linux/CMakeLists.txt @@ -0,0 +1,16 @@ +add_entrypoint_object( + utimes + SRCS + utimes.cpp + HDRS + ../utimes.h + DEPENDS + libc.hdr.types.struct_timeval + libc.hdr.fcntl_macros + libc.src.__support.OSUtil.osutil + libc.include.sys_stat + libc.include.sys_syscall + libc.include.fcntl + libc.src.__support.OSUtil.osutil + libc.src.errno.errno +) diff --git a/libc/src/sys/time/linux/utimes.cpp b/libc/src/sys/time/linux/utimes.cpp new file mode 100644 index 0000000000000..1cc5a8344e91a --- /dev/null +++ b/libc/src/sys/time/linux/utimes.cpp @@ -0,0 +1,76 @@ +//===-- Linux implementation of utimes ------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "src/sys/time/utimes.h" + +#include "hdr/fcntl_macros.h" +#include "hdr/types/struct_timeval.h" + +#include "src/__support/OSUtil/syscall.h" +#include "src/__support/common.h" + +#include "src/errno/libc_errno.h" + +#include + +namespace LIBC_NAMESPACE_DECL { + +LLVM_LIBC_FUNCTION(int, utimes, + (const char *path, const struct timeval times[2])) { + int ret; + +#ifdef SYS_utimes + // No need to define a timespec struct, use the syscall directly. + ret = LIBC_NAMESPACE::syscall_impl(SYS_utimes, path, times); +#elif defined(SYS_utimensat) + // the utimensat syscall requires a timespec struct, not timeval. + struct timespec ts[2]; + struct timespec *ts_ptr = nullptr; // default value if times is NULL + + // convert the microsec values in timeval struct times + // to nanosecond values in timespec struct ts + if (times != NULL) { + + // ensure consistent values + if ((times[0].tv_usec < 0 || times[1].tv_usec < 0) || + (times[0].tv_usec >= 1000000 || times[1].tv_usec >= 1000000)) { + libc_errno = EINVAL; + return -1; + } + + // set seconds in ts + ts[0].tv_sec = times[0].tv_sec; + ts[1].tv_sec = times[1].tv_sec; + + // convert u-seconds to nanoseconds + ts[0].tv_nsec = times[0].tv_usec * 1000; + ts[1].tv_nsec = times[1].tv_usec * 1000; + + ts_ptr = ts; + } + + // If times was NULL, ts_ptr remains NULL, which utimensat interprets + // as setting times to the current time. + + // utimensat syscall. + // flags=0 means don't follow symlinks (like utimes) + ret = LIBC_NAMESPACE::syscall_impl(SYS_utimensat, AT_FDCWD, path, ts_ptr, + 0); + +#else +#error "utimensat and utimes syscalls not available." +#endif // SYS_utimensat + + if (ret < 0) { + libc_errno = -ret; + return -1; + } + + return 0; +} +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/sys/time/utimes.h b/libc/src/sys/time/utimes.h new file mode 100644 index 0000000000000..6e19e412d69ac --- /dev/null +++ b/libc/src/sys/time/utimes.h @@ -0,0 +1,21 @@ +//===-- Implementation header for utimes ----------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_SYS_TIME_UTIMES_H +#define LLVM_LIBC_SRC_SYS_TIME_UTIMES_H + +#include "hdr/types/struct_timeval.h" +#include "src/__support/macros/config.h" + +namespace LIBC_NAMESPACE_DECL { + +int utimes(const char *path, const struct timeval times[2]); + +} // namespace LIBC_NAMESPACE_DECL + +#endif // LLVM_LIBC_SRC_SYS_TIME_UTIMES_H diff --git a/libc/test/src/sys/CMakeLists.txt b/libc/test/src/sys/CMakeLists.txt index 9e9293aab628f..224cc7905ad31 100644 --- a/libc/test/src/sys/CMakeLists.txt +++ b/libc/test/src/sys/CMakeLists.txt @@ -12,3 +12,4 @@ add_subdirectory(prctl) add_subdirectory(auxv) add_subdirectory(epoll) add_subdirectory(uio) +add_subdirectory(time) diff --git a/libc/test/src/sys/time/CMakeLists.txt b/libc/test/src/sys/time/CMakeLists.txt new file mode 100644 index 0000000000000..c092d33e43d85 --- /dev/null +++ b/libc/test/src/sys/time/CMakeLists.txt @@ -0,0 +1,19 @@ +add_custom_target(libc_sys_time_unittests) + +add_libc_unittest( + utimes_test + SUITE + libc_sys_time_unittests + SRCS + utimes_test.cpp + DEPENDS + libc.hdr.fcntl_macros + libc.src.errno.errno + libc.src.fcntl.open + libc.src.sys.time.utimes + libc.src.unistd.close + libc.src.unistd.read + libc.src.unistd.write + libc.src.stdio.remove + libc.src.sys.stat.stat +) diff --git a/libc/test/src/sys/time/utimes_test.cpp b/libc/test/src/sys/time/utimes_test.cpp new file mode 100644 index 0000000000000..b97befb8626e3 --- /dev/null +++ b/libc/test/src/sys/time/utimes_test.cpp @@ -0,0 +1,92 @@ +//===-- Unittests for utimes ----------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "hdr/fcntl_macros.h" +#include "hdr/types/struct_timeval.h" +#include "src/errno/libc_errno.h" +#include "src/fcntl/open.h" +#include "src/stdio/remove.h" +#include "src/sys/stat/stat.h" +#include "src/sys/time/utimes.h" +#include "src/unistd/close.h" +#include "test/UnitTest/ErrnoSetterMatcher.h" +#include "test/UnitTest/Test.h" + +constexpr const char *FILE_PATH = "utimes.test"; + +// SUCCESS: Takes a file and successfully updates +// its last access and modified times. +TEST(LlvmLibcUtimesTest, ChangeTimesSpecific) { + using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds; + + auto TEST_FILE = libc_make_test_file_path(FILE_PATH); + int fd = LIBC_NAMESPACE::open(TEST_FILE, O_WRONLY | O_CREAT); + ASSERT_GT(fd, 0); + ASSERT_THAT(LIBC_NAMESPACE::close(fd), Succeeds(0)); + + // make a dummy timeval struct + struct timeval times[2]; + times[0].tv_sec = 54321; + times[0].tv_usec = 12345; + times[1].tv_sec = 43210; + times[1].tv_usec = 23456; + + // ensure utimes succeeds + ASSERT_THAT(LIBC_NAMESPACE::utimes(FILE_PATH, times), Succeeds(0)); + + // verify the times values against stat of the TEST_FILE + struct stat statbuf; + ASSERT_EQ(LIBC_NAMESPACE::stat(FILE_PATH, &statbuf), 0); + + // seconds + ASSERT_EQ(statbuf.st_atim.tv_sec, times[0].tv_sec); + ASSERT_EQ(statbuf.st_mtim.tv_sec, times[1].tv_sec); + + // microseconds + ASSERT_EQ(statbuf.st_atim.tv_nsec, + static_cast(times[0].tv_usec * 1000)); + ASSERT_EQ(statbuf.st_mtim.tv_nsec, + static_cast(times[1].tv_usec * 1000)); + + ASSERT_THAT(LIBC_NAMESPACE::remove(TEST_FILE), Succeeds(0)); +} + +// FAILURE: Invalid values in the timeval struct +// to check that utimes rejects it. +TEST(LlvmLibcUtimesTest, InvalidMicroseconds) { + using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails; + using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds; + + auto TEST_FILE = libc_make_test_file_path(FILE_PATH); + int fd = LIBC_NAMESPACE::open(TEST_FILE, O_WRONLY | O_CREAT); + ASSERT_GT(fd, 0); + ASSERT_THAT(LIBC_NAMESPACE::close(fd), Succeeds(0)); + + // make a dummy timeval struct + // populated with bad usec values + struct timeval times[2]; + times[0].tv_sec = 54321; + times[0].tv_usec = 4567; + times[1].tv_sec = 43210; + times[1].tv_usec = 1000000; // invalid + + // ensure utimes fails + ASSERT_THAT(LIBC_NAMESPACE::utimes(FILE_PATH, times), Fails(EINVAL)); + + // check for failure on + // the other possible bad values + + times[0].tv_sec = 54321; + times[0].tv_usec = -4567; // invalid + times[1].tv_sec = 43210; + times[1].tv_usec = 1000; + + // ensure utimes fails once more + ASSERT_THAT(LIBC_NAMESPACE::utimes(FILE_PATH, times), Fails(EINVAL)); + ASSERT_THAT(LIBC_NAMESPACE::remove(TEST_FILE), Succeeds(0)); +}