SetFileTime & LastWriteTime
От: Fortnum  
Дата: 07.09.14 16:01
Оценка:
Вопрос, возможно, имеющий отношение скорее к WinApi, но код на C#. Не могу найти никакой информации по следующему эффекту. В нижеприведенном коде, если закоменчена строка CallSetFileTime(fileStream), то время изменения файла в NTFS постоянно модифицируется — посекундно:

  Скрытый текст
static void Main()
{
    while (true)
    {
        using(var fileStream = new FileStream("TestFile.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
        {
            //CallSetFileTime(fileStream);

            fileStream.Write(new byte[] { 0x77 }, 0, 1);
        }
    }
}

static void CallSetFileTime(FileStream fileStream)
{
    var hFile = fileStream.SafeFileHandle.DangerousGetHandle();

    var pLastWriteTime = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(System.Runtime.InteropServices.ComTypes.FILETIME)));

    if (!GetFileTime(hFile, IntPtr.Zero, IntPtr.Zero, pLastWriteTime))
        throw new Exception();

    if (!SetFileTime(hFile, IntPtr.Zero, IntPtr.Zero, pLastWriteTime))
        throw new Exception();

    Marshal.FreeHGlobal(pLastWriteTime);
}

[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool GetFileTime(IntPtr hFile, IntPtr pCreationTime, IntPtr pLastAccessTime, IntPtr pLastWriteTime);

[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool SetFileTime(IntPtr hFile, IntPtr pCreationTime, IntPtr pLastAccessTime, IntPtr pLastWriteTime);


А вот если вызов CallSetFileTime в Main раскоментить, то время изменения файла стоит как вкопанное, несмотря ни на что. Почему?

PS.

Задачу решаю такую. Мне надо открыть файл в режиме CREATE_ALWAYS (FileMode.Create = 2), то есть чтобы файл всегда опустошался и если его нет, автоматически создавался бы. Но при этом режиме открытия, вернувшись из CreateFile, обнаруживаем дату изменения файла (если он существовал на момент вызова) уже модифицированной (я так понимаю, что ОС делает это за нас).

Поэтому возникла идея открывать файл в режиме OPEN_ALWAYS (FileMode.OpenOrCreate = 4), и если GetLastError возвращает нам ERROR_ALREADY_EXISTS (183), вызывать SetEndOfFile, или в .Net FileStream.SetLength(0) — этого вызова в приведенном выше коде нет, я его убрал, там всего лишь время lastWriteTime вычитывается, и вычитанное, без каких-либо манипуляций, устанавливается обратно.

Необходимость вычитывания времени lastWriteTime до SetEndOfFile/SetLength(0) и установка его после обратно обусловлена тем, что после вызова SetEndOfFile/SetLength(0) ОС меняет lastWriteTime, а мне надо, чтобы если ни одного WriteFile после открытия не произошло, время lastWriteTime файла не менялось бы (файл оставался бы пустым но с прежним временем).

Собственно, независимо от задачи, меня заинтересовал этот эффект. Я пока не смог найти никакой документации на эту тему. Буду благодарен за наводку.
Re: SetFileTime & LastWriteTime
От: Fortnum  
Дата: 07.09.14 17:01
Оценка:
Да, странно, никогда не обращал внимание. После вызова SetFileTime, даже если это не первое обращение к открытому hFile, lastWriteFile файла перестаёт меняться, и не реагирует уже ни на WriteFile, ни на SetEndOfFile (FileStream.SetLength). Не могу нагуглить ничего по этому поводу. Может, только у меня так?

Как вернуть его реакцию, не переоткрывая хэндл?
Re[2]: SetFileTime & LastWriteTime
От: Fortnum  
Дата: 07.09.14 17:19
Оценка:
В описании SetFileTime на MSDN ошибка в описании — дан один и тот же абзац и к параметру lastAccessTime и к параметру lastWriteTime:

To prevent file operations using the given handle from modifying the last access time, call SetFileTime immediately after opening the file handle and pass a FILETIME structure whose dwLowDateTime and dwHighDateTime members are both set to 0xFFFFFFFF.


Если предположить, что в описании lastWriteTimer опечатка, и там речь идет не о "last access time", а о "last write time", то у меня получается, что если -1 туда передать, метка времени lastWriteTime, действительно, остается без изменений и, действительно, перестает реагировать на WriteFile и SetEndOfFile (FileStream.SetLength), однако:

1) нет никакого immediatly — можно вызвать какое хочешь количество WriteFile/SetEndOfFile и даже FlushFileBuffers в любом порядке, затем вызвать SetFileTime, где lastWriteTime = -1 и время это будет до закрытия хэндла неизменным (параллельную работу хэндлов не проверял);

2) не обязательно именно -1 (0xFFFFFFFF) передавать — при передаче любого значения параметра в SetFileTime, время lastWriteTime при последующих вызовах WriteFile/SetEndOfFile/FlushFileBuffers замирает намертво; как оживить его — непонятно.
Re: только гипотеза
От: Pavel Dvorkin Россия  
Дата: 09.09.14 10:06
Оценка:
Здравствуйте, Fortnum, Вы писали:

F> using(var fileStream = new FileStream("TestFile.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))

F> {
F> //CallSetFileTime(fileStream);

F> fileStream.Write(new byte[] { 0x77 }, 0, 1);

F> }
F> }
F>}

Если раскомментировать, то здесь будут следующие операции на каждой итерации

1. Чтение времени
2. Запись времени
3. Запись в файл

Не исключено, что запись времени и запись в файл Windows делает в другом порядке. Строго говоря, запись времени — это запись в другой файл (каталог), а операции с разными файлами Windows вовсе не обязана делать в том порядке, в котором ты указал.

Если опреации выполнятся в следующем порядке

1. Чтение времени
2. Запись в файл
3. Запись времени

то и получишь свой эффект.

Попробуй вставить задержку, посмотри, что будет.
With best regards
Pavel Dvorkin
Re[2]: только гипотеза
От: Fortnum  
Дата: 09.09.14 11:29
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Попробуй вставить задержку, посмотри, что будет.


Смущает то, что такая "неправильная" последовательность — в 100% случаев. Попробовал добавить аж 30-секундную задержку, до и после FileStream.Write() — не помогает. Добавил (мало ли) сразу после FileStream.Write — FileStream.Flush(true) — нет, время lastWriteTime все так же не меняется. А еще, если вместо времени в SetFileTime передать 0xFFFFFFFF, то и GetFileTime не потребуется — время, собственно, не меняется, но lastWriteTime перестает изменяться. В MSDN написано об этом, но только в отношении lastAccessTime. Плюс есть еще пара неточностей в документации по SetFileTime: http://www.rsdn.ru/forum/dotnet/5773006
Автор: Fortnum
Дата: 07.09.14
В частности, написано, что такой эффект должен наблюдаться только при передаче 0xFFFFFFFF, а он по факту наблюдается при передаче любой метки времени.
Re[3]: только гипотеза
От: Pavel Dvorkin Россия  
Дата: 09.09.14 14:28
Оценка:
Здравствуйте, Fortnum, Вы писали:

Проверил на чистом Win API, эффект есть как ты его описал.

F>Смущает то, что такая "неправильная" последовательность — в 100% случаев. Попробовал добавить аж 30-секундную задержку, до и после FileStream.Write() — не помогает. Добавил (мало ли) сразу после FileStream.Write — FileStream.Flush(true) — нет, время lastWriteTime все так же не меняется. А еще, если вместо времени в SetFileTime передать 0xFFFFFFFF, то и GetFileTime не потребуется — время, собственно, не меняется, но lastWriteTime перестает изменяться. В MSDN написано об этом, но только в отношении lastAccessTime.


Нет, в отношении lastWriteTime тоже

lpLastWriteTime [in, optional]
A pointer to a FILETIME structure that contains the new last modified date and time for the file or directory. This parameter can be NULL if the application does not need to change this information.
To prevent file operations using the given handle from modifying the last access time, call SetFileTime immediately after opening the file handle and pass a FILETIME structure whose dwLowDateTime and dwHighDateTime members are both set to 0xFFFFFFFF.

http://msdn.microsoft.com/ru-ru/library/windows/desktop/ms724933(v=vs.85).aspx


>Плюс есть еще пара неточностей в документации по SetFileTime: http://www.rsdn.ru/forum/dotnet/5773006
Автор: Fortnum
Дата: 07.09.14
В частности, написано, что такой эффект должен наблюдаться только при передаче 0xFFFFFFFF, а он по факту наблюдается при передаче любой метки времени.


Да. Странно.

Но вот если хоть чуть-чуть изменить время, то все начинает правильно работать


int main(int argc, _TCHAR* argv[])
{
        while (true)
        {
            HANDLE hFile = CreateFile("TestFile.txt", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, 0, 0);
            FILETIME ft;
            GetFileTime(hFile, 0, 0, &ft);
            printf("%u\n", ft.dwLowDateTime);
            FILETIME ft1 = { 0 };
            ft.dwLowDateTime++; // с этой строчкой работает, без нее - нет
            SetFileTime(hFile, 0, 0, &ft);
            char cBuf = 0x77;
            DWORD dwWritten;
            WriteFile(hFile, &cBuf, 1, &dwWritten, NULL);
            CloseHandle(hFile);
        }
        return 0;
}


Такое впечатление, что установка того времени, что и так там стоит, тоже блокирует дальнейшие изменения при записи.

P.S. Хорошо, что на RSDN появилось редактирование
With best regards
Pavel Dvorkin
Отредактировано 09.09.2014 14:35 Pavel Dvorkin . Предыдущая версия . Еще …
Отредактировано 09.09.2014 14:33 Pavel Dvorkin . Предыдущая версия .
Re[4]: только гипотеза
От: Fortnum  
Дата: 09.09.14 20:29
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Нет, в отношении lastWriteTime тоже

PD>lpLastWriteTime [in, optional]
PD>A pointer to a FILETIME structure that contains the new last modified date and time for the file or directory. This parameter can be NULL if the application does not need to change this information.
PD>To prevent file operations using the given handle from modifying the last access time, call SetFileTime immediately after opening the file handle and pass a FILETIME structure whose dwLowDateTime and dwHighDateTime members are both set to 0xFFFFFFFF.
PD>http://msdn.microsoft.com/ru-ru/library/windows/desktop/ms724933(v=vs.85).aspx

Да, сказано. Только абзац для lastWriteTime дословно повторяет абзац lastAccessTime. Смотри второй абзац под каждым из этих 2-х параметров, он начинается со слов "To prevent ... from modifying the last access time". То ли они для lastWriteTime просто "access" на "modified" не заменили, то ли этот второй абзац "To prevent..." для lastWriteTime должен иметь совершенно другое содержание. Склоняюсь ко второму варианту, т.к. замена слова "access" на "modified" приводит к забавно звучащему тексту: "To prevent ... from modifying the last modified time" Хотя, может это и нормально звучит, не знаю. Но то, что там с описанием что-то не так — это точно.

>>Плюс есть еще пара неточностей в документации по SetFileTime: http://www.rsdn.ru/forum/dotnet/5773006
Автор: Fortnum
Дата: 07.09.14
В частности, написано, что такой эффект должен наблюдаться только при передаче 0xFFFFFFFF, а он по факту наблюдается при передаче любой метки времени.

PD>Да. Странно.
PD>Но вот если хоть чуть-чуть изменить время, то все начинает правильно работать
  Скрытый текст
PD>
PD>int main(int argc, _TCHAR* argv[])
PD>{
PD>        while (true)
PD>        {
PD>            HANDLE hFile = CreateFile("TestFile.txt", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, 0, 0);
PD>            FILETIME ft;
PD>            GetFileTime(hFile, 0, 0, &ft);
PD>            printf("%u\n", ft.dwLowDateTime);
PD>            FILETIME ft1 = { 0 };
PD>            ft.dwLowDateTime++; // с этой строчкой работает, без нее - нет
PD>            SetFileTime(hFile, 0, 0, &ft);
PD>            char cBuf = 0x77;
PD>            DWORD dwWritten;
PD>            WriteFile(hFile, &cBuf, 1, &dwWritten, NULL);
PD>            CloseHandle(hFile);
PD>        }
PD>        return 0;
PD>}
PD>

PD>Такое впечатление, что установка того времени, что и так там стоит, тоже блокирует дальнейшие изменения при записи.

И у тебя на WriteFile меняется дата изменения файла?

Я вот так написал:

  Скрытый текст
static void Main()
{
    var random = new Random();

    while (true)
    {
        using(var fileStream = new FileStream("TestFile.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read))
        {
            var hFile = fileStream.SafeFileHandle.DangerousGetHandle();

            for (int i = 0; i < 10; i++)
            {
                fileStream.Write(new byte[] { (byte)random.Next(0, 255) }, 0, 1);
            }

            fileStream.Flush(true);

            SetSameFileTime(hFile);

            for (int i = 0; i < 10; i++)
            {
                fileStream.Write(new byte[] { (byte)random.Next(0, 255) }, 0, 1);
            }

            fileStream.Flush(true);

            IncFileTimeALittle(hFile);

            for (int i = 0; i < 10; i++)
            {
                fileStream.Write(new byte[] { (byte)random.Next(0, 255) }, 0, 1);
            }

            fileStream.Flush(true);
        }
    }
}

static void SetSameFileTime(IntPtr hFile)
{
    var pLastWriteTime = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(System.Runtime.InteropServices.ComTypes.FILETIME)));

    try
    {
        if (!WinApi.GetFileTime(hFile, IntPtr.Zero, IntPtr.Zero, pLastWriteTime))
            throw new Exception();

        if (!WinApi.SetFileTime(hFile, IntPtr.Zero, IntPtr.Zero, pLastWriteTime))
            throw new Exception();
    }
    finally
    {
        Marshal.FreeHGlobal(pLastWriteTime);
    }
}

static void IncFileTimeALittle(IntPtr hFile)
{
    var pLastWriteTime = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(System.Runtime.InteropServices.ComTypes.FILETIME)));

    try
    {
        if (!WinApi.GetFileTime(hFile, IntPtr.Zero, IntPtr.Zero, pLastWriteTime))
            throw new Exception();

        var fileTime = (System.Runtime.InteropServices.ComTypes.FILETIME)Marshal.PtrToStructure(pLastWriteTime, typeof(System.Runtime.InteropServices.ComTypes.FILETIME));

        fileTime.dwHighDateTime++;

        Marshal.StructureToPtr(fileTime, pLastWriteTime, false);

        if (!WinApi.SetFileTime(hFile, IntPtr.Zero, IntPtr.Zero, pLastWriteTime))
            throw new Exception();
    }
    finally
    {
        Marshal.FreeHGlobal(pLastWriteTime);
    }
}


Так у меня время меняется только до вызова SetSameFileTime, и во время вызова IncFileTimeALittle. То есть IncFileTimeALittle сам время, естественно, меняет. Но вот fileStream.Write после него уже нет — это увеличенное немного время стоит как вкопанное При этом я даж dwHighDateTime инкременчу, чтоб уж наверняка.
Re[5]: только гипотеза
От: Pavel Dvorkin Россия  
Дата: 10.09.14 08:09
Оценка:
Здравствуйте, Fortnum, Вы писали:

PD>>Такое впечатление, что установка того времени, что и так там стоит, тоже блокирует дальнейшие изменения при записи.


F>И у тебя на WriteFile меняется дата изменения файла?


См. мой пример, там печатается младшая часть датавремени. Она меняется.
Твой пример проверить не могу, я сейчас не дома.

>fileTime.dwHighDateTime++;


Я младшую часть менял. Инкремент старшей — это плюс 4 млрд * 100 нсек
With best regards
Pavel Dvorkin
Re[6]: только гипотеза
От: Fortnum  
Дата: 10.09.14 08:22
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

F>>И у тебя на WriteFile меняется дата изменения файла?

PD>См. мой пример, там печатается младшая часть датавремени. Она меняется.
PD>Твой пример проверить не могу, я сейчас не дома.
>>fileTime.dwHighDateTime++;
PD>Я младшую часть менял. Инкремент старшей — это плюс 4 млрд * 100 нсек

Так младшую я инкрементил, как у тебя, так же без результата — от WriteFile lastWriteTime не меняется. Просто при увеличении младшей части оно визуально в окне свойств не меняется... а вот GetFileTime-то я и не попробовал, а надо бы... так я для того, чтоб визуально можно было наблюдать увеличение времени, стал старшую часть инкрементить: по факту 7 минут 9 секунд получается... 100 нсек = 100E-10 = 1E-8... умножить на млрд = 1E1 = 40 секунд... может, ошибся в вычислениях , но число небольшое, и видимое. А вот GetFileTime позже попробую еще посмотреть, а то я всё по окну свойств файла смотрю, может API даст другой результат, хотя вряд ли.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.