Bug 1786608 - Add nsIFile::accessTime{,OfLink} r=xpcom-reviewers,nika

Differential Revision: https://phabricator.services.mozilla.com/D155926
This commit is contained in:
Barret Rennie 2022-11-06 18:33:22 +00:00
Родитель 6d99f6fc5c
Коммит 6d359be1d0
6 изменённых файлов: 280 добавлений и 100 удалений

Просмотреть файл

@ -243,6 +243,13 @@ interface nsIFile : nsISupports
attribute unsigned long permissions;
attribute unsigned long permissionsOfLink;
/**
* The last accesss time of the file in milliseconds from midnight, January
* 1, 1970 GMT, if available.
*/
attribute PRTime lastAccessedTime;
attribute PRTime lastAccessedTimeOfLink;
/**
* File Times are to be in milliseconds from
* midnight (00:00:00), January 1, 1970 Greenwich Mean

Просмотреть файл

@ -1155,10 +1155,11 @@ nsLocalFile::Remove(bool aRecursive) {
return NSRESULT_FOR_RETURN(rmdir(mPath.get()));
}
nsresult nsLocalFile::GetLastModifiedTimeImpl(PRTime* aLastModTime,
bool aFollowLinks) {
nsresult nsLocalFile::GetTimeImpl(PRTime* aTime,
nsLocalFile::TimeField aTimeField,
bool aFollowLinks) {
CHECK_mPath();
if (NS_WARN_IF(!aLastModTime)) {
if (NS_WARN_IF(!aTime)) {
return NS_ERROR_INVALID_ARG;
}
@ -1170,17 +1171,36 @@ nsresult nsLocalFile::GetLastModifiedTimeImpl(PRTime* aLastModTime,
return NSRESULT_FOR_ERRNO();
}
struct timespec* timespec;
switch (aTimeField) {
case TimeField::AccessedTime:
#if (defined(__APPLE__) && defined(__MACH__))
*aLastModTime = TimespecToMillis(fileStats.st_mtimespec);
timespec = &fileStats.st_atimespec;
#else
*aLastModTime = TimespecToMillis(fileStats.st_mtim);
timespec = &fileStats.st_atim;
#endif
break;
case TimeField::ModifiedTime:
#if (defined(__APPLE__) && defined(__MACH__))
timespec = &fileStats.st_mtimespec;
#else
timespec = &fileStats.st_mtim;
#endif
break;
default:
MOZ_CRASH("Unknown TimeField");
}
*aTime = TimespecToMillis(*timespec);
return NS_OK;
}
nsresult nsLocalFile::SetLastModifiedTimeImpl(PRTime aLastModTime,
bool aFollowLinks) {
nsresult nsLocalFile::SetTimeImpl(PRTime aTime,
nsLocalFile::TimeField aTimeField,
bool aFollowLinks) {
CHECK_mPath();
using UtimesFn = int (*)(const char*, const timeval*);
@ -1192,49 +1212,82 @@ nsresult nsLocalFile::SetLastModifiedTimeImpl(PRTime aLastModTime,
}
#endif
int result;
if (aLastModTime != 0) {
ENSURE_STAT_CACHE();
timeval access{};
#if (defined(__APPLE__) && defined(__MACH__))
access.tv_sec = mCachedStat.st_atimespec.tv_sec;
access.tv_usec = mCachedStat.st_atimespec.tv_nsec / 1000;
#else
access.tv_sec = mCachedStat.st_atim.tv_sec;
access.tv_usec = mCachedStat.st_atim.tv_nsec / 1000;
#endif
timeval modification{};
modification.tv_sec = aLastModTime / PR_MSEC_PER_SEC;
modification.tv_usec = (aLastModTime % PR_MSEC_PER_SEC) * PR_USEC_PER_MSEC;
ENSURE_STAT_CACHE();
timeval times[2];
times[0] = access;
times[1] = modification;
result = utimesFn(mPath.get(), times);
} else {
result = utimesFn(mPath.get(), nullptr);
if (aTime == 0) {
aTime = PR_Now();
}
timeval times[2];
const size_t writeIndex = aTimeField == TimeField::AccessedTime ? 0 : 1;
const size_t copyIndex = aTimeField == TimeField::AccessedTime ? 1 : 0;
#if (defined(__APPLE__) && defined(__MACH__))
auto* copyFrom = aTimeField == TimeField::AccessedTime
? &mCachedStat.st_atimespec
: &mCachedStat.st_mtimespec;
#else
auto* copyFrom = aTimeField == TimeField::AccessedTime ? &mCachedStat.st_atim
: &mCachedStat.st_mtim;
#endif
times[copyIndex].tv_sec = copyFrom->tv_sec;
times[copyIndex].tv_usec = copyFrom->tv_nsec / 1000;
times[writeIndex].tv_sec = aTime / PR_MSEC_PER_SEC;
times[writeIndex].tv_usec = (aTime % PR_MSEC_PER_SEC) * PR_USEC_PER_MSEC;
int result = utimesFn(mPath.get(), times);
return NSRESULT_FOR_RETURN(result);
}
NS_IMETHODIMP
nsLocalFile::GetLastAccessedTime(PRTime* aLastAccessedTime) {
return GetTimeImpl(aLastAccessedTime, TimeField::AccessedTime,
/* follow links? */ true);
}
NS_IMETHODIMP
nsLocalFile::SetLastAccessedTime(PRTime aLastAccessedTime) {
return SetTimeImpl(aLastAccessedTime, TimeField::AccessedTime,
/* follow links? */ true);
}
NS_IMETHODIMP
nsLocalFile::GetLastAccessedTimeOfLink(PRTime* aLastAccessedTime) {
return GetTimeImpl(aLastAccessedTime, TimeField::AccessedTime,
/* follow links? */ false);
}
NS_IMETHODIMP
nsLocalFile::SetLastAccessedTimeOfLink(PRTime aLastAccessedTime) {
return SetTimeImpl(aLastAccessedTime, TimeField::AccessedTime,
/* follow links? */ false);
}
NS_IMETHODIMP
nsLocalFile::GetLastModifiedTime(PRTime* aLastModTime) {
return GetLastModifiedTimeImpl(aLastModTime, /* follow links? */ true);
return GetTimeImpl(aLastModTime, TimeField::ModifiedTime,
/* follow links? */ true);
}
NS_IMETHODIMP
nsLocalFile::SetLastModifiedTime(PRTime aLastModTime) {
return SetLastModifiedTimeImpl(aLastModTime, /* follow links ? */ true);
return SetTimeImpl(aLastModTime, TimeField::ModifiedTime,
/* follow links ? */ true);
}
NS_IMETHODIMP
nsLocalFile::GetLastModifiedTimeOfLink(PRTime* aLastModTimeOfLink) {
return GetLastModifiedTimeImpl(aLastModTimeOfLink, /* follow link? */ false);
return GetTimeImpl(aLastModTimeOfLink, TimeField::ModifiedTime,
/* follow link? */ false);
}
NS_IMETHODIMP
nsLocalFile::SetLastModifiedTimeOfLink(PRTime aLastModTimeOfLink) {
return SetLastModifiedTimeImpl(aLastModTimeOfLink, /* follow links? */ false);
return SetTimeImpl(aLastModTimeOfLink, TimeField::ModifiedTime,
/* follow links? */ false);
}
NS_IMETHODIMP

Просмотреть файл

@ -85,8 +85,10 @@ class nsLocalFile final
nsresult CreateAndKeepOpen(uint32_t aType, int aFlags, uint32_t aPermissions,
bool aSkipAncestors, PRFileDesc** aResult);
nsresult SetLastModifiedTimeImpl(PRTime aLastModTime, bool aFollowLinks);
nsresult GetLastModifiedTimeImpl(PRTime* aLastModTime, bool aFollowLinks);
enum class TimeField { AccessedTime, ModifiedTime };
nsresult SetTimeImpl(PRTime aTime, TimeField aTimeField, bool aFollowLinks);
nsresult GetTimeImpl(PRTime* aTime, TimeField aTimeField, bool aFollowLinks);
nsresult GetCreationTimeImpl(PRTime* aCreationTime, bool aFollowLinks);
#if defined(USE_LINUX_QUOTACTL)

Просмотреть файл

@ -607,7 +607,8 @@ static void FileTimeToPRTime(const FILETIME* aFiletime, PRTime* aPrtm) {
// copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} with some
// changes : PR_GetFileInfo64, _PR_MD_GETFILEINFO64
static nsresult GetFileInfo(const nsString& aName, PRFileInfo64* aInfo) {
static nsresult GetFileInfo(const nsString& aName,
nsLocalFile::FileInfo* aInfo) {
if (aName.IsEmpty()) {
return NS_ERROR_INVALID_ARG;
}
@ -638,8 +639,6 @@ static nsresult GetFileInfo(const nsString& aName, PRFileInfo64* aInfo) {
aInfo->size = fileData.nFileSizeHigh;
aInfo->size = (aInfo->size << 32) + fileData.nFileSizeLow;
FileTimeToPRTime(&fileData.ftLastWriteTime, &aInfo->modifyTime);
if (0 == fileData.ftCreationTime.dwLowDateTime &&
0 == fileData.ftCreationTime.dwHighDateTime) {
aInfo->creationTime = aInfo->modifyTime;
@ -647,6 +646,9 @@ static nsresult GetFileInfo(const nsString& aName, PRFileInfo64* aInfo) {
FileTimeToPRTime(&fileData.ftCreationTime, &aInfo->creationTime);
}
FileTimeToPRTime(&fileData.fLastAccessTime, &aInfo->accessTime);
FileTimeToPRTime(&fileData.ftLastWriteTime, &aInfo->modifyTime);
return NS_OK;
}
@ -924,7 +926,7 @@ nsresult nsLocalFile::ResolveSymlink() {
}
// Resolve any shortcuts and stat the resolved path. After a successful return
// the path is guaranteed valid and the members of mFileInfo64 can be used.
// the path is guaranteed valid and the members of mFileInfo can be used.
nsresult nsLocalFile::ResolveAndStat() {
// if we aren't dirty then we are already done
if (!mDirty) {
@ -948,12 +950,12 @@ nsresult nsLocalFile::ResolveAndStat() {
// first we will see if the working path exists. If it doesn't then
// there is nothing more that can be done
nsresult rv = GetFileInfo(nsprPath, &mFileInfo64);
nsresult rv = GetFileInfo(nsprPath, &mFileInfo);
if (NS_FAILED(rv)) {
return rv;
}
if (mFileInfo64.type != PR_FILE_OTHER) {
if (mFileInfo.type != PR_FILE_OTHER) {
mResolveDirty = false;
mDirty = false;
return NS_OK;
@ -970,7 +972,7 @@ nsresult nsLocalFile::ResolveAndStat() {
mResolveDirty = false;
// get the details of the resolved path
rv = GetFileInfo(mResolvedPath, &mFileInfo64);
rv = GetFileInfo(mResolvedPath, &mFileInfo);
if (NS_FAILED(rv)) {
return rv;
}
@ -2356,8 +2358,9 @@ nsLocalFile::Remove(bool aRecursive) {
return rv;
}
NS_IMETHODIMP
nsLocalFile::GetLastModifiedTime(PRTime* aLastModifiedTime) {
nsresult nsLocalFile::GetDateImpl(PRTime* aTime,
nsLocalFile::TimeField aTimeField,
bool aFollowLinks) {
// Check we are correctly initialized.
CHECK_mWorkingPath();
@ -2365,55 +2368,77 @@ nsLocalFile::GetLastModifiedTime(PRTime* aLastModifiedTime) {
return NS_ERROR_INVALID_ARG;
}
// get the modified time of the target as determined by mFollowSymlinks
// If true, then this will be for the target of the shortcut file,
// otherwise it will be for the shortcut file itself (i.e. the same
// results as GetLastModifiedTimeOfLink)
FileInfo symlinkInfo;
FileInfo* pInfo;
nsresult rv = ResolveAndStat();
if (NS_FAILED(rv)) {
return rv;
if (aFollowLinks) {
if (nsresult rv = GetFileInfo(mWorkingPath, &info); NS_FAILED(rv)) {
return rv;
}
pInfo = &symlinkInfo
} else {
if (nsresult rv = ResolveAndStat(); NS_FAILED(rv)) {
return rv;
}
pInfo = &mFileInfo;
}
switch (aTimeField) {
case TimeField:
AccessedTime:
*aTime = pInfo->accessTime / PR_USEC_PER_MSEC;
break;
case TimeField::ModifiedTime:
*aTime = pInfo->modifyTime / PR_USEC_PR_MSEC;
break;
default:
MOZ_CRASH("Unknown time field");
}
// microseconds -> milliseconds
*aLastModifiedTime = mFileInfo64.modifyTime / PR_USEC_PER_MSEC;
return NS_OK;
}
NS_IMETHODIMP
nsLocalFile::GetLastAccessedTime(PRTime* aLastAccessedTime) {
return GetDateImpl(aLastAccessedTime, TimeField::AccessedTime,
/* aFollowSymlinks = */ true);
}
NS_IMETHODIMP
nsLocalFile::GetLastAccessedTimeOfLink(PRTime* aLastAccessedTime) {
return GetDateImpl(aLastAccessedTime, TimeField::AccessedTime,
/* aFollowSymlinks = */ false);
}
NS_IMETHODIMP
nsLocalFile::SetLastAccessedTime(PRTime aLastAccessedTime) {
return SetDateImpl(aLastAccessedTime, TimeField::AccessedTime);
}
NS_IMETHODIMP
nsLocalFile::SetLastAccessedTimeOfLink(PRTime aLastAccessedTime) {
return SetLastAccessedTime(aLastAccessedTime);
}
NS_IMETHODIMP
nsLocalFile::GetLastModifiedTime(PRTime* aLastModifiedTime) {
return GetDateImpl(aLastModified, TimeField::ModifiedTime,
/* aFollowSymlinks = */ true);
}
NS_IMETHODIMP
nsLocalFile::GetLastModifiedTimeOfLink(PRTime* aLastModifiedTime) {
// Check we are correctly initialized.
CHECK_mWorkingPath();
if (NS_WARN_IF(!aLastModifiedTime)) {
return NS_ERROR_INVALID_ARG;
}
// The caller is assumed to have already called IsSymlink
// and to have found that this file is a link.
PRFileInfo64 info;
nsresult rv = GetFileInfo(mWorkingPath, &info);
if (NS_FAILED(rv)) {
return rv;
}
// microseconds -> milliseconds
*aLastModifiedTime = info.modifyTime / PR_USEC_PER_MSEC;
return NS_OK;
return GetDateImpl(aLastModified, TimeField::ModifiedTime,
/* aFollowSymlinks = */ false);
}
NS_IMETHODIMP
nsLocalFile::SetLastModifiedTime(PRTime aLastModifiedTime) {
// Check we are correctly initialized.
CHECK_mWorkingPath();
nsresult rv = SetModDate(aLastModifiedTime, mWorkingPath.get());
if (NS_SUCCEEDED(rv)) {
MakeDirty();
}
return rv;
return SetDateImpl(aLastModifiedTime, TimeField::ModifiedTime);
}
NS_IMETHODIMP
@ -2454,17 +2479,21 @@ nsLocalFile::GetCreationTimeOfLink(PRTime* aCreationTime) {
return NS_OK;
}
nsresult nsLocalFile::SetModDate(PRTime aLastModifiedTime,
const wchar_t* aFilePath) {
nsresult nsLocalFile::SetDateImpl(PRTime aTime,
nsLocalFile::TimeField aTimeField) {
// Check we are correctly initialized.
CHECK_mWorkingPath();
// The FILE_FLAG_BACKUP_SEMANTICS is required in order to change the
// modification time for directories.
HANDLE file = ::CreateFileW(aFilePath, // pointer to name of the file
GENERIC_WRITE, // access (write) mode
0, // share mode
nullptr, // pointer to security attributes
OPEN_EXISTING, // how to create
FILE_FLAG_BACKUP_SEMANTICS, // file attributes
nullptr);
HANDLE file =
::CreateFileW(mWorkingPath.get(), // pointer to name of the file
GENERIC_WRITE, // access (write) mode
0, // share mode
nullptr, // pointer to security attributes
OPEN_EXISTING, // how to create
FILE_FLAG_BACKUP_SEMANTICS, // file attributes
nullptr);
if (file == INVALID_HANDLE_VALUE) {
return ConvertWinError(GetLastError());
@ -2474,8 +2503,12 @@ nsresult nsLocalFile::SetModDate(PRTime aLastModifiedTime,
SYSTEMTIME st;
PRExplodedTime pret;
if (aTime == 0) {
aTime = PR_Now();
}
// PR_ExplodeTime expects usecs...
PR_ExplodeTime(aLastModifiedTime * PR_USEC_PER_MSEC, PR_GMTParameters, &pret);
PR_ExplodeTime(aTime * PR_USEC_PER_MSEC, PR_GMTParameters, &pret);
st.wYear = pret.tm_year;
st.wMonth =
pret.tm_month + 1; // Convert start offset -- Win32: Jan=1; NSPR: Jan=0
@ -2486,14 +2519,23 @@ nsresult nsLocalFile::SetModDate(PRTime aLastModifiedTime,
st.wSecond = pret.tm_sec;
st.wMilliseconds = pret.tm_usec / 1000;
nsresult rv = NS_OK;
const FILETIME* accessTime = nullptr;
const FILETIME* modifiedTime = nullptr;
if (aTimeField == TimeField::AccessedTime) {
accessTime = &ft;
} else {
modifiedTime = &ft;
}
// if at least one of these fails...
if (!(SystemTimeToFileTime(&st, &ft) != 0 &&
SetFileTime(file, nullptr, nullptr, &ft) != 0)) {
SetFileTime(file, nullptr, accessTime, modifiedTime) != 0)) {
rv = ConvertWinError(GetLastError());
}
CloseHandle(file);
return rv;
}

Просмотреть файл

@ -53,6 +53,15 @@ class nsLocalFile final : public nsILocalFileWin {
// (com1, com2, etc...) and truncates the string if so.
static void CheckForReservedFileName(nsString& aFileName);
// PRFileInfo64 does not hvae an accessTime field;
struct FileInfo {
PRFileType type;
PROffset64 size;
PRTime creationTime;
PRTime accessTime;
PRTime modifyTime;
};
private:
// CopyMove and CopySingleFile constants for |options| parameter:
enum CopyFileOption {
@ -81,7 +90,7 @@ class nsLocalFile final : public nsILocalFileWin {
// mWorkingPath
nsString mShortWorkingPath;
PRFileInfo64 mFileInfo64;
FileInfo mFileInfo;
void MakeDirty() {
mDirty = true;
@ -103,7 +112,10 @@ class nsLocalFile final : public nsILocalFileWin {
nsresult CopySingleFile(nsIFile* aSource, nsIFile* aDest,
const nsAString& aNewName, uint32_t aOptions);
nsresult SetModDate(int64_t aLastModifiedTime, const wchar_t* aFilePath);
enum class TimeField { AccessedTime, ModifiedTime };
nsresult SetDateImpl(int64_t aTime, TimeField aTimeField);
nsresult GetDateImpl(PRTime* aTime, TimeField aTimeField, bool aFollowLinks);
nsresult HasFileAttribute(DWORD aFileAttrib, bool* aResult);
nsresult AppendInternal(const nsString& aNode, bool aMultipleComponents);

Просмотреть файл

@ -38,8 +38,8 @@ add_task(function test_normalize_crash_if_media_missing() {
});
// Tests that changing a file's modification time is possible
add_task(function test_file_modification_time() {
var file = do_get_profile();
add_task(async function test_file_modification_time() {
let file = do_get_profile();
file.append("testfile");
// Should never happen but get rid of it anyway
@ -47,36 +47,100 @@ add_task(function test_file_modification_time() {
file.remove(true);
}
var now = Date.now();
const now = Date.now();
file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
Assert.ok(file.exists());
const atime = file.lastAccessedTime;
// Modification time may be out by up to 2 seconds on FAT filesystems. Test
// with a bit of leeway, close enough probably means it is correct.
var diff = Math.abs(file.lastModifiedTime - now);
let diff = Math.abs(file.lastModifiedTime - now);
Assert.ok(diff < MAX_TIME_DIFFERENCE);
var yesterday = now - MILLIS_PER_DAY;
const yesterday = now - MILLIS_PER_DAY;
file.lastModifiedTime = yesterday;
diff = Math.abs(file.lastModifiedTime - yesterday);
Assert.ok(diff < MAX_TIME_DIFFERENCE);
Assert.equal(
file.lastAccessedTime,
atime,
"Setting lastModifiedTime should not set lastAccessedTime"
);
var tomorrow = now - MILLIS_PER_DAY;
const tomorrow = now - MILLIS_PER_DAY;
file.lastModifiedTime = tomorrow;
diff = Math.abs(file.lastModifiedTime - tomorrow);
Assert.ok(diff < MAX_TIME_DIFFERENCE);
var bug377307 = 1172950238000;
const bug377307 = 1172950238000;
file.lastModifiedTime = bug377307;
diff = Math.abs(file.lastModifiedTime - bug377307);
Assert.ok(diff < MAX_TIME_DIFFERENCE);
file.lastModifiedTime = 0;
Assert.greater(
file.lastModifiedTime,
now,
"Setting lastModifiedTime to 0 should set it to current date and time"
);
file.remove(true);
});
add_task(function test_lastAccessedTime() {
const file = do_get_profile();
file.append("test-atime");
if (file.exists()) {
file.remove(true);
}
const now = Date.now();
file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
Assert.ok(file.exists());
const mtime = file.lastModifiedTime;
// Modification time may be out by up to 2 seconds on FAT filesystems. Test
// with a bit of leeway, close enough probably means it is correct.
let diff = Math.abs(file.lastModifiedTime - now);
Assert.ok(diff < MAX_TIME_DIFFERENCE);
const yesterday = now - MILLIS_PER_DAY;
file.lastModifiedTime = yesterday;
diff = Math.abs(file.lastAccessedTime - yesterday);
Assert.ok(diff < MAX_TIME_DIFFERENCE);
Assert.equal(
file.lastModifiedTime,
mtime,
"Setting lastAccessedTime should not set lastModifiedTime"
);
const tomorrow = now - MILLIS_PER_DAY;
file.lastAccessedTime = tomorrow;
diff = Math.abs(file.lastAccessedTime - tomorrow);
Assert.ok(diff < MAX_TIME_DIFFERENCE);
const bug377307 = 1172950238000;
file.lastAccessedTime = bug377307;
diff = Math.abs(file.lastAccessedTime - bug377307);
Assert.ok(diff < MAX_TIME_DIFFERENCE);
file.lastAccessedTime = 0;
Assert.greater(
file.lastAccessedTime,
now,
"Setting lastAccessedTime to 0 should set it to the current date and time"
);
});
// Tests that changing a directory's modification time is possible
add_task(function test_directory_modification_time() {
var dir = do_get_profile();