{
internal static partial class Sys
{
- internal struct UTimBuf
+ internal struct TimeSpec
{
- internal long AcTime;
- internal long ModTime;
+ internal long TvSec;
+ internal long TvNsec;
}
/// <summary>
/// <returns>
/// Returns 0 on success; otherwise, returns -1
/// </returns>
- [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_UTime", SetLastError = true)]
- internal static extern int UTime(string path, ref UTimBuf time);
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_UTimensat", SetLastError = true)]
+ internal static extern int UTimensat(string path, ref TimeSpec times);
}
}
#cmakedefine01 HAVE_CRT_EXTERNS_H
#cmakedefine01 HAVE_GETDOMAINNAME
#cmakedefine01 HAVE_UNAME
+#cmakedefine01 HAVE_UTIMENSAT
#cmakedefine01 HAVE_FUTIMES
#cmakedefine01 HAVE_FUTIMENS
#cmakedefine01 HAVE_MKSTEMPS
#include <assert.h>
#include <utime.h>
#include <time.h>
+#include <sys/stat.h>
+#include <fcntl.h>
#include <sys/time.h>
#if HAVE_MACH_ABSOLUTE_TIME
#include <mach/mach_time.h>
SecondsToNanoSeconds = 1000000000 // 10^9
};
-static void ConvertUTimBuf(const UTimBuf* pal, struct utimbuf* native)
+int32_t SystemNative_UTimensat(const char* path, TimeSpec* times)
{
- native->actime = (time_t)(pal->AcTime);
- native->modtime = (time_t)(pal->ModTime);
-}
-
-int32_t SystemNative_UTime(const char* path, UTimBuf* times)
-{
- assert(times != NULL);
-
- struct utimbuf temp;
- ConvertUTimBuf(times, &temp);
-
int32_t result;
- while (CheckInterrupted(result = utime(path, &temp)));
+#if HAVE_UTIMENSAT
+ struct timespec origTimes[2];
+ origTimes[0].tv_sec = (time_t)times[0].tv_sec;
+ origTimes[0].tv_nsec = (long)times[0].tv_nsec;
+
+ origTimes[1].tv_sec = (time_t)times[1].tv_sec;
+ origTimes[1].tv_nsec = (long)times[1].tv_nsec;
+ while (CheckInterrupted(result = utimensat(AT_FDCWD, path, origTimes, 0)));
+#else
+ struct timeval origTimes[2];
+ origTimes[0].tv_sec = (long)times[0].tv_sec;
+ origTimes[0].tv_usec = (int)times[0].tv_nsec / 1000;
+
+ origTimes[1].tv_sec = (long)times[1].tv_sec;
+ origTimes[1].tv_usec = (int)times[1].tv_nsec / 1000;
+ while (CheckInterrupted(result = utimes(path, origTimes)));
+#endif
+
return result;
}
#include "pal_compiler.h"
#include "pal_types.h"
-typedef struct UTimBuf
+typedef struct TimeSpec
{
- int64_t AcTime;
- int64_t ModTime;
-} UTimBuf;
+ int64_t tv_sec; // seconds
+ int64_t tv_nsec; // nanoseconds
+} TimeSpec;
/**
* Sets the last access and last modified time of a file
*
* Returns 0 on success; otherwise, returns -1 and errno is set.
*/
-DLLEXPORT int32_t SystemNative_UTime(const char* path, UTimBuf* time);
+DLLEXPORT int32_t SystemNative_UTimensat(const char* path, TimeSpec* times);
/**
* Gets the resolution of the timestamp, in counts per second.
futimens
HAVE_FUTIMENS)
+check_function_exists(
+ utimensat
+ HAVE_UTIMENSAT)
+
set (PREVIOUS_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
set (CMAKE_REQUIRED_FLAGS "-Werror -Wsign-conversion")
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.RmDir.cs">
<Link>Common\Interop\Unix\Interop.RmDir.cs</Link>
</Compile>
- <Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.UTime.cs">
- <Link>Common\Interop\Unix\Interop.UTime.cs</Link>
+ <Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.UTimensat.cs">
+ <Link>Common\Interop\Unix\Interop.UTimensat.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\CoreLib\System\IO\PathInternal.Unix.cs">
<Link>Common\CoreLib\System\IO\PathInternal.Unix.cs</Link>
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System.Runtime.InteropServices;
+
namespace System.IO
{
internal struct FileStatus
// force a refresh so that we have an up-to-date times for values not being overwritten
_fileStatusInitialized = -1;
EnsureStatInitialized(path);
- Interop.Sys.UTimBuf buf;
- // we use utime() not utimensat() so we drop the subsecond part
- buf.AcTime = accessTime ?? _fileStatus.ATime;
- buf.ModTime = writeTime ?? _fileStatus.MTime;
- Interop.CheckIo(Interop.Sys.UTime(path, ref buf), path, InitiallyDirectory);
+
+ // we use utimes()/utimensat() to set the accessTime and writeTime
+ Span<Interop.Sys.TimeSpec> buf = stackalloc Interop.Sys.TimeSpec[2];
+
+ // setting second part
+ buf[0].TvSec = accessTime ?? _fileStatus.ATime;
+ buf[1].TvSec = writeTime ?? _fileStatus.MTime;
+
+ // setting nanosecond part
+ buf[0].TvNsec = accessTime == null ? _fileStatus.ATimeNsec : 0;
+ buf[1].TvNsec = writeTime == null ? _fileStatus.MTimeNsec : 0;
+
+ Interop.CheckIo(Interop.Sys.UTimensat(path, ref MemoryMarshal.GetReference(buf)), path, InitiallyDirectory);
+
_fileStatusInitialized = -1;
}
}
[ConditionalFact(nameof(isNotHFS))] // OSX HFS driver format does not support millisec granularity
- public void TimesIncludeMillisecondPart_Unix()
+ public void TimesIncludeMillisecondPart()
{
T item = GetExistingItem();
Assert.All(TimeFunctions(), (function) =>
}
[ConditionalFact(nameof(isHFS))]
- public void TimesIncludeMillisecondPart_OSX()
+ public void TimesIncludeMillisecondPart_HFS()
{
T item = GetExistingItem();
// OSX HFS driver format does not support millisec granularity
});
}
}
+
+ [Fact]
+ public void SetLastWriteTimeMilliSec()
+ {
+ string firstFile = GetTestFilePath();
+ string secondFile = GetTestFilePath();
+
+ File.WriteAllText(firstFile, "");
+ File.WriteAllText(secondFile, "");
+
+ File.SetLastAccessTimeUtc(secondFile, DateTime.UtcNow);
+ long firstFileTicks = File.GetLastWriteTimeUtc(firstFile).Ticks;
+ long secondFileTicks = File.GetLastWriteTimeUtc(secondFile).Ticks;
+ Assert.True(firstFileTicks <= secondFileTicks, $"First File Ticks\t{firstFileTicks}\nSecond File Ticks\t{secondFileTicks}");
+ }
}
}