dmitri
2008-05-26 23:00:18 UTC
Dear all,
I have been tasked with reading a file without modifying its access
time (this is for a sort of a backup application). I was under the
impression that to do a proper backup, I had to call CreateFile()
using FILE_FLAG_BACKUP_SEMANTICS flag. Then, Last Access time would
not be changed after the file contents were read. My test program is
attached at the end of this post; it does not work. That is, the Last
Access time stubbornly gets updated every time a file is read. I
tried several combinations of arguments to CreateFile() and using both
ReadFile() and BackupRead() calls to read the data, but to no avail.
I was then pointed to documentation about special trick one can do [1]
by calling SetFileTime() with atime set to 0xFFFFFFFF right after the
file is opened. This _does_ work; however, one has to call
CreateFile() with FILE_WRITE_ATTRIBUTES flag -- otherwise,
SetFileTime() call fails. In my case, the files to be read are on a
read-only share, so calling CreateFIle() with any FILE_WRITE_* flag
fails.
Then I decided to investigate whether other backup/copy applications
are able to do what my program could not. I looked at operation of
ntbackup and robocopy. While both worked (Last Access time remained
unchanged) when reading files from a regular directory, neither did
when reading files from a read-only share. Tracing both programs
using StraceNT [2], I discovered that ntbackup in fact relies on the
SetFileTime() trick; when reading the file from a read-only share the
call fails. robocopy uses CopyFileEx() and it also fails to preserve
Last Access time on a read-only share (how it does it reading files
from a regular directory remains a mystery to me).
The question, therefore, is as follows: is what I am trying to achieve
possible? or is it simply not done because of some limitations that
are not explicitly documented? Am I missing something obvious in my
code below?
Thanks in advance,
- Dmitri.
P.S. I am new to Windows programming, so it took a while to
understand that there are _two_ records of Last Access time [3] in
NTFS. I wrote a separate program (not attached) to print the actual
atime of the file as it is reported for the file, not by what's stored
in its directory entry (this is how dir /ta reports it).
1. http://msdn.microsoft.com/en-us/library/ms724933(VS.85).aspx
2. http://www.intellectualheaven.com/default.asp?BH=projects&H=strace.htm
3. http://technet2.microsoft.com/windowsserver/en/library/8cc5891d-bf8e-4164-862d-dac5418c59481033.mspx?mfr=true
/*
* mycat.cpp -- read a text file and print its contents to console.
*
* Purpose: check out this whole FILE_FLAG_BACKUP_SEMANTICS business.
Does
* atime of a file change when we read it?
*/
#include "stdafx.h"
#include "windows.h"
static BOOL
enable_privilege (LPCWSTR lpPrivName)
{
TOKEN_PRIVILEGES Privileges;
HANDLE hToken;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY|
TOKEN_ADJUST_PRIVILEGES, &hToken)) {
fprintf(stderr, "OpenProcessToken\n");
return FALSE;
}
Privileges.PrivilegeCount = 1;
Privileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!LookupPrivilegeValue(NULL, lpPrivName,
&Privileges.Privileges[0].Luid)) {
fprintf(stderr, "LookupPrivilegeValue\n");
return FALSE;
}
BOOL bResult = AdjustTokenPrivileges(hToken, FALSE, &Privileges,
0, NULL, NULL);
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) {
fprintf(stderr, "The token does not have the specified
privilege. \n");
return FALSE;
}
CloseHandle(hToken);
if (!bResult)
fprintf(stderr, "Cannot get priv\n");
return bResult;
}
#define DIE_IF_ATIME_CHANGES 0
int _tmain(int argc, _TCHAR* argv[])
{
if (!enable_privilege(SE_BACKUP_NAME))
return 1;
if (!enable_privilege(SE_RESTORE_NAME))
return 1;
HANDLE hSrc = CreateFile(argv[1], GENERIC_READ, NULL, NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_SEQUENTIAL_SCAN, NULL);
if (INVALID_HANDLE_VALUE == hSrc) {
fprintf(stderr, "Invalid handle value!\n");
return 1;
}
HANDLE hConOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (INVALID_HANDLE_VALUE == hConOut) {
fprintf(stderr, "Can't get stdout handle: %d\n",
GetLastError());
return 1;
}
/* Get atime before the read happens: */
FILETIME atime_before_read;
if (!GetFileTime(hSrc, NULL, &atime_before_read, NULL)) {
fprintf(stderr, "error: GetFileTime(): %d\n", GetLastError());
return 1;
}
while (1) {
unsigned char buf[4096];
DWORD nread;
if (!ReadFile(hSrc, buf, sizeof(buf), &nread, 0)) {
fprintf(stderr, "Error reading file: %d\n", GetLastError());
return 1;
}
if (0 == nread)
break; // EOF
/* Compare atime: */
FILETIME new_atime;
if (!GetFileTime(hSrc, NULL, &new_atime, NULL)) {
fprintf(stderr, "error: GetFileTime(): %d\n", GetLastError());
return 1;
}
if (!(new_atime.dwHighDateTime == atime_before_read.dwHighDateTime
&&
new_atime.dwLowDateTime == atime_before_read.dwHighDateTime))
{
fprintf(stderr, "atime changed!\n");
if (DIE_IF_ATIME_CHANGES)
return 1;
}
/* Print buffer to console: */
while (nread > 0) {
DWORD nwritten;
if (!WriteFile(hConOut, buf, nread, &nwritten, 0)) {
fprintf(stderr, "Error writing to console: %d\n", GetLastError());
return 1;
}
nread -= nwritten;
}
}
CloseHandle(hSrc);
CloseHandle(hConOut);
return 0;
}
I have been tasked with reading a file without modifying its access
time (this is for a sort of a backup application). I was under the
impression that to do a proper backup, I had to call CreateFile()
using FILE_FLAG_BACKUP_SEMANTICS flag. Then, Last Access time would
not be changed after the file contents were read. My test program is
attached at the end of this post; it does not work. That is, the Last
Access time stubbornly gets updated every time a file is read. I
tried several combinations of arguments to CreateFile() and using both
ReadFile() and BackupRead() calls to read the data, but to no avail.
I was then pointed to documentation about special trick one can do [1]
by calling SetFileTime() with atime set to 0xFFFFFFFF right after the
file is opened. This _does_ work; however, one has to call
CreateFile() with FILE_WRITE_ATTRIBUTES flag -- otherwise,
SetFileTime() call fails. In my case, the files to be read are on a
read-only share, so calling CreateFIle() with any FILE_WRITE_* flag
fails.
Then I decided to investigate whether other backup/copy applications
are able to do what my program could not. I looked at operation of
ntbackup and robocopy. While both worked (Last Access time remained
unchanged) when reading files from a regular directory, neither did
when reading files from a read-only share. Tracing both programs
using StraceNT [2], I discovered that ntbackup in fact relies on the
SetFileTime() trick; when reading the file from a read-only share the
call fails. robocopy uses CopyFileEx() and it also fails to preserve
Last Access time on a read-only share (how it does it reading files
from a regular directory remains a mystery to me).
The question, therefore, is as follows: is what I am trying to achieve
possible? or is it simply not done because of some limitations that
are not explicitly documented? Am I missing something obvious in my
code below?
Thanks in advance,
- Dmitri.
P.S. I am new to Windows programming, so it took a while to
understand that there are _two_ records of Last Access time [3] in
NTFS. I wrote a separate program (not attached) to print the actual
atime of the file as it is reported for the file, not by what's stored
in its directory entry (this is how dir /ta reports it).
1. http://msdn.microsoft.com/en-us/library/ms724933(VS.85).aspx
2. http://www.intellectualheaven.com/default.asp?BH=projects&H=strace.htm
3. http://technet2.microsoft.com/windowsserver/en/library/8cc5891d-bf8e-4164-862d-dac5418c59481033.mspx?mfr=true
/*
* mycat.cpp -- read a text file and print its contents to console.
*
* Purpose: check out this whole FILE_FLAG_BACKUP_SEMANTICS business.
Does
* atime of a file change when we read it?
*/
#include "stdafx.h"
#include "windows.h"
static BOOL
enable_privilege (LPCWSTR lpPrivName)
{
TOKEN_PRIVILEGES Privileges;
HANDLE hToken;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY|
TOKEN_ADJUST_PRIVILEGES, &hToken)) {
fprintf(stderr, "OpenProcessToken\n");
return FALSE;
}
Privileges.PrivilegeCount = 1;
Privileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!LookupPrivilegeValue(NULL, lpPrivName,
&Privileges.Privileges[0].Luid)) {
fprintf(stderr, "LookupPrivilegeValue\n");
return FALSE;
}
BOOL bResult = AdjustTokenPrivileges(hToken, FALSE, &Privileges,
0, NULL, NULL);
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) {
fprintf(stderr, "The token does not have the specified
privilege. \n");
return FALSE;
}
CloseHandle(hToken);
if (!bResult)
fprintf(stderr, "Cannot get priv\n");
return bResult;
}
#define DIE_IF_ATIME_CHANGES 0
int _tmain(int argc, _TCHAR* argv[])
{
if (!enable_privilege(SE_BACKUP_NAME))
return 1;
if (!enable_privilege(SE_RESTORE_NAME))
return 1;
HANDLE hSrc = CreateFile(argv[1], GENERIC_READ, NULL, NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_SEQUENTIAL_SCAN, NULL);
if (INVALID_HANDLE_VALUE == hSrc) {
fprintf(stderr, "Invalid handle value!\n");
return 1;
}
HANDLE hConOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (INVALID_HANDLE_VALUE == hConOut) {
fprintf(stderr, "Can't get stdout handle: %d\n",
GetLastError());
return 1;
}
/* Get atime before the read happens: */
FILETIME atime_before_read;
if (!GetFileTime(hSrc, NULL, &atime_before_read, NULL)) {
fprintf(stderr, "error: GetFileTime(): %d\n", GetLastError());
return 1;
}
while (1) {
unsigned char buf[4096];
DWORD nread;
if (!ReadFile(hSrc, buf, sizeof(buf), &nread, 0)) {
fprintf(stderr, "Error reading file: %d\n", GetLastError());
return 1;
}
if (0 == nread)
break; // EOF
/* Compare atime: */
FILETIME new_atime;
if (!GetFileTime(hSrc, NULL, &new_atime, NULL)) {
fprintf(stderr, "error: GetFileTime(): %d\n", GetLastError());
return 1;
}
if (!(new_atime.dwHighDateTime == atime_before_read.dwHighDateTime
&&
new_atime.dwLowDateTime == atime_before_read.dwHighDateTime))
{
fprintf(stderr, "atime changed!\n");
if (DIE_IF_ATIME_CHANGES)
return 1;
}
/* Print buffer to console: */
while (nread > 0) {
DWORD nwritten;
if (!WriteFile(hConOut, buf, nread, &nwritten, 0)) {
fprintf(stderr, "Error writing to console: %d\n", GetLastError());
return 1;
}
nread -= nwritten;
}
}
CloseHandle(hSrc);
CloseHandle(hConOut);
return 0;
}