Which operations update last access time? - windows

Assuming given filesystem is tracking Last Access Time (aka atime) -- which operations on a file cause atime to update?
As far as I know:
opening existing file (and subsequent closing related handle/fd) does not update atime
reading/writing file will update atime (I wonder if read-0-bytes operation does that)
reading file security descriptor (via related Win32 API) does not update atime or other file attributes
Is there an exhaustive list of operations that update atime?

The last access time includes the last time the file or directory was written to, read from, or, in the case of executable files, run.
Other operations, like accessing the file to retrieve properties to show in Explorer
or some other viewer, accessing the file to retrieve its icon etc. don't update last access time.
Refer to "GetFileTime - lpLastAccessTime", "How do I access a file without updating its last-access time?"
Update: Add test results of read/write 0 bytes and read/write 1 bytes.
Code used for testing:
void GetLastAccessTime(HANDLE hFile)
{
FILETIME ftAccess;
SYSTEMTIME stUTC, stLocal;
printf("Get last access time\n");
// Retrieve the file times for the file.
if (!GetFileTime(hFile, NULL, &ftAccess, NULL))
return;
// Convert the last-write time to local time.
FileTimeToSystemTime(&ftAccess, &stUTC);
SystemTimeToTzSpecificLocalTime(NULL, &stUTC, &stLocal);
// Build a string showing the date and time.
wprintf(
L"%02d/%02d/%d %02d:%02d \n",
stLocal.wMonth, stLocal.wDay, stLocal.wYear,
stLocal.wHour, stLocal.wMinute);
}
int main()
{
HANDLE tFile = INVALID_HANDLE_VALUE;
printf("Open file\n");
// Open file
tFile = CreateFile(L"C:\\Users\\ritah\\Desktop\\test1.txt", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if (INVALID_HANDLE_VALUE == tFile)
{
printf("CreateFile fails with error: %d\n", GetLastError());
getchar();
return 0;
}
printf("Sleep 60 seconds\n");
Sleep(60000);
GetLastAccessTime(tFile);
// Read 0 bytes
printf("Read 0 bytes\n");
WCHAR redBuf[10];
DWORD redBytes = 0;
if(!ReadFile(tFile, redBuf, 0, &redBytes, NULL))
{
printf("ReadFile fails with error: %d\n", GetLastError());
getchar();
return 0;
}
printf("Sleep 60 seconds\n");
Sleep(60000);
GetLastAccessTime(tFile);
// Write 0 bytes
printf("Write 0 bytes\n");
WCHAR writeBuf[] = L"write test";
DWORD writeBytes = 0;
if(!WriteFile(tFile, writeBuf, 0, &writeBytes, NULL))
{
printf("WriteFile fails with error: %d\n", GetLastError());
getchar();
return 0;
}
printf("Sleep 60 seconds\n");
Sleep(60000);
GetLastAccessTime(tFile);
getchar();
}
So, read/write 0 bytes doesn't update last access time.

Related

How to read files in a junction point directory, using GetFileInformationByHandleEx?

I have this code which runs fine if path points to a regular directory:
#include <windows.h>
#include <stdio.h>
int main()
{
wchar_t path[1024] = L"C:\\MyPath\\MyDir";
auto h = CreateFile(path, FILE_LIST_DIRECTORY, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (h == INVALID_HANDLE_VALUE)
{
printf("CreateFile error: 0x%08X\n", GetLastError());
return 0;
}
FILE_ID_INFO id = {};
if (!GetFileInformationByHandleEx(h, FILE_INFO_BY_HANDLE_CLASS::FileIdInfo, &id, sizeof(id)))
{
printf("FileIdInfo error: 0x%08X\n", GetLastError());
}
unsigned char buffer[4096];
do
{
if (!GetFileInformationByHandleEx(h, FILE_INFO_BY_HANDLE_CLASS::FileIdExtdDirectoryInfo, buffer, ARRAYSIZE(buffer)))
{
auto gle = GetLastError();
if (gle == ERROR_NO_MORE_FILES)
break;
printf("FileIdExtdDirectoryInfo error: 0x%08X\n", gle);
break;
}
auto current = buffer;
do
{
auto info = (FILE_ID_EXTD_DIR_INFO*)current;
wprintf(L"name: %.*s\n", info->FileNameLength / 2, info->FileName);
if (!info->NextEntryOffset)
break;
current += info->NextEntryOffset;
} while (true);
} while (true);
CloseHandle(h);
}
But if the directory points to a junction point, it returns (ERROR_INVALID_PARAMETER invalid parameter)
FileIdInfo error: 0x00000057
FileIdExtdDirectoryInfo error: 0x00000057
So, I've tried this for CreateFile
auto h = CreateFile(path, FILE_LIST_DIRECTORY, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL);
and now, I have no error, but it only shows "." and ".." entries, not all the files in the directory which I can read using cmd.exe
C:\MyPath\MyDir>dir
Volume in drive C has no label.
Volume Serial Number is AEA6-688A
Directory of C:\MyPath\MyDir
20/10/2017 14:08 (157) My Icon.ico
11/04/2018 09:08 321 My File.cpp
30/04/2018 15:14 <DIR> My dossier
19/09/2019 10:40 (41 650) New Rich Text Document.rtf
10/10/2021 11:06 0 New Text Document.txt
4 File(s) 46 224 bytes
1 Dir(s) 544 625 274 880 bytes free
It turns out my code in itself is correct, but it can fail if the file system below doesn't support NTFS object ID, since this is what it's trying to read (using FILE_INFO_BY_HANDLE_CLASS::FileIdInfo and FILE_INFO_BY_HANDLE_CLASS::FileIdExtdDirectoryInfo).
This happens for example if the directory is an NTFS mount point with a substitute name that points to a volume that doesn't have the FILE_SUPPORTS_OBJECT_IDS flag set (seen in virtual drive scenarios).
The error here (ERROR_INVALID_PARAMETER) is kind of misleading as there's no problem with any parameter (except maybe the FileInformationClass one), I would have expected some "unsupported" error instead.
As for cmd.exe, well, it just doesn't read/need that information so it works fine.

Windows 10 File Cloud / Sync Provider API - TransferData problem

I am building upon the Cloud Mirror Sample and having a similar issue to this one
Here is the test code involved:
// When the client needs to fetch data from the cloud, this method will be called.
// The FakeMirrorDataMover class does the actual work of copying files from
// the "cloud" to the "client" and updating the transfer status along the way.
void CALLBACK FakeCloudProvider::OnFetchData(
_In_ CONST CF_CALLBACK_INFO* callbackInfo,
_In_ CONST CF_CALLBACK_PARAMETERS* callbackParameters)
{
//FileCopierWithProgress::CopyFromServerToClient(callbackInfo, callbackParameters, ProviderFolderLocations::GetServerFolder());
const UINT CHUNKSIZE = 48 * 1024 * 1024;
UINT len;
LONG64 offset = callbackParameters->FetchData.RequiredFileOffset.QuadPart;
LONG64 requiredLength = callbackParameters->FetchData.RequiredLength.QuadPart;
byte *buffer = new byte[CHUNKSIZE];
FillMemory(buffer, CHUNKSIZE, (byte)0xA5);
while (0 < requiredLength)
{
len = requiredLength < CHUNKSIZE ? requiredLength : CHUNKSIZE;
if (0 != len % 4096)
len = 4096 * (len / 4096 + 1);
Placeholders::TransferData(callbackInfo->TransferKey.QuadPart, buffer, offset, len, 0);
requiredLength -= len;
offset += len;
}
delete[] buffer;
}
HRESULT Placeholders::TransferData(
//_In_ CF_CONNECTION_KEY connectionKey,
_In_ LONG64 transferKey,
_In_reads_bytes_opt_(length.QuadPart) LPCVOID transferData,
_In_ LONG64 startingOffset,
_In_ LONG64 length,
_In_ NTSTATUS completionStatus)
{
CF_OPERATION_INFO opInfo = { 0 };
CF_OPERATION_PARAMETERS opParams = { 0 };
opInfo.StructSize = sizeof(opInfo);
opInfo.Type = CF_OPERATION_TYPE_TRANSFER_DATA;
opInfo.ConnectionKey = FakeCloudProvider::GetConnectionKey();
opInfo.TransferKey.QuadPart = transferKey;
opParams.ParamSize = CF_SIZE_OF_OP_PARAM(TransferData);
opParams.TransferData.CompletionStatus = completionStatus;
opParams.TransferData.Buffer = transferData;
opParams.TransferData.Offset.QuadPart = startingOffset;
opParams.TransferData.Length.QuadPart = length;
winrt::check_hresult(CfExecute(&opInfo, &opParams));
return S_OK;
}
There is only one placeholder file created inside the sync root folder of ~ 9.3 GB size. After I create this I either double click it or right click and "always keep on this device" - same result.
The result being that after the last call to TransferData the Windows Explorer file progress gets stuck. It will eventually time out, I click on "try again", my breakpoints inside OnFetchData() get hit and file progress remains stuck. If I cancel the transfer then onCancelFetchData() gets called once as it should. Subsequent attempts to download the rest of the file will no longer call onFetchData()
If I comment out the if (0 != len % 4096) len = 4096 * (len / 4096 + 1); part then I am getting the dreaded 0x8007017c the cloud operation is invalid with the rest of the symptoms as above.
I have tried with a smaller ~ 9.3 MB file and it went fine... what else to try?
edit
The ~100MB and ~1GB sizes also working fine
Result of CfExecute calls:
//[...] same output for all previous offsets except for the last call
TransferData method - offset: 1298137088, length: 50331648, syncStatBeforeCall: null, syncStatAfterCall: null, hresult: 0
TransferData method - offset: 1348468736, length: 50331648, syncStatBeforeCall: null, syncStatAfterCall: null, hresult: 0
TransferData method - offset: 1398800384, length: 11265024, syncStatBeforeCall: null, syncStatAfterCall: null, hresult: 8007017c
As far as I can tell null is fine for SyncStatus:
SyncStatus
Note This member is new for Windows 10, version 1803.
The current sync status of the platform.
The platform queries this information upon any failed operations on a
cloud file placeholder. If a structure is available, the platform will
use the information provided to construct a more meaningful and
actionable message to the user. The platform will keep this
information on the file until the last handle on it goes away. If
null, the platform will clear the previously set sync status, if there
is one.
--edit--
HRESULT Placeholders::Create(_In_ PCWSTR destPath,
_In_ PCWSTR fileName,
_In_ CF_PLACEHOLDER_CREATE_FLAGS flags,
_In_ LONG64 fileSize,
_In_ DWORD fileAttributes,
_In_ LONG64 ftCreationTime,
_In_ LONG64 ftLastWriteTime,
_In_ LONG64 ftLastAccessTime,
_In_ LONG64 ftChangeTime)
{
CF_PLACEHOLDER_CREATE_INFO cloudEntry;
cloudEntry.FileIdentity = fileName;
cloudEntry.FileIdentityLength = (DWORD)((wcslen(fileName)+1) * sizeof(WCHAR));
cloudEntry.RelativeFileName = fileName;
cloudEntry.Flags = flags;
cloudEntry.FsMetadata.FileSize.QuadPart = fileSize;
cloudEntry.FsMetadata.BasicInfo.FileAttributes = fileAttributes;
cloudEntry.FsMetadata.BasicInfo.CreationTime.QuadPart = ftCreationTime;
cloudEntry.FsMetadata.BasicInfo.LastWriteTime.QuadPart = ftLastWriteTime;
cloudEntry.FsMetadata.BasicInfo.LastAccessTime.QuadPart = ftLastAccessTime;
cloudEntry.FsMetadata.BasicInfo.ChangeTime.QuadPart = ftChangeTime;
if ((fileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
{
cloudEntry.Flags |= CF_PLACEHOLDER_CREATE_FLAG_DISABLE_ON_DEMAND_POPULATION;
cloudEntry.FsMetadata.FileSize.QuadPart = 0;
cloudEntry.FileIdentity = nullptr;
}
try
{
wprintf(L"Creating placeholder for %s\n", fileName);
winrt::check_hresult(CfCreatePlaceholders(destPath, &cloudEntry, 1, CF_CREATE_FLAG_NONE, NULL));
}
catch (...)
{
// winrt::to_hresult() will eat the exception if it is a result of winrt::check_hresult,
// otherwise the exception will get rethrown and this method will crash out as it should
wprintf(L"Failed to create placeholder for %s with %08x\n", fileName, static_cast<HRESULT>(winrt::to_hresult()));
// Eating it here lets other files still get a chance. Not worth crashing the sample, but
// certainly noteworthy for production code
return static_cast<HRESULT>(winrt::to_hresult());
}
return S_OK;
}
Create(_T("C:\\SyncRootDT"), _T("file1"), CF_PLACEHOLDER_CREATE_FLAG_MARK_IN_SYNC,
10000000000, FILE_ATTRIBUTE_NORMAL, 532657415, 532657415, 532657415, 532657415));
I observe similar behaviour. The hydration of files up to 4Gb works fine, but files over 4Gb always stuck and failed with the 'Cloud operation is invalid' exception. I was able to overcome it with this fix but instead of the CF_CALLBACK_PARAMETERS.FETCHDATA.RequiredLength and OptionalLength, I used a complete file length from CF_CALLBACK_INFO.FileSize. It looks like after a recent Windows update the OptionalLength is always zero.
My code is in .NET, but I guess you can easily translate it into C++:
// Use a complete file size in case the file is over 4Gb
if (callbackInfo.FileSize > 0x100000000)
{
requiredLength = callbackInfo.FileSize;
}

How to read a file using readfile on Winapi

I'm learning how to use in Winapi
And I'm trying to read a file from My Computer
But for some reason it doesn't work ...
HANDLE hFile;
//PVOID First_Bytes[2048];
char First_Bytes[2048];
DWORD dbr = 0;
hFile = CreateFile(L"d:\\My-File",GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL , NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("Error %x", GetLastError());
return 1;
}
if (ReadFile(hFile, &First_Bytes, 512, &dbr, NULL) == 0) {
printf("ReadFile error: %x", GetLastError());
return 1;
}
printf("%s", First_Bytes);
CloseHandle(hFile);
The console doesn't print anything.
What am I doing wrong?
I edited the code and add that errors checks.
But still consul does not print anything
The logical conclusion is that the first byte in your file is a zero. You treat the buffer as a null-terminated string, and so nothing is printed.
Do note that there is no guarantee that your buffer is null terminated so you potentially have undefined behaviour.

WriteProcessMemory in debugged process

I try to write simple debugger. For simplicity, assume the debugger runs under Windows XP.
At first I create new process as follows:
CreateProcess(processName,
NULL,
NULL,
NULL,
false,
DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS,
NULL,
NULL,
&startInfo,
&openedProcessInfo);
And when I try to read or write something in memory of debugging process there are some problems. For example:
DWORD oldProtect;
if(!VirtualProtectEx(hProcess, breakpointAddr, 1, PAGE_EXECUTE_READWRITE, &oldProtect)) {
printf("Error: %d\n", GetLastError());
}
SIZE_T bytesRead = 0;
SIZE_T bytesWritten = 0;
BYTE instruction;
BOOL isOk = ReadProcessMemory(hProcess, breakpointAddr, &instruction, 1, &bytesRead);
BYTE originalByte = instruction;
instruction = 0xCC;
if(isOk && bytesRead == 1) {
isOk = WriteProcessMemory(hProcess, breakpointAddr, &instruction, 1, &bytesWritten);
if(isOk) {
isOk = FlushInstructionCache(hProcess, breakpointAddr, 1);
}
}
if(!isOk) {
printf("Error: %d\n", GetLastError());
}
It works, but not everywhere. It works when the address to which I want to write(read) something, is located within executable module (.exe).
But when I try to write(read) something within DLL library (for example, read at address of function VirtualAlloc) VirtualProtectEx returns false and GetLastError = 487 (Attempt to access invalid address) and ReadProcessMemory also returns false and GetLastError = 299 (Only part of a ReadProcessMemory or WriteProcessMemory request was completed.)
Debug privileges are enabled but it has no effect.
Your code looks fine, if you're running as administrator than the most likely cause of the problem is that breakpointAddr is an invalid address. VirtualProtectEx giving you the "Attempt to access invalid address" error supports this conclusion.

Process32Next fails with ERROR_INSUFFICIENT_BUFFER (windows 7)

I am trying to get a list of all the executable paths of running processes
The do-while loop (shown below) starts off and after about 90 something iterations it fails with a ERROR_INSUFFICIENT_BUFFER error. I presume thats the pBuffer, I tried with a very large buffer and it still failed. The ProcessEntry struct on the failed iteration has garbage in szExeFile. Please advise (I close the handles, not shown below)
Code:
// Retrieve a handle to the process snapshot
HANDLE hProcessSnapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0));
if (INVALID_HANDLE_VALUE == hProcessSnapshot) ERROR;
// Retrieve information about the first process and exit if unsuccessful
PROCESSENTRY32 oProcessEntry;
memset(&oProcessEntry, 0x00, sizeof(oProcessEntry));
oProcessEntry.dwSize = sizeof(PROCESSENTRY32);
BOOL bFileFound(Process32First(hProcessSnapshot, &oProcessEntry));
DWORD dwError;
if(!bFileFound) {
dwError = GetLastError();
if(ERROR_NO_MORE_FILES == dwError) return TPathList();
// Error
ERROR;
}
// Walk the snapshot of processes
TCHAR pBuffer[MAX_PATH];
TPathList lExecutablePaths;
do {
// Get handle to process
HANDLE hProcess(OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
FALSE,
oProcessEntry.th32ProcessID));
if(INVALID_HANDLE_VALUE == hProcess) ERROR;
if (!hProcess) continue;
// Get the module path
if(GetModuleFileNameEx(hProcess, 0, pBuffer, MAX_PATH) == 0) ERROR;
lExecutablePaths.push_back(CPath(pBuffer));
}
// Get next process
while(Process32Next(hProcessSnapshot, &oProcessEntry));
// If we ran out of files return what has been found
dwError = GetLastError();
if(ERROR_NO_MORE_FILES == dwError) return lExecutablePaths;
ERROR;
You can use like this:
CString sBuffer;
DWORD dwSize = MAX_PATH + 1, dwError = 0;
do
{
GetModuleFileName( NULL, sBuffer.GetBuffer( dwSize ), dwSize );
// Retrieve the last error. If we've succeeded ERROR_SUCCESS
// will be returned; otherwise, we'll get an ERROR_INSUFFICIENT_BUFFER
// error.
dwError = ::GetLastError( );
// Buffer may not be big enough so double its size
dwSize *= 2;
}
while( dwError == ERROR_INSUFFICIENT_BUFFER
&& dwError != ERROR_SUCCESS );
// Release the buffer (turn the string back to const)
sBuffer.ReleaseBuffer( );
I hope it will work.
Also you can prefer How can I calculate the complete buffer size for GetModuleFileName?

Resources