//===-- 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_timespec.h"
#include "hdr/types/struct_timeval.h"

#include "src/__support/OSUtil/syscall.h"
#include "src/__support/common.h"

#include "src/__support/libc_errno.h"

#include <sys/syscall.h>

namespace LIBC_NAMESPACE_DECL {

#ifdef SYS_utimes
constexpr auto UTIMES_SYSCALL_ID = SYS_utimes;
#elif defined(SYS_utimensat)
constexpr auto UTIMES_SYSCALL_ID = SYS_utimensat;
#elif defined(SYS_utimensat_time64)
constexpr auto UTIMES_SYSCALL_ID = SYS_utimensat_time64;
#else
#error "utimes, utimensat, utimensat_time64,  syscalls not available."
#endif

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>(UTIMES_SYSCALL_ID, path, times);
#elif defined(SYS_utimensat) || defined(SYS_utimensat_time64)
  // the utimensat syscall requires a timespec struct, not timeval.
  struct timespec ts[2];
  struct timespec *ts_ptr = nullptr; // default value if times is nullptr

  // convert the microsec values in timeval struct times
  // to nanosecond values in timespec struct ts
  if (times != nullptr) {

    // 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 nullptr, ts_ptr remains nullptr, 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>(UTIMES_SYSCALL_ID, AT_FDCWD, path,
                                          ts_ptr, 0);
#endif // SYS_utimensat

  if (ret < 0) {
    libc_errno = -ret;
    return -1;
  }

  return 0;
}
} // namespace LIBC_NAMESPACE_DECL
