Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[libc] Implemented utimes (Issue #133953) #134167

Merged
merged 8 commits into from
Apr 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions libc/config/linux/x86_64/entrypoints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
7 changes: 6 additions & 1 deletion libc/include/sys/time.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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*
1 change: 1 addition & 0 deletions libc/src/sys/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
10 changes: 10 additions & 0 deletions libc/src/sys/time/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
endif()

add_entrypoint_object(
utimes
ALIAS
DEPENDS
.${LIBC_TARGET_OS}.utimes
)
16 changes: 16 additions & 0 deletions libc/src/sys/time/linux/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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
)
76 changes: 76 additions & 0 deletions libc/src/sys/time/linux/utimes.cpp
Original file line number Diff line number Diff line change
@@ -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 <sys/syscall.h>

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<int>(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<int>(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
21 changes: 21 additions & 0 deletions libc/src/sys/time/utimes.h
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions libc/test/src/sys/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ add_subdirectory(prctl)
add_subdirectory(auxv)
add_subdirectory(epoll)
add_subdirectory(uio)
add_subdirectory(time)
19 changes: 19 additions & 0 deletions libc/test/src/sys/time/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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
)
92 changes: 92 additions & 0 deletions libc/test/src/sys/time/utimes_test.cpp
Original file line number Diff line number Diff line change
@@ -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<long>(times[0].tv_usec * 1000));
ASSERT_EQ(statbuf.st_mtim.tv_nsec,
static_cast<long>(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));
}
Loading