Go: предложение: os: Create/Open/OpenFile() установить FILE_SHARE_DELETE в окнах

Созданный на 16 мая 2019  ·  194Комментарии  ·  Источник: golang/go

В Linux и MacOS мы можем написать это; в Windows происходит сбой с «нарушением общего доступа»:

path := "delete-after-open"
fd, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0600)
if err != nil { ... }
err = os.Remove(path)            // or os.Rename(path, path+"2")
if err != nil { ... }
fd.Close()

Если вы разрабатываете для Windows и развертываете для Linux и т. д., и ваш код основан на этом _недокументированном_ GOOS=windows поведении os.Rename() и .Remove(), он неисправен и, возможно, уязвим. Обратите внимание, что пакет «os» имеет _пятнадцать упоминаний_ о другом поведении, специфичном для Windows.

Чтобы это исправить, используйте syscall.Open() по адресу https://golang.org/src/syscall/syscall_windows.go#L272.
нужно sharemode |= FILE_SHARE_DELETE

Microsoft рекомендует сделать это значение по умолчанию: https://github.com/golang/go/issues/32088#issuecomment -502850674.
Rust сделал это по умолчанию: https://doc.rust-lang.org/std/os/windows/fs/trait.OpenOptionsExt.html.
Mingw-w64 сделал это по умолчанию семь лет назад:
https://sourceforge.net/p/mingw-w64/code/HEAD/tree/stable/v3.x/mingw-w64-headers/include/ntdef.h#l858
Erlang сделал его по умолчанию более шести лет назад: erlang /
Python не смог принять его из-за ограничений среды выполнения MSVC: https://bugs.python.org/issue15244.

~Поэтому syscall.Open() должен использовать file_share_delete по умолчанию , а syscall должен обеспечивать оба:
а) глобальный переключатель для его отключения (для любых существующих приложений, которые полагаются на его отсутствие) и
б) флаг для использования с os.OpenFile(), чтобы отключить его для определенного дескриптора файла.~

Обновление после https://github.com/golang/go/issues/32088#issuecomment-532784947 от @rsc :
а) os.Create/Open/OpenFile() всегда должен
b) syscall.Open() в Windows должен принимать флаг, разрешающий file_share_delete, и
c) системный вызов в Windows должен экспортировать константу для нового флага.

В документах для os.Remove() также следует отметить, что для повторного использования имени файла после удаления открытого файла в Windows необходимо выполнить: os.Rename(path, unique_path); os.Remove(unique_path) .

Документация WinAPI
https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-deletefilea
https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-createfilea

Если есть причина не делать этого, это должно быть задокументировано в os.Remove() и .Rename().

копия @alexbrainman
@gopherbot добавить ОС-Windows

FrozenDueToAge OS-Windows Proposal Proposal-FinalCommentPeriod release-blocker

Самый полезный комментарий

Я попытаюсь представить здесь точку зрения команды Windows... мы бы предпочли, чтобы Go просто всегда устанавливал FILE_SHARE_DELETE без каких-либо опций. В общем, чтобы упростить перенос в/из Windows, мы бы предпочли согласованное поведение с Linux, и я не понимаю, почему пользователи Windows или программное обеспечение предпочитают поведение по умолчанию !FILE_SHARE_DELETE, достаточное для того, чтобы это было исключением из правила.

Интересное замечание, противоречащее комментарию @mattn : в самой последней версии Windows мы обновили DeleteFile (в NTFS), чтобы выполнить удаление «POSIX», при котором файл немедленно удаляется из пространства имен, а не ждет, пока все откроются. указывает на файл, который нужно закрыть. Он по-прежнему учитывает FILE_SHARE_DELETE, но в остальном ведет себя больше как отвязка POSIX. Эта функциональность была добавлена ​​для WSL и считалась полезной по умолчанию для программного обеспечения Windows.

Другими словами, если вы запустите тестовую программу mattn и последовательность del, dir и т. д. в последней версии Windows, вы увидите, что файл исчезнет из пространства имен сразу после удаления файла, а не после выхода тестовой программы. Так же, как линукс.

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

Все 194 Комментарий

/cc @alexbrainman

syscall.Open() на https://golang.org/src/syscall/syscall_windows.go#L272
следует использовать sharemode := ... | FILE_SHARE_DELETE

Почему?

Алекс

FILE_SHARE_DELETE включает приведенный выше пример кода, который работает в MacOS и Linux, но не работает в Windows. Это также необходимо для:

fd, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0600)
defer fd.Close()
_, err = fd.Write(...)
err = fd.Sync()  // file now safe to share
err = os.Rename(path, path+"2")
_, err = fd.Read(...)

FILE_SHARE_DELETE включает приведенный выше пример кода, который работает в MacOS и Linux, но не работает в Windows.

Почему бы нам вместо этого не скорректировать код MacOS и Linux?

Это также необходимо для:

Я не понимаю, что ты пытаешься сказать.

Алекс

@networkimprov Вы должны вызывать Remove после Close() в Windows.

path := "delete-after-open"
fd, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0600)
if err != nil { panic(err) }
fd.Close()
err = os.Remove(path)            // or os.Rename(path, path+"2")
if err != nil { panic(err) }

@alexbrainman при надежном файловом

Открытые файлы могут быть переименованы или удалены по умолчанию в Unix. Кажется недосмотром то, что флаг Windows для этой возможности не установлен. Сомневаюсь, что мы убедим команду Go изменить то, как это работает для Linux и MacOS :-)

@mattn, пожалуйста, примените исправление, которое я описал, и попробуйте код, который я разместил.

Сомневаюсь, что мы убедим команду Go изменить то, как это работает для Linux и MacOS :-)

Я в порядке, как сейчас.

Алекс

Есть ли причина для отказа от этой общей возможности в Windows?

Можете ли вы предоставить переключатель в syscall_windows.go, чтобы мы могли выбрать поведение Unix при запуске программы?

Я не против, если мы добавим новый API или флаги. Но я возражаю против изменения текущего поведения. Поскольку FILE_SHARE_DELETE не совпадает с поведением Unix.

#include <windows.h>
#include <stdio.h>

int
main(int argc, char* argv[]) {
  HANDLE h = CreateFile("test.txt",
      GENERIC_READ | GENERIC_WRITE,
      FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
      NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  getchar();
  char buf[256] = {0};
  DWORD nread;
  printf("%d\n", ReadFile(h, buf, 256, &nread, NULL));
  printf("%s,%d\n", buf, nread);
  return 0;
}

Скомпилируйте этот код в Windows и попробуйте запустить его как test.exe . Пока это приложение ожидает нажатия клавиши, откройте новый cmd.exe, удалите «test.txt», как показано ниже.

image

Файл может быть удален, но оставаться там, пока существует процесс. Таким образом, это изменение не будет работать хорошо для ваших ожиданий.

Я понимаю, что запись в каталоге не удалена, но в документах говорится

Последующие вызовы CreateFile для открытия файла завершаются ошибкой ERROR_ACCESS_DENIED.

Так что я не понимаю ваш экранный журнал.

В любом случае, такой переключатель был бы в порядке:

syscall.OpenFileShareDelete = true

Я хотел бы упомянуть, что я был укушен этим на работе раньше.

В основном у нас было:

f, err := os.Open(...)
if err != nil { ... }
defer f.Close()

// lots of code

if err := os.Rename(...); err != nil { ... }

Код работал нормально на наших платформах CI на основе Unix, но в Windows давал сбои.

Предполагая, что нет никаких странных побочных эффектов, было бы неплохо, если бы все работало .

IIRC, в прошлом мы внесли несколько изменений в поведение GOOS=windows и plan9, чтобы более точно соответствовать семантике Unix. Я был бы не против сделать это еще одним таким случаем, если семантика достаточно близка. Однако комментарий

Однако я не хочу видеть какой-то глобальный вариант. Это просто похоже на кошмар отладки.

@брэдфитц
Скорее всего, нет, пожалуйста, обратитесь к моему комментарию здесь
https://groups.google.com/forum/#!topic/golang-dev/R79TJAzsBfM
или, если вы предпочитаете, я могу скопировать содержимое сюда, так как есть также возможное решение, хотя я бы не реализовал его как поведение по умолчанию , так как это не соответствовало бы тому, как ведут себя программы Windows, а скорее обходной путь для создания аналогичного креста. -ос опыт.

@guybrand пишет в ветке golang-dev:

моя программа записывает файл с именем "my.data", а затем вызывает другую программу и не ждет ее окончания... эта программа, скажем, загружает этот файл, что занимает 10 секунд (давайте назовем эту другую программу "загрузчиком") .
...
Когда моя программа вызывает .Remove в Windows (FILE_SHARE_DELETE включен):
...
загрузчик получит сообщение об ошибке, сообщающее, что файл удален (вероятно, EOF).

Предполагая, что «загрузчик» открывает файл до того, как «моя программа» вызывает os.Remove(), я думаю, вы противоречите документам Win API:

_DeleteFile помечает файл для удаления при закрытии. Поэтому удаление файла не происходит до тех пор, пока не будет закрыт последний дескриптор файла. Последующие вызовы CreateFile для открытия файла завершаются ошибкой ERROR_ACCESS_DENIED._

«Сопряжение _open_osfhandle() CreateFile с _fdopen», можете указать пример кода?

Если мы добавим FILE_SHARE_DELETE, многие программисты ошибочно воспользуются им для имитации поведения Unix. В этом случае вы можете сделать функцию, разделенную сборочными ограничениями для каждой ОС. И он должен вернуть os.NewFile() , используйте FILE_SHARE_DELETE в Windows.

сделать функцию, разделенную ограничениями сборки для каждой ОС... вернуть os.NewFile(), использовать FILE_SHARE_DELETE в Windows

Чтобы сохранить функциональность os.OpenFile(), этот план требует повторной реализации всех:

ОС.ОткрытьФайл()
открытьФайлНолог()
открыть файл()
открытьКаталог()
новый файл()
исправитьДлинныйПуть()
системный вызов.Открыть()
makeInheritSa()

@networkimprov

@guybrand пишет в ветке golang-dev:

моя программа записывает файл с именем "my.data", а затем вызывает другую программу и не ждет ее окончания... эта программа, скажем, загружает этот файл, что занимает 10 секунд (давайте назовем эту другую программу "загрузчиком") .
...
Когда моя программа вызывает .Remove в Windows (FILE_SHARE_DELETE включен):
...
загрузчик получит сообщение об ошибке, сообщающее, что файл удален (вероятно, EOF).

Предполагая, что «загрузчик» открывает файл до того, как «моя программа» вызывает os.Remove(), разве вы не противоречите документам Win API?

DeleteFile помечает файл для удаления при закрытии. Поэтому удаление файла не происходит до тех пор, пока не будет закрыт последний дескриптор файла. Последующие вызовы CreateFile для открытия файла завершаются ошибкой ERROR_ACCESS_DENIED.

«Сопряжение _open_osfhandle() CreateFile с _fdopen», можете указать пример кода?

Я не вижу противоречия с WIndows API, пожалуйста, укажите, что похоже на противоречие.

Что касается примера кода, я немного погуглил и нашел это:
http://blog.httrack.com/blog/2013/10/05/creating-deletable-and-movable-files-on-windows/

НО
Обратите внимание: это даст вам поведение, подобное nix, только внутри, любая другая внешняя программа, работающая с ним, сломает его (поскольку на 99% используется стандартный Windows API).

@networkimprov
Если вы имеете в виду, что «это звучит так, как будто «dir my.data» будет отображать файл, насколько я помню, это было поведение до Windows .... XP или 7 (не помню, какой) и измененный с тех пор (возможно, проводник просто скрывает это как-то) - я могу повторить тестирование в следующий раз, когда запущу свою среду Windows (происходит каждые несколько недель...)

Если вы имеете в виду, что «это звучит так, как будто другие процессы с дескриптором файла по-прежнему смогут читать и записывать в файл», я бы поспорил, что обед, когда вы читаете запись в такой файл, вы действительно получаете ошибку стиля «EOF» - но снова нужно подтвердить, чтобы быть 100% положительным.

В любом случае моя точка зрения будет (на данный момент - я принимаю "стороны" в "собственной" и "независимой от ОС" точке) - даже если вы реализуете решение в стиле _fdopen, вы получите несоответствие между вашим сервисом и все другие исполняемые файлы, с которыми вы сотрудничаете, поэтому использование может быть только во взаимодействии с другими исполняемыми файлами go (или редкими службами, которые ДЕЙСТВИТЕЛЬНО используют fd напрямую).

Другими словами, ваше приложение будет «самым умным ребенком в классе», которого не сможет понять ни один другой ребенок.
Два примера из многих, которые я могу придумать:
Входы :
Мое приложение загружает файл, антивирус идентифицирует его harfull и удаляет/помещает в карантин (== как бы переименовывает) его, если я использую fd - мое приложение все равно сможет делать с ним все, что захочет (что колл, но может закончиться наткнулся на вирус...)
выходы :
мое приложение передает файл в другую службу (например, «загрузчик») и удаляет его, я даже потрудился и написал тестер в go, чтобы убедиться, что все работает нормально - и тест проходит.
Теперь вместо моего го-теста я использую filezilla, wetransfter, dropbx API, что угодно
он потерпит неудачу/не будет вести себя так же, как работает мой тест...

Вы все еще думаете, что изменение этого поведения на поведение по умолчанию имеет смысл?

Есть ли причина для отказа от этой общей возможности в Windows?

Я никогда не рассматривал этот вопрос. Я не знаю.

То, как файлы Go работают в Windows, согласуется со всеми другими инструментами разработчика, которые я использовал в своей жизни. Для меня было бы удивительно, если бы файлы Go работали так, как вы предлагаете. Я также подозреваю, что это сломает многие существующие программы.

Алекс

Я также подозреваю, что это сломает многие существующие программы.

@alexbrainman Я также предложил включить его вместо изменения значения по умолчанию.

@bradfitz есть syscall.SocketDisableIPv6, который на самом деле не отличается от флага для настройки поведения syscall.Open().

Поскольку в syscall_windows*.go указано
func Open (строка пути, режим int, perm uint32) (дескриптор fd, ошибка ошибки) {
....
режим общего доступа: = uint32 (FILE_SHARE_READ | FILE_SHARE_WRITE)

и type_windows*.go имеет
FILE_SHARE_DELETE = 0x00000004

Все вполне готово, вопрос только как реализовать флаг.
Мне не нравится глобальная опция, а также то, что она не является явной, и хотя один разработчик может помнить, что он установил os.DeleteUponLastHandleClosed = 1, это не очень хорошая практика для долгосрочных или нескольких разработчиков.
Другими вариантами могут быть установка определенного зарезервированного номера для флагов, например:
fd, ошибка: = os.OpenFile (путь, os.O_RDWR|os.O_CREATE|os.DELETE_WHEN_FREED, 0600)
тогда как DELETE_WHEN_FREED может быть даже 0 для env. кроме окон,

Другим вариантом может быть использование параметра perm, который не поддерживается для Windows, это может стать немного неудобным.
fd, ошибка: = os.OpenFile (путь, os.O_RDWR|os.O_CREATE, 777)
777 зарезервирован, поэтому нам понадобится либо 1777, либо -777 для поддержки обеих систем.
сделать читаемым DELETE_WHEN_FREED | 777

последний вариант, который я могу придумать, это os.OpenDeletableFile(
Что бы os.OpenFile на nix и
повернуть
режим общего доступа: = uint32 (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)
на окнах

все вышеперечисленные решения просты в реализации, немного больше времени для тестирования (перекрестная ОС), просто нужен избиратель...

Один из способов поддержки такого поведения без изменения значений по умолчанию или расширения поверхности API может состоять в том, чтобы просто принять syscall.FILE_SHARE_DELETE в параметре flag os.OpenFile в Windows и сложить его в вычисляемый Стоимость sharemode . Поскольку флаги syscall.O_* (и, следовательно, os.O_* ) в Windows в любом случае используют выдуманные значения , их можно спроектировать таким образом, чтобы избежать столкновения с любыми флагами, специфичными для Windows, которые нужно включить. К счастью, они уже избегают коллизий с syscall.FILE_SHARE_DELETE .

В любом случае, я бы решительно возражал против изменения поведения по умолчанию. Сделав FILE_SHARE_DELETE значением по умолчанию, вы окажетесь в том же положении, что и системы POSIX, с точки зрения невозможности обеспечить обход файловой системы без гонок. Если этот флаг является необязательным, Windows не нуждается в эквиваленте openat , renameat , readlinkat и т. д.

Я действительно не думал о том, что мы должны здесь делать, но я хочу прояснить одну вещь:

Можете ли вы предоставить переключатель в syscall_windows.go, чтобы мы могли выбрать поведение Unix при запуске программы?

Мы не будем этого делать. Это сделало бы невозможным использование одной программой разных пакетов, ожидающих разного поведения.

@havoc-io
Ваше предложение приведет к созданию программ, подверженных ошибкам, поскольку syscall.FILE_SHARE_DELETE отличается от стандартного поведения POSIX.
Обратите внимание, что авторы syscall_windows*.go знали о флаге FILE_SHARE_DELETE (он там есть) и решили, что его использование не является предпочтительным.
Почему?

давайте возьмем код Лиама, он
отложить fd.Close()
удаляет/переименовывает
а потом читает/пишет

поэтому фактический порядок сортировки
открыть
пометить как удаленное
чтение/запись (и, возможно, кросс-процесс/кросс-процедура чтения и записи)
Закрыть

Текущее поведение Windows предупреждает разработчика «это неправильно», разработчик, вероятно, также отложит удаление.
Если вы добавите FILE_SHARE_DELETE, Windows позволит вам «пометить как удаление, даже если файл открыт», НО другой процесс/горутина, работающая одновременно, попытается получить доступ к файлу между удалением и закрытием (что, вероятно, является более длинной задачей в этом коде). ) не получится
Результат: вместо последовательного поведения «вы можете сделать это» вы получите «иногда», особенно когда есть много одновременных пользователей, это не удается, и я не знаю, почему.
Таким образом, вы не решаете проблему, а просто обрабатываете конкретный вариант использования, в котором разработчик «запускает это только один раз».
Мой голос за последовательное поведение, конечно...

Как это отличается от Posix?
После того, как файл помечен как удаленный, он больше не находится в таблице файлов, он существует только как fd, что позволяет другой процедуре/процессу создавать файл с тем же именем.

Я думаю, что нам следует перестать искать «одно и то же решение», поскольку есть различия, которые мы не сможем решить в рамках go (имена файлов с учетом регистра? :) мы позволим Linux 5.2 сделать это...)

В целом это похоже на поиск решения для временного файла, если это так, Windows поддерживает GetTempFileName, что можно рассматривать как стандартное решение для файла, который «используется, а затем удаляется».

Если, с другой стороны, мы хотим разрешить разработчику откладывать удаление в окнах, это возможно, см. мои предложения выше, но разработчик должен взять на себя ответственность за это и понять сознание - поэтому должен быть флаг с хорошим соглашением об имени.

Основное использование этой функции — переименование открытого файла после его создания:

fd, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0600)
_, err = fd.Write(...)
err = fd.Sync()  // file now safe to share
err = os.Rename(path, shared_path)
// os.Close() at program exit

Я не знаю, почему бы вам не добавить флаг os.O_WRNDEL (переименование/удаление Windows; не работает на других платформах) и документировать различия этого режима с Unix; что удаленный файл остается в своем каталоге, но не может быть открыт до os.Close(). Также упомяните, что перемещение файла во временный каталог перед его удалением обеспечивает поведение, более похожее на Unix.

@guybrand Я думаю, возможно, вы неправильно понимаете мое предложение. Я не выступаю за то, чтобы FILE_SHARE_DELETE было включено по умолчанию — на самом деле я против этого по причинам, упомянутым во втором абзаце моего комментария. Мое предложение заключалось в механизме, который позволяет пользователям соглашаться на поведение FILE_SHARE_DELETE (для каждого файла), продолжая использовать файловую инфраструктуру пакета os . Это предложение предоставляет механизм для этого без расширения поверхности API любого пакета.

Я не знаю, почему вы не добавили бы флаг os.O_WRNDEL (переименование/удаление Windows; не работает на других платформах)

@networkimprov По сути, это то, что я предлагаю, за исключением того, что нет смысла определять новый флаг, поскольку уже существует совершенно правильный: syscall.FILE_SHARE_DELETE . Единственным изменением реализации будет отслеживание этого флага в syscall.Open в Windows и добавление проверок, чтобы гарантировать, что никакие будущие дополнения к флагам syscall.O_* / os.O_* конфликтовать с этим флагом. Затем документацию для os.OpenFile можно обновить, чтобы отразить принятие этого флага в Windows.

@havoc-io извините за недоразумение, это был мой вывод из:
"... чтобы просто принять syscall.FILE_SHARE_DELETE... в вычисляемый общий режим..."

Добавление os.O_WRNDEL соответствует моему второму предложению, за исключением того, что оно не является достаточно явным для разработчика с точки зрения «каким будет поведение», возможно, os.WADRNDEL — Windows разрешает отложенное переименование/удаление).

@alexbrainman Я также предложил включить его вместо изменения значения по умолчанию.

Я не интересуюсь. Спасибо.

Алекс

@guybrand переименование открытого файла не откладывается, я проверил.

Извините, я снова спрашиваю, что вы предлагаете? удалить открывающийся файл? переименовать открывающийся файл? оба?

Трое из нас сейчас предлагают новый флаг, например

fd, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE| os._WRNDEL, 0600)

fd, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE| syscall.FILE_SHARE_DELETE, 0600)

Трое из нас сейчас предлагают новый флаг

Я думаю, что новый флаг неуместен, особенно тот, который не может быть легко разобран кем-то, кто читает код. Использование syscall.FILE_SHARE_DELETE было бы гораздо более явным и ясным, сразу сигнализируя читателю, что происходит что-то специфичное для платформы.

Это уже разрешено на платформах POSIX. Существует множество специфичных для POSIX (и даже для конкретной платформы) флагов open , которые не включены в пакет os . Например, не имеет смысла добавлять что-то вроде os._DEVTONLY для поддержки флага Дарвина O_EVTONLY , потому что вы можете просто передать флаг из пакета syscall непосредственно в os.OpenFile , например:

os.OpenFile(..., os.O_RDONLY | syscall.O_EVTONLY, ...)

В этом случае флаг, зависящий от платформы, будет передан базовому системному вызову open .

Если поведение FILE_SHARE_DELETE — это действительно то, что нужно людям, то я думаю, что ответ заключается в том, чтобы просто сделать так, чтобы os.OpenFile мог аналогичным образом передавать флаги нижележащему вызову CreateFileW в Windows. Хотя бы за FILE_SHARE_DELETE .

Пример реализации можно найти здесь .

@networkimprov

Трое из нас сейчас предлагают новый флаг, например

Что вы ожидаете от флага?

@guybrand переименование открытого файла не откладывается, я проверил.

Верно, но обоим требуется FILE_SHARE_DELETE, чтобы их можно было разрешить, пока файл открыт.
Если вы имеете в виду мою предложенную терминологию, мы можем
os.WARNDFDEL — разрешение Windows на переименование и отложенное удаление слишком длинное, я бы сам вообще использовал аббревиатуру WINDOWS_ALLOW_OPENNED_FILE_RENAME_OR_DEFFERED_DELETE
лучше ИМО.

@guybrand Я действительно не вижу смысла в добавлении нового флага для конкретной платформы со сложным именем и непрозрачным поведением в пакет os , когда совершенно хороший флаг для этого существует уже несколько десятилетий ( FILE_SHARE_DELETE ). Использование syscall.FILE_SHARE_DELETE в качестве флага для этого гораздо более ясно и ясно. Как я упоминал выше , существует множество флагов, специфичных для POSIX и платформы, которые не добавлены в пакет os , но по-прежнему принимаются им.

syscall.FILE_SHARE_DELETE предназначен только для Windows; нам нужно что-то, что не работает в другом месте. Может быть, он должен появиться в pkg os с префиксом WIN или WINDOWS?

@mattn, пожалуйста, посмотрите патч выше от @havoc-io

нам нужно что-то, что не работает в другом месте.

@networkimprov Флаг не должен существовать где-либо еще. Ваш код Windows будет выглядеть так:

import (
    "syscall"
    "os"
)

file, err := os.OpenFile(..., os.O_RDWR | syscall.FILE_SHARE_DELETE, ...)

Да, этот код должен быть закрыт на платформе, но вам, вероятно, придется это сделать в любом случае, поскольку поведение с этим флагом не будет соответствовать поведению систем POSIX. Или, если вы хотите иметь один и тот же код на всех платформах, вы можете определить свою собственную константу (со значением syscall.FILE_SHARE_DELETE в Windows и 0 в системах, отличных от Windows) и передать это до os.OpenFile . Это именно то, что вы сделали бы, если бы захотели ввести флаг O_* конкретной платформы, который не был частью пакета os (например, O_EVTONLY на Darwin или O_ASYNC в Linux).

Когда я пишу код, я ожидаю, что он будет кросс-платформенным, FILE_SHARE_DELETE не существует в syscall_*, только в Windows (поэтому anf не будет компилироваться в других системах).

Можем ли мы использовать 0x4 const как его:
FILE_SHARE_DELETE = 0x00000004

Помимо того, что некрасиво,
netbsd_arm
O_NDELAY = 0x4
О_НОНБЛОК = 0x4

Таким образом, использование 0x4 создаст другой код в netbsd_arm.

Таким образом, либо мы добавляем FILE_SHARE_DELETE для всех платформ, для Windows одни будут 0x4, другие будут 0x0 и, таким образом, «отбрасываем» его, либо создаем специальный флаг для этой проблемы.

И в любом случае мы должны изменить syscall_windows*
функция открыта(
...
режим общего доступа: = uint32 (FILE_SHARE_READ | FILE_SHARE_WRITE | isDeleteOn)
где isDeleteOn проверит, включает ли режим новый флаг

@guybrand

Когда я пишу код, я ожидаю, что он будет кроссплатформенным.

Я хотел бы предупредить вас, что только потому, что один и тот же вызов os.OpenFile компилируется на нескольких платформах, это не означает, что ваш код является кросс-платформенным. Существует ряд соображений безопасности в Windows, которые необходимо учитывать при использовании FILE_SHARE_DELETE , а семантика жизненных циклов файлов настолько отличается в системах POSIX и Windows, что действительно трудно представить сценарий, в котором ваш код обработки файлов не будет выглядеть хоть немного по-разному на разных платформах. Использование FILE_SHARE_DELETE фактически помещает вас в ситуацию, предшествовавшую POSIX.1-2008, без какого-либо способа обеспечить обход файловой системы без гонок (и без преимуществ доступа к файлам POSIX после unlink для открытия файлы).

В любом случае, если вам нужен флаг со значением syscall.FILE_SHARE_DELETE в Windows и 0 на других платформах, вы все равно можете легко сделать это с помощью собственного определения константы, которое контролируется тегами сборки, а затем ваш основной код, который вызывает os.OpenFile может быть одинаковым на всех платформах (с использованием этого пользовательского флага).

В пакетах syscall других платформ существует множество флагов для конкретных платформ, но не все они могут быть доступны в пакете os и недоступны на платформах, где они не используются. т поддерживается. Если вам нужен специфичный для платформы код и поведение, необходимо использовать пакет syscall .

Единственное, что должна (потенциально) делать стандартная библиотека Go, — это поддерживать syscall.FILE_SHARE_DELETE в syscall.Open (и, следовательно, os.OpenFile ) в Windows.

Как я упоминал выше , если мы добавим FILE_SHARE_DELETE, многие программисты по ошибке воспользуются им для имитации поведения Unix. Но это нехорошо. Например, подумайте о случае создания приложения для просмотра существования файла. Поддерживается, чтобы файл не был найден в других приложениях, если приложение удаляет файл. Но FILE_SHARE_DELETE только отметьте для удаления позже. Файл остался. В UNIX это работает хорошо, а в Windows нет. Пожалуйста, НЕ используйте FILE_SHARE_DELETE для имитации поведения UNIX.

@mattn Я согласен с вашими опасениями - люди могут попытаться использовать флаг для удобства, чтобы заставить код Windows «работать так же», как системы POSIX, не понимая тонких последствий. Однако я думаю, что если им нужно перейти к пакету syscall , чтобы получить доступ к этому флагу, они могли бы потратить время, чтобы понять, что он делает.

@mattn , для поведения, подобного Unix, приложениям потребуются 2 дополнительные строки независимого от платформы кода:

fd, err := os.OpenFile(path, ... | syscall.FILE_SHARE_DELETE, 0600)
...
tmp_path := mkTempName()
err = os.Rename(path, tmp_path)  // works immediately; path is now available
err = os.Remove(tmp_path)

Для документирования этого требуется одно предложение в документации для os.OpenFile().

Пожалуйста, не заставляйте меня распространять патч для системного вызова pkg, который должен применяться всеми, кто работает с моим кодом Windows! См. также https://github.com/golang/go/issues/32088#issuecomment-493876119 .

Я думаю, что мы все согласны с большинством фактов, просто сформулируем это по-разному, поэтому я попытаюсь обобщить и определить курс действий:

  1. Согласен: Windows и POSIX различаются описанным выше поведением, и мы не должны стремиться к одному и тому же поведению.
  2. Согласен: запрос на разрешение FILE_SHARE_DELETE в качестве флага полезен
  3. Предложение: давайте изменим тикет на «support FILE_SHARE_DELETE», чтобы это звучало как запрос функции, а не как ошибка.
  4. В основном согласны: эта функция должна быть флагом разработчика на уровне программы, а не базовым пакетом.

Пункты действий:

  1. изменить syscall_windows.go
    функция открыта(
    ...
    режим общего доступа: = uint32 (FILE_SHARE_READ | FILE_SHARE_WRITE | (режим и FILE_SHARE_DELETE) )
  2. Разработчик, который хотел бы использовать это поведение, либо
    file, err := os.OpenFile(..., os.O_RDWR | syscall.FILE_SHARE_DELETE, ...)
    или если они хотели бы, чтобы их программа была кросс-платформенной, они создали бы внутренний флаг:

mycode_windows.go
const OPENFILEFLAG = syscall.FILE_SHARE_DELETE
mycode_others.go
const OPENFILEFLAG = 0

а затем используйте:
file, err := os.OpenFile(..., os.O_RDWR | OPENFILEFLAG, ...)

Если это согласовано, исправление минимально (один вкладыш) с очень низким риском.

Позвольте проголосовать за это, и мы сможем быстро это исправить

Согласен: запрос на разрешение FILE_SHARE_DELETE в качестве флага полезен

Я пока не согласен с этим, так как не понимаю, какова цель этого вопроса?

@mattn, пожалуйста, см. https://github.com/golang/go/issues/32088#issuecomment -494305074

Назначение: разрешить разработчикам переименовывать или удалять открытые файлы.

Чего не хватает: syscall_windows.go в настоящее время жестко закодирован :
func Open(path string, mode int, perm uint32) (fd Handle, err error) {
...
sharemode := uint32(**FILE_SHARE_READ | FILE_SHARE_WRITE**)

Предлагаемое изменение:
Когда разработчик передает параметр режима с включенным побитовым значением 4 (FILE_SHARE_DELETE), режим общего доступа изменится соответственно на
sharemode := uint32(FILE_SHARE_READ | FILE_SHARE_WRITE | **(mode & FILE_SHARE_DELETE)** )

Мне все еще интересно, почему вы не вызываете Close() перед Rename() или Remove(). Вы можете прочитать код стандартной библиотеки Go. Файлы закрываются функцией Close() перед Rename() или Remove(). Почему вы должны вызывать Close() после Renamoe/Rename?

Я не уверен, какое экономическое обоснование рассматривает поводу - это временный общий файл - мое приложение и другое приложение используют этот файл, я хочу убедиться (даже если мое приложение выйдет из строя) этот файл больше не будет существовать после того, как я закончу, поэтому я создаю его, удаляю (), и когда я либо закрываю его, либо мое приложение закрывается, файл удаляется.

но похоже, что @networkimprov хочет использовать Rename(), так что, вероятно, вариант использования diff.

Как упоминалось в https://github.com/golang/go/issues/32088#issuecomment -494305074, нам может не понадобиться закрывать переименованный файл до выхода из программы.

@mattn ,

Я хочу убедиться (даже если мое приложение выйдет из строя), что этот файл больше не будет существовать после того, как я закончу

@guybrand Оператор defer не гарантирует выполнение при каждом типе сбоя, поэтому, если вы пытаетесь обеспечить удаление файла, откладывание операции os.Remove не является надежным способом иди об этом. Если вам нужен файл, который автоматически удаляется, лучше использовать временный файл, который удаляет система. Если вы хотите, чтобы расположение файла было общим для двух программ, оно должно координироваться блокировками (например, блокировки fcntl в POSIX и LockFileEx / UnlockFileEx в Windows), а не наличием файла. .

Как упоминалось в #32088 (комментарий), нам может не понадобиться закрывать переименованный файл до выхода из программы.
@mattn ,

@networkimprov Операция переименования после открытия является допустимым вариантом использования, но если вы являетесь программой, содержащей файл HANDLE , разве вы уже не можете это сделать? Единственная цель FILE_SHARE_DELETE — разрешить другим процессам переименовывать файл, пока вы держите его открытым. В любом случае вы можете использовать FILE_SHARE_DELETE сейчас, вам просто нужно вручную вызвать CreateFileW и передать результат os.NewFile . Вы можете найти пример того, как это выглядит здесь . Полученный HANDLE можно просто передать os.NewFile .

@mattn Я начинаю соглашаться с вами, что этот флаг будет скорее ножным ружьем, чем полезным инструментом. Просто для ясности: на самом деле мне не нужен этот флаг, и единственные аргументы, которые я вижу в его существовании, — это полнота и паритет контроля между POSIX и Windows. С другой стороны, как я упоминал выше, также относительно легко вручную вызвать CreateFileW (с указанным флагом), а затем передать результат os.NewFile , поэтому, возможно, нет необходимости добавить поддержку для него.

@havoc-io нет os.File.Rename(). Это было бы прекрасным решением, но это относительно тяжелый подъем.

Re CreateFileW + os.NewFile(), см. https://github.com/golang/go/issues/32088#issuecomment -493876119.

@havoc-io

Я хочу убедиться (даже если мое приложение выйдет из строя), что этот файл больше не будет существовать после того, как я закончу
@guybrand Оператор defer не гарантирует работу при каждом типе сбоя.
Это было именно то, что я имел в виду, когда сказал «даже если мое приложение выйдет из строя».

единственные аргументы, которые я вижу в его существовании, - это полнота и паритет контроля между POSIX и Windows.
Я не согласен, что это принесет «полноту и паритет контроля между POSIX и Windows» - IMO это только смутит разработчика, думая, что поведение такое же, когда это не так, я привел несколько примеров выше.

легко вручную вызвать CreateFileW
Это действительно настройка, простая или нет, и, другими словами, говоря: «мы не хотим поддерживать это в процессе», что, кстати, является хорошим решением, ИМО, но заказчиком является

К вашему сведению, вот почему мы отказались от использования FILE_SHARE_DELETE.

КЛ: https://codereview.appspot.com/8203043/

Зафиксировать в erlang/otp: https://github.com/erlang/otp/commit/0e02f488971b32ff9ab88a3f0cb144fe5db161b2

Python: https://bugs.python.org/issue15244.

@havoc-io нет os.File.Rename(). Это было бы прекрасным решением, но это относительно тяжелый подъем.
Re CreateFileW + os.NewFile(), см. #32088 (комментарий).

@networkimprov Разве вы не можете просто использовать os.Rename(file.Name(), <target>) ? В этом прелесть того, что Go не имеет FILE_SHARE_DELETE по умолчанию — вы знаете, что file.Name() прежнему будет указывать на что-то действительное, так как вы держите файл HANDLE (что-то, что вы можете' не гарантируется на POSIX, даже с renameat ). Что касается CreateFileW + os.NewFile , большая часть стека, описанного в вашем комментарии, станет неактуальной, а makeInheritSa почти наверняка вам не нужен (он предназначен только для имитации POSIX плохое поведение наследования файловых дескрипторов по умолчанию - это не по умолчанию). Единственное, что вы упустите, это внутреннюю функцию пути fixLongPath , которая, вероятно, не понадобится для описанных вами вариантов использования.

@guybrand Просто чтобы уточнить, что я имею в виду...

полнота: полнота поверхности API (т. е. возможность использовать FILE_SHARE_DELETE если вы хотите использовать его по какой-то причине)
контроль четности: возможность указывать флаги CreateFileW помощью параметра os.OpenFile flag . Например, я могу маршрутизировать syscall.O_NOFOLLOW через os.OpenFile в POSIX, но не могу маршрутизировать syscall.FILE_FLAG_OPEN_REPARSE_POINT через os.OpenFile в Windows. Хотя опять же, я думаю, что полный паритет контроля здесь — несбыточная мечта, поскольку пакет os смоделирован на основе POSIX API. Я вполне счастлив получить syscall.CreateFileW когда это необходимо.

@mattn спасибо за ссылки. Go CL должен был исправить syscall.Open() _default_. Обсуждение не дает никаких общих причин для запрета _flag_ для Open(). Он отмечает, что сторонний пакет может возвращать os.File без флага, но это проблема только в том случае, если вызывающая сторона пытается переименовать/удалить этот файл — редкий угловой случай.

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

В Эрланге file_share_delete используется по умолчанию уже более ШЕСТИ лет .

Размышляя об опыте Erlang и потоке Python, указанном выше, становится очевидным, что:

  1. File_share_delete по умолчанию хорошо работает в кросс-платформенной среде; единственное предостережение заключается в том, что пользовательский код должен переименовывать файл в уникальное имя непосредственно перед его удалением (тривиальное изменение), если исходное имя файла можно использовать повторно.
  2. Большинство программ Windows не могут использовать file_share_delete просто потому, что среда выполнения MSVC разрешает это только в сочетании с O_TEMPORARY.

Поэтому я бы пришел к выводу, что syscall.Open() должен использовать file_share_delete по умолчанию , а syscall должен предоставить глобальный флаг для его отключения при необходимости (например, при отладке).

Если проблемы с этим подходом возникают во время цикла 1.14, вместо него можно применить метод явного флага.

Поэтому я бы пришел к выводу, что syscall.Open() должен использовать file_share_delete по умолчанию, а syscall должен предоставить глобальный флаг, чтобы отключить его там, где это необходимо (например, при отладке).

@networkimprov Помимо взлома огромного количества существующих программ тонкими и не очень тонкими способами, дело в том, что введение этого флага сейчас было бы проблемой безопасности для программ, полагающихся на отсутствие FILE_SHARE_DELETE . В частности, это откроет ряд уязвимостей времени от проверки до времени использования для программ, полагающихся на неизменность файлов и каталогов, которые они держат открытыми.

Редактировать: Для дальнейшего чтения о том, почему изменение значения по умолчанию является плохой идеей и почти наверняка сломает существующие программы, см . Закон Хайрама .

Наиболее частым случаем Go для Windows является разработка для развертывания в Linux или других Unix.

Если ваш код зависит от какого-либо поведения GOOS=windows, он неисправен и, возможно, уязвим в Linux. Итак, у нас уже есть возможная проблема с безопасностью.

os.Rename() и .Remove() не документируют различия в Windows, поэтому никто не догадается об их существовании. Документирование их сейчас не исправит существующий код. Исправление несовместимости и публикация сообщения в блоге помогут гораздо больше.

Обратите внимание, что Erlang существовал некоторое время, прежде чем он принял file_share_delete по умолчанию.

Наиболее частым случаем Go для Windows является разработка для развертывания в Linux или других Unix.

Если ваш код зависит от какого-либо поведения GOOS=windows, он неисправен и, возможно, уязвим в Linux. Итак, у нас уже есть возможная проблема с безопасностью.

@networkimprov

По этой логике инфраструктура открытия файлов в Windows также должна быть изменена, чтобы дескрипторы файлов наследовались по умолчанию при создании процесса, чтобы соответствовать поведению POSIX по умолчанию. Существующие программы можно обвинить в том, что они полагаются на предыдущее поведение по умолчанию среды выполнения/стандартной библиотеки, а шаблон CVE можно включить в примечания к выпуску, чтобы пользователи могли загружать свои программы.

Но, конечно, это не то, что сделано, и вместо этого среда выполнения Go и стандартная библиотека из кожи вон лезут, чтобы обойти стандартное и плохо разработанное поведение POSIX, пытаясь отразить то, что делает Windows.

Такая же ситуация с общим доступом к файлам. Возможно, Windows правильно разработала дизайн, и POSIX.1-2008 пришлось добавить ряд функций, чтобы обойти его ограничения дизайна.

Кроме того, как несколько раз упоминал @mattn выше, включение FILE_SHARE_DELETE при открытии файлов НЕ приводит к тому, что Windows ведет себя как POSIX — все равно будут значительные поведенческие различия, которые станут гораздо более очевидными с этим флагом, чем с отличающимся os.Remove Поведение os.Rename .

Я бы сказал, что если ваш код повсеместно зависит от поведения любой платформы, он сломан и, возможно, уязвим на других платформах (включая зависимость от поведения POSIX в Windows). Если вы не тратите время на то, чтобы понять и учесть варианты платформы в своем коде, значит, вы не занимаетесь кросс-платформенной разработкой.

В любом случае @ianlancetaylor уже наложил вето на идею использования глобального флага для управления включением этого, поэтому я серьезно сомневаюсь, что вы получите его, чтобы отключить его после изменения значения по умолчанию.

На мой взгляд, единственный ненарушающий (и некомпрометирующий) вариант здесь — это добавить поддержку syscall.FILE_SHARE_DELETE в аргумент syscall.Open flag или просто заставить пользователи, которым нужна функциональность FILE_SHARE_DELETE используют CreateFileW + os.NewFile . Да, это требует некоторой работы от разработчиков, которые хотят такого поведения, но Win32 и POSIX — принципиально разные звери, и удивительно, что среда выполнения и стандартная библиотека так же хорошо справляются со своей работой, как и сглаживая их различия. Между этими платформами есть еще много существенных различий, которые проявляются в стандартной библиотеке, например, совершенно разные модели разрешений, тот факт, что os.File.Chmod не работает на Windows, os.Chown не работает на Windows, тот факт, что внутренний опросчик поддерживает разные типы файлов на разных платформах и т. д. Среда выполнения Go и стандартная библиотека делают все возможное (и делают это хорошо), но они не являются панацеей от проблем кросс-платформенной разработки. В какой-то момент разработчикам приходится справляться с этими различиями самостоятельно. Добавление критического изменения для изменения (заметьте, я не сказал правильно ) неясного поведенческого пограничного случая не имеет для меня никакого смысла.

Я хотел поделиться еще одним вариантом использования для этого.

В настоящее время, когда логика ротации журнала Docker работает так, что механизм Docker переименует активный журнал в имя .1, создаст новый со старым именем.
Любой клиент, который активно следит за журналом с помощью команды docker logs --follow <container>
заметит, что из событий файловой системы:
https://github.com/moby/moby/blob/916eabd459fe707b5c4a86377d12e2ad1871b353/daemon/logger/loggerutils/logfile.go#L552 -L593

Существует также эта логика, которая заставляет Windows немедленно замечать эти события:
https://github.com/moby/moby/blob/916eabd459fe707b5c4a86377d12e2ad1871b353/daemon/logger/loggerutils/logfile.go#L670 -L676

Все это прекрасно работает в Linux, но в Windows при вращении журнала возникает ошибка The process cannot access the file because it is being used by another process. если есть какие-либо клиенты, следующие за журналом (проблема: https://github.com/moby/moby/issues/39274)

Поскольку возможно иметь n числа тех клиентов, которые следят за журналом, было бы очень сложно сначала собрать все эти дескрипторы открытых файлов и закрыть их перед ротацией файла, поэтому я действительно хотел бы, чтобы была поддержка syscall.FILE_SHARE_DELETE флаг.

Привет! Краткая справка: я работаю над инфраструктурой Chrome CI. Мы переносим наш низкоуровневый рабочий код на Go. Мы запускаем сотни тысяч вызовов тестов на Windows в день.

Я прочитал все комментарии и надеюсь, что нижеследующее является четким и правильным изложением мнения каждого человека.

Наш вариант использования — создание исполняемых файлов Go в Windows для запуска в Windows, в отличие от удивительной характеристики @networkimprov . Я проигнорирую этот вариант использования «предполагать поведение POSIX в Windows» в этом комментарии, поскольку это просто неверное предположение, и я перетащил @mattn для выделения различий, что, ИМХО, является спорным вопросом, подробнее об этом ниже.

В рамках нашей миграции нам нужен FILE_SHARE_DELETE, отчасти из-за ротации файлов журнала, аналогично #39274. Бывает, что это также проблема для issue #25965.

Я согласен с @ianlancetaylor, изменение всего поведения цепочки инструментов, как это предлагается в https://codereview.appspot.com/8203043/ , не является хорошей идеей. Одна непосредственная проблема, которая приходит на ум, это предложение при использовании дескриптора файла в качестве файла блокировки. Мы используем это поведение. И что сказал @havoc-io. Так что давайте проигнорируем и это.

Подводя итог, я собираюсь проигнорировать эти предложения в остальной части этого комментария:

  • Попытка вести себя точно так же, как POSIX, не сработает.
  • Глобальное изменение поведения путем отказа (или без отказа!), что ломает пользователей

Это оставляет нам флаг для каждого вызова с именем, которое явно специфично для Windows. Это именно то, что предлагает @guybrand .

При всем уважении, я не понимаю возражений @mattn . То, что предлагает но в Windows все по-другому . Оно уже расходится во многом. Возражение против флага, специфичного для Windows, на позиции «Разработчики могут неправильно понять его поведение» - это не факт, это мнение. Вы предполагаете, что разработчики не знают, что они делают, или не будут читать документацию в MSDN. Это справедливое возражение для общего случая 😎, но не сильное на основании блокировки законного варианта использования в качестве необязательного флага.

По иронии судьбы, это означает, что пока эта проблема не будет решена, чтобы исправить #25965, мне придется изменить «go run» на прямой вызов CreateFileW 🙃, потому что нам также нужен FILE_FLAG_DELETE_ON_CLOSE .

Спасибо!

Я просто хочу знать, как может существовать случай, чтобы узнать, должны ли мы реализовать новую функцию (golang.org/x/sys?) для открытия/создания файла с новыми атрибутами или изменить исходное поведение. В настоящее время, насколько я знаю из приведенных выше комментариев:

  1. создание удаляемого (переименовываемого) файла для регистрации.
  2. создание файла для временного файла, который будет удален при закрытии.

Для 1 это может быть предоставлено из новой функции для создания/открытия файла с атрибутами. И мы можем установить файл, используя logger.SetOutput(file).

Для 2 также можно создавать/открывать с новой функцией.

Кстати, я предполагаю, что № 25965 не имеет отношения к этой проблеме. Возможно, это ограничение винды.

(В любом случае мы не можем удалить каталог, в котором существует файл, открытый с этим атрибутом)

@mattn Можете ли вы объяснить компромисс между:

  • добавление совершенно новой функции в golang.org/x/sys
  • поддержка нового флага (уже определенного!) для OpenFile(). [1]

Я не понимаю, почему мы должны отдавать предпочтение первому, а не второму.

[1] Я только что понял, что предполагал здесь изменение поведения os.OpenFile(), но проблема вызывает Open().

Во-первых, пакет системных вызовов уже заблокирован. Поэтому я ищу способ исправить это, не меняя пакет системного вызова.

Кстати, я предполагаю, что № 25965 не имеет отношения к этой проблеме. Возможно, это ограничение винды.

(В любом случае мы не можем удалить каталог, в котором существует файл, открытый с этим атрибутом)

@mattn это не совсем так. Флаг FILE_SHARE_DELETE позволит вам переместить эти файлы в другую папку, чтобы вы могли удалить исходный. Вот простой пример PowerShell:

# Create folder and open new file with FILE_SHARE_DELETE flag
New-Item -Type Directory -Path C:\folder1
$file = [System.IO.File]::Open("C:\folder1\test.txt", "Create", "ReadWrite", "Delete")

# Create temp folder and move all open files to there
New-Item -Type Directory -Path C:\folder1-removing
Get-ChildItem -Path C:\folder1 -Recurse | Move-Item -Destination C:\folder1-removing\

# Remove original folder
Remove-Item -Path C:\folder1

# Trigger removal for files on temp folder (NOTE! Those will exist on disk until they are closed).
Get-ChildItem -Path C:\folder1-removing -File -Recurse | Remove-Item -Force

# Close file and remove temp folder
$file.Close()
Remove-Item -Path C:\folder1-removing

Во-первых, пакет системных вызовов уже заблокирован. Поэтому я ищу способ исправить это, не меняя пакет системного вызова.

@mattn Если упоминал / в демонстрации выше, syscall.Open можно просто настроить, чтобы понять syscall.FILE_SHARE_DELETE , решив проблему без изменения API.

Если это заморозка реализации, то изменение можно внести в функцию golang.org/x/sys/windows Open а затем добавить в пакет internal/syscall . Код file_windows.go уже использует функции из internal/syscall . Тогда единственное несоответствие будет заключаться в том, что syscall.Open не поймет этот флаг, но большинство людей, желающих получить доступ к низкоуровневым функциям Windows API, все равно используют golang.org/x/sys/windows и, вероятно, используют CreateFile вместо Open .

Предложение, которое инициировало блокировку пакета системных вызовов, гласит:
_Основной репозиторий не будет зависеть от пакетов go.sys_

Таким образом, изменение x/sys не поможет вызывающим os.OpenFile(). Но внутренний/системный вызов может добавить Open(), и os.OpenFile() вызовет его.

@maruel , проект Docker и, предположительно, многие другие предполагали поведение Unix для os.Rename() и .Remove() открытых файлов, потому что пакет «os» не упоминает для них поведение, специфичное для Windows, но имеет __пятнадцать упоминаний__ о другое поведение Windows.

Основной репозиторий не будет зависеть от пакетов go.sys.
Но внутренний/системный вызов может добавить Open(), и os.OpenFile() вызовет его.

@networkimprov Хорошо, у меня сложилось ошибочное впечатление, что internal/syscall было вендорным подмножеством golang.org/x/sys , но в любом случае я думаю, что это правильная стратегия (добавление internal/syscall/windows.Open ). Конечно, это будет исходить из предположения, что поведение syscall.Open неизменно из-за заморозки. В идеале его можно было бы изменить напрямую, потенциально с соответствующими изменениями в golang.org/x/sys/windows.Open .

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

Но если люди вызывают syscall.Open напрямую, то они должны использовать пакет x/sys/windows, а не пакет syscall.

Но если люди вызывают syscall.Open напрямую, то они должны использовать пакет x/sys/windows, а не пакет syscall.

Я не думаю, что кто-то собирается вызывать syscall.Open напрямую — это только предмет обсуждения, потому что он лежит в основе os.OpenFile (так что добавление поддержки FILE_SHARE_DELETE в syscall.Open добавляет поддержку использования флага с os.OpenFile ).

Спасибо. Можно изменить пакет системного вызова для поддержки изменений в пакете os.

Спасибо @ianlancetaylor и @

  • Измените syscall.Open() с явным намерением разрешить os.OpenFile() принимать FILE_SHARE_DELETE (и, в конечном итоге, FILE_FLAG_DELETE_ON_CLOSE в качестве потенциального продолжения)
  • Обновите документацию os.OpenFile(), чтобы описать новый флаг только для Windows.
  • Обновите документацию os.OpenFile(), os.Rename() и os.Remove(), чтобы прояснить разницу в поведении между POSIX и Windows.

Третий момент — решить проблему @networkimprov . Я понимаю эту проблему, и хотя язык не должен нести ответственность за описание того, как работает ОС, я начинаю соглашаться с @mattn в том, что эти случаи достаточно тонкие, чтобы

Я готов внести свой вклад в это, если будет одобрение.

Это оставляет нам флаг для каждого вызова с именем, которое явно специфично для Windows.

Вы предлагаете новый флаг os.Open? Тогда этот флаг должен поддерживаться всеми операционными системами - в этом весь смысл пакета ОС - быть независимым от ОС. Итак, какова будет семантика этого флага? И вам потребуются новые тесты, которые проверят, что флаг работает так, как задокументировано.

Я не понимаю, почему вы не можете написать любой код, который вам нужен, используя пакет syscall. Я не думаю, что ваш код будет хорошо сосуществовать с другими программами Windows. И, может быть, это нормально в особых обстоятельствах. Но я не хочу, чтобы люди легкомысленно использовали эту функциональность, поэтому она не должна быть частью стандартной библиотеки.

Алекс

@alexbrainman
пожалуйста, посмотрите это

Я посмотрел. Я до сих пор не понимаю, как все предложенные вами изменения будут работать на других ОС? Какие тесты вам придется провести для реализации предложенного изменения?

Алекс

цитируя это

тогда как DELETE_WHEN_FREED может быть даже 0 для env. кроме окон

Итак - нечего тестировать на других ОС, призыв к:
fd, ошибка: = os.OpenFile (путь, os.O_RDWR|os.O_CREATE|os.DELETE_WHEN_FREED, 0600)
в окнах будет переводить const в 4
fd, ошибка := os.OpenFile(путь, os.O_RDWR|os.O_CREATE| 4 , 0600)
В то время как другие ( 0 )
fd, ошибка: = os.OpenFile (путь, os.O_RDWR|os.O_CREATE| 0 , 0600)

нечего тестировать на других ОС

Я не понимаю, как мы могли бы добавить новую константу или переменную пакета ОС, которую можно использовать только в Windows.

c:\>go doc os
package os // import "os"

Package os provides a platform-independent interface to operating system
functionality. 
...
The os interface is intended to be uniform across all operating systems.
Features not generally available appear in the system-specific package
syscall.

Алекс

Alex ответ на ваше возражение впервые был изложен здесь https://github.com/golang/go/issues/32088#issuecomment -494157514

Тест тривиален

_, err := os.OpenFile(path, os.O_CREATE | syscall.FILE_SHARE_DELETE, 0);
err = os.Rename(path, path+"2")

Мы находимся в точке, когда кто-то может отправить патч; Я подозреваю, что @ianlancetaylor примет это.

Я исследовал настройку FILE_SHARE_DELETE как часть #32188, но обнаружил, что ее установка эмпирически не снижает количество ошибок от вызовов os.Rename и io.ReadFile ; в любом случае цикл повторных попыток все равно был необходим.

Мне было бы очень интересно увидеть тест, демонстрирующий существенную наблюдаемую разницу от установки этого флага, потому что я сам не совсем понимаю его последствия.

Я экспериментировал с FILE_SHARE_DELETE несколько лет назад, и он вел себя
иначе.
Я, вероятно, могу перезапустить свои окна env. и посмотри, может поможет
но это будет ~ Go 1.3 или около того.

В понедельник, 10 июня 2019 г., в 17:44, Брайан С. Миллс, [email protected]
написал:

Я исследовал установку FILE_SHARE_DELETE как часть #32188.
https://github.com/golang/go/issues/32188 , но обнаружил, что его настройка
эмпирически не уменьшило количество ошибок от os.Rename и
вызовы io.ReadFile; в любом случае цикл повторных попыток все равно был необходим.

Мне было бы очень интересно увидеть тест, демонстрирующий значительный
заметное отличие от установки этого флага, потому что я действительно не
сам понимаю его последствия.


Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/golang/go/issues/32088?email_source=notifications&email_token=ABNEY4VVIVJ2IEWQPWCWBZTPZZSETA5CNFSM4HNPNYIKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODXKCG4Y#issuecomment-5000
или заглушить тему
https://github.com/notifications/unsubscribe-auth/ABNEY4STTW7U526HPO6TUHDPZZSETANCNFSM4HNPNYIA
.

@bcmills , чтение #32188 Насколько я понимаю, вы видели ошибки от os.Rename() в файле, который _не был открыт_ во время переименования? Я предполагаю, что можно обойти журнал NTFS, который, как я полагаю, записывается для всех операций изменения каталога, и тем самым вызвать ошибку; но я понятия не имею, что это за ошибка.

Запрос на включение FILE_SHARE_DELETE в syscall.Open() заключается в том, чтобы позволить os.Rename() работать с открытыми файлами. Я проверил этот флаг с 1.12 на Win7, он работает; и без него не получится.

Я специально не тестировал os.Rename() в окнах «под нагрузкой», но не видел никаких неожиданных ошибок от os.Rename() в открытых файлах с момента включения fsd. Я сообщу об этом, если сделаю.

Насколько я понимаю, вы видели ошибки от os.Rename() в файле, который не был открыт во время переименования?

да.

Я понятия не имею, что это будет за ошибка.

Эмпирически, ошибки, которые я вижу (от MoveFileEx , ReplaceFile и/или CreateFile ), равны ERROR_ACCESS_DENIED , ERROR_SHARING_VIOLATION , и ERROR_FILE_NOT_FOUND .

Запрос на включение FILE_SHARE_DELETE в syscall.Open() заключается в том, чтобы позволить os.Rename() работать с открытыми файлами. Я проверил этот флаг с 1.12 на Win7, он работает; и без него не получится.

Правильно; связанные проблемы, которые я пытаюсь решить, заключаются в том, чтобы разрешить одновременные вызовы os.Rename и разрешить вызовы io.ReadFile одновременно с os.Rename . Получение одного os.Rename для работы с открытыми существующими дескрипторами может быть шагом в правильном направлении, но я не думаю, что этого достаточно для тех случаев использования, для которых мы используем POSIX rename .

Мне было бы очень интересно увидеть тест, демонстрирующий существенную наблюдаемую разницу от установки этого флага, потому что я сам не совсем понимаю его последствия.

Пример @bcmills PowerShell, поскольку я пишу намного быстрее, чем Go

New-Item -Type Directory -Path C:\folder1
for($i=0; $i -le 1000; $i++)
{
    $temp = [System.IO.File]::Open("C:\folder1\test.txt", "Create", "ReadWrite", "Delete")
    Set-Variable -Name "file$i" -Value $temp -Force
    $TempName = "C:\folder1\test.txt." + (New-Guid).Guid
    Rename-Item -Path C:\folder1\test.txt -NewName $TempName
    Remove-Item -Path $TempName -Force
}

После этого законченного будут тысячи test.txt.??? файлов в C:\folder1 , но при закрытии чтобы PowerShell этих файлы будут удалены.

Итак, какой флаг FILE_SHARE_DELETE самом деле позволяет вам переименовывать/перемещать/удалять открытые файлы, но вам все равно нужно убедиться, что имена файлов назначения уникальны, потому что они будут существовать на диске, пока открыты дескрипторы.

«использовать переименование POSIX» невозможно в Windows, и, возможно, это причина ошибок, которые вы получаете.

POSIX:

создать файл "имя файла"
чтение/запись из "имя файла"
удалить "имя файла"

создать файл "имя файла"
...
удалить "имя файла"

будет работать, так как каждый файл создания будет иметь diff fd.

Окна:
создать файл "имя файла"
чтение/запись из "имя файла"
удалить "имя файла"

создать файл "имя файла" - бум, нарушение обмена

Должно быть очень легко реконструировать...

Что тогда может сделать FILE_SHARE_DELETE?
разрешить отложенное удаление, пока файл открыт.

Вот и все.

В понедельник, 10 июня 2019 г., в 19:32, Олли Янатуйнен, [email protected]
написал:

Мне было бы очень интересно увидеть тест, демонстрирующий значительный
заметное отличие от установки этого флага, потому что я действительно не
сам понимаю его последствия.

@bcmills https://github.com/bcmills Пример PowerShell, когда я пишу это
намного быстрее, чем Go

New-Item -Type Directory -Path C:folder1for($i=0; $i -le 1000; $i++)
{
$temp = [System.IO.File]::Open("C:folder1test.txt", "Создать", "Чтение и запись", "Удалить")
Set-Variable -Name "file$i" -Value $temp -Force
$TempName = "C:folder1test.txt." + (Новый-Guid).Guid
Переименовать-Элемент -Путь C:folder1test.txt -НовоеИмя $TempName
Remove-Item -Path $TempName -Force
}

После этого готовых будут тысячи test.txt.??? файлы под
C:folder1, но когда вы закроете PowerShell, эти файлы будут удалены.

Итак, какой флаг FILE_SHARE_DELETE на самом деле позволяет вам
переименовывать/перемещать/удалять открытые файлы, но вам все равно нужно убедиться, что место назначения
имена файлов уникальны, потому что они будут существовать на диске до тех пор, пока дескрипторы
открыты.


Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/golang/go/issues/32088?email_source=notifications&email_token=ABNEY4WSTWIUVNVHLYMBFY3PZZ62DA5CNFSM4HNPNYIKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODXKMMDY#issuecomment-500
или заглушить тему
https://github.com/notifications/unsubscribe-auth/ABNEY4VS5XF3EXB6QPBWVQ3PZZ62DANCNFSM4HNPNYIA
.

разрешить вызовы io.ReadFile одновременно с os.Rename для достижения успеха

@bcmills Я думаю, что всегда следует работать с установленным флагом fsd, предполагая, что он не работает одновременно с другими потоками, пытающимися выполнить ту же последовательность для несвязанных файлов. И обычно это должно терпеть неудачу без флага.

Этот аспект тестового сценария правильно обнаружил ошибку в Windows syscall.Open(). Но никто из комментирующих в этой ветке не хочет, чтобы это исправляли :-( так что да, исправьте скрипт.

TLDR:
небольшое безопасное изменение в syscal_windows.go и работает как положено.

объяснил:
Итак, вот что я сделал, работает как положено и без риска (IMO)

  1. настроить syscal_windows.go
func Open(path string, mode int, perm uint32) (fd Handle, err error) {

sharemode := uint32(FILE_SHARE_READ | FILE_SHARE_WRITE | (mode & FILE_SHARE_DELETE))

дополнение: "| (режим и FILE_SHARE_DELETE)"
если разработчик не отправит mode & 4 set (зачем ему - раньше это никогда не работало...) - ничего не изменилось, поэтому код типа

os.OpenFile(filename,os.O_RDWR|os.O_CREATE, 0600)

будет вести себя ТОЧНО так же, как и до изменения, только разработчики, которые будут

os.OpenFile(filename,os.O_RDWR|os.O_CREATE  | 4 , 0600)

будет "почувствовать" изменение

  1. запустил код
package main

import (
    "fmt"
    "os"
)

func main() {
    filename := "./myfile"
    if f ,err := os.OpenFile(filename,os.O_RDWR|os.O_CREATE|4, 0600);err!=nil{
        fmt.Printf("Open file %s error : %s" , filename, err.Error())
    } else if _,err:= f.WriteString("bla bla") ;err!=nil{
        fmt.Printf("writing to %s returned error : %s" , filename, err.Error())
    } else if err := os.Remove(filename);err!=nil{
        fmt.Printf("removing %s returned error : %s" , filename, err.Error())
    }
}

и это сработало

  1. запустил код без |4 и не удалось
  2. запустил код с небольшим дополнением:
package main

import (
    "fmt"
    "os"
)

func main() {
    filename := "./myfile"
    if f ,err := os.OpenFile(filename,os.O_RDWR|os.O_CREATE|4, 0600);err!=nil{
        fmt.Printf("Open file %s error : %s" , filename, err.Error())
    } else if _,err:= f.WriteString("bla bla") ;err!=nil{
        fmt.Printf("writing to %s returned error : %s" , filename, err.Error())
    } else if err := os.Remove(filename);err!=nil{
        fmt.Printf("removing %s returned error : %s" , filename, err.Error())
    } else if secondF ,err := os.OpenFile(filename,os.O_RDWR|os.O_CREATE|4, 0600);err!=nil{
        fmt.Printf("reOpen file %s error : %s" , filename, err.Error())
    } else {
        secondF.Close()
    }
}

и конечно на втором F не получилось
Но это ожидаемое поведение в Windows — не нужно «тестировать нагрузку» — для deferred .Remove нет параметра параллелизма, поэтому я думаю, что мы можем безопасно добавить эту половину строки в syscal_windows, не ставя под угрозу какой-либо существующий код и разрешая только разработчикам, которые действительно хочу использовать этот режим, чтобы иметь возможность откладывать удаление.

все, что нам нужно сделать (выделено жирным шрифтом):
syscal_windows.go:
режим общего доступа: = uint32 (FILE_SHARE_READ | FILE_SHARE_WRITE | (режим и FILE_SHARE_DELETE) )

Я исследовал настройку FILE_SHARE_DELETE как часть #32188, но обнаружил, что ее установка эмпирически не снижает количество ошибок от вызовов os.Rename и io.ReadFile ; в любом случае цикл повторных попыток все равно был необходим.

Мне было бы очень интересно увидеть тест, демонстрирующий существенную наблюдаемую разницу от установки этого флага, потому что я сам не совсем понимаю его последствия.

@bcmills теперь вы можете найти модульные/регрессионные тесты для этого на https://github.com/olljanat/go/commit/3828f1a5d0ebb69b4c459d5243799ded36ac1ee8. В настоящее время он не выдает ошибку The process cannot access the file because it is being used by another process. в Windows и начинает работать после FILE_SHARE_DELETE Флаг

Примечание! что в Windows вам все еще нужен этот шаг, который перемещает файл журнала example.log.1 в случайное/уникальное имя, потому что иначе переименование example.log в example.log.1 приведет к ошибке Access is denied. даже когда флаг FILE_SHARE_DELETE включен. Это специфичная для платформы вещь, о которой должен позаботиться разработчик.

/cc @jhowardmsft @jterry75 @jstarks @ddebroy FYI; вас может заинтересовать и этот.

@kevpar - к вашему сведению, так как вы тоже работаете над этим

Если какие-либо люди, присоединяющиеся к обсуждению, могут озвучить причины (например, существующий код сломан), чтобы сделать это значение по умолчанию, а не доступным через флаг только для Windows в os.OpenFile(), пожалуйста!

@networkimprov IMO, это должно обрабатываться с флагом только для Windows, потому что разработчику в любом случае необходимо обрабатывать также некоторые другие специфические для Windows вещи, как в моем примере с

Однако, как я вижу, на обсуждение была приглашена пара сотрудников Microsoft, поэтому интересно посмотреть, думают ли они по-другому.

Кстати, кто-нибудь начал разрабатывать новое решение для этого? Есть добровольцы?

@olljanat
Если это исправление как флаг, то исправление состоит из одной строки, пожалуйста, прочитайте:
https://github.com/golang/go/issues/32088#issuecomment-500562223

Я попытаюсь представить здесь точку зрения команды Windows... мы бы предпочли, чтобы Go просто всегда устанавливал FILE_SHARE_DELETE без каких-либо опций. В общем, чтобы упростить перенос в/из Windows, мы бы предпочли согласованное поведение с Linux, и я не понимаю, почему пользователи Windows или программное обеспечение предпочитают поведение по умолчанию !FILE_SHARE_DELETE, достаточное для того, чтобы это было исключением из правила.

Интересное замечание, противоречащее комментарию @mattn : в самой последней версии Windows мы обновили DeleteFile (в NTFS), чтобы выполнить удаление «POSIX», при котором файл немедленно удаляется из пространства имен, а не ждет, пока все откроются. указывает на файл, который нужно закрыть. Он по-прежнему учитывает FILE_SHARE_DELETE, но в остальном ведет себя больше как отвязка POSIX. Эта функциональность была добавлена ​​для WSL и считалась полезной по умолчанию для программного обеспечения Windows.

Другими словами, если вы запустите тестовую программу mattn и последовательность del, dir и т. д. в последней версии Windows, вы увидите, что файл исчезнет из пространства имен сразу после удаления файла, а не после выхода тестовой программы. Так же, как линукс.

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

@jstarks , спасибо, что оправдали меня. Я получил много тепла в этой теме за эту позицию.

Я бы предложил глобальную опцию для отключения fsd на случай, если какие-либо развертывания Windows полагаются на текущее недокументированное поведение.

@ianlancetaylor , ты согласен?

Поскольку поведение CreateFile по умолчанию заключается в том, что нельзя удалить открывающий файл, я думаю, что мы должны сохранить поведение по умолчанию. Кому-то может понадобиться оригинальное поведение. Например, могут быть программисты, которые проверяют, работает ли их приложение, используя способ, которым невозможно удалить файл.

Поведение Python такое же, как текущее поведение Go. И я никогда не слышал, чтобы Python планировал изменить поведение, которое не может удалять открытые файлы.

У меня не было времени выработать серьезное мнение по этому вопросу, но я не думаю, что глобальный вариант — это хороший выбор. Мы должны либо сохранить текущее поведение (и, возможно, добавить флаг для выбора другого поведения), либо изменить поведение (и, возможно, добавить флаг для выбора исходного, текущего поведения).

Что касается того, следует ли изменить поведение по умолчанию или нет, я думаю, мы должны спросить, более ли вероятно, что изменение поведения исправит программу Go, написанную для работы в системах Unix и Windows, или более вероятно, что изменение поведения сломает программу Go, написанную для работы в системах Windows. Я не знаю ответа на этот вопрос.

Как предполагает @mattn , также стоит спросить, что делают другие языки.

Если он станет значением по умолчанию и от него зависят любые развертывания Windows, я полагаю, им понадобится как флаг os.OpenFile(), так и глобальная опция для переключения обратно в переходных целях.

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

Один из способов узнать о влиянии — изменить его по наводке (и объявить в блоге?) и посмотреть, что сообщается.

Python и Erlang оба упоминаются в тексте выпуска:
Erlang сделал его по умолчанию более шести лет назад: erlang /
Python хотел, но не смог из-за ограничений среды выполнения MSVC: https://bugs.python.org/issue15244.

Я попытаюсь представить здесь точку зрения команды Windows... мы бы предпочли, чтобы Go просто всегда устанавливал FILE_SHARE_DELETE без каких-либо опций. В общем, чтобы упростить перенос в/из Windows, мы бы предпочли согласованное поведение с Linux, и я не понимаю, почему пользователи Windows или программное обеспечение предпочитают поведение по умолчанию !FILE_SHARE_DELETE, достаточное для того, чтобы это было исключением из правила.

в самой последней версии Windows мы обновили DeleteFile (в NTFS), чтобы выполнить удаление «POSIX», при котором файл удаляется из пространства имен немедленно, а не ждет закрытия всех открытых дескрипторов файла. Он по-прежнему учитывает FILE_SHARE_DELETE, но в остальном ведет себя больше как отвязка POSIX. Эта функциональность была добавлена ​​для WSL и считалась полезной по умолчанию для программного обеспечения Windows.

@jstarks , это очень интересная деталь. Это то, чего мы можем ожидать от Microsoft и в более старых версиях ОС? Я особенно думаю о Windows Server 2019, которая является последней, только что выпущенной версией LTSC, которая будет поддерживаться еще долгое время.

Как будто Microsoft делает это, я бы предпочел включить FILE_SHARE_DELETE по умолчанию и в Go.

Java, C# и все другие основные языки, о которых я знаю, принимают поведение ОС по умолчанию. На мой взгляд, поведение по умолчанию следует оставить разработчику ОС. Если Windows действительно хочет использовать подход POSIX в отношении открытия файлов, они должны пойти на этот риск и сделать так, как они сделали для DeleteFile.

Я думаю, что библиотека Go должна позволять создавать/открывать общие файлы для удаления. Я могу привести один практический пример работы с файловым режимом. При переносе Hadoop на Windows нам приходится прилагать дополнительные усилия для создания специального метода в JNI для получения общего дескриптора файла удаления или потока.

https://github.com/apache/hadoop/search?q=getShareDeleteFileInputStream&unscoped_q=getShareDeleteFileInputStream

@jstarks

Говоря

в самой последней версии Windows мы обновили DeleteFile (в NTFS), чтобы выполнить удаление «POSIX», при котором файл немедленно удаляется из пространства имен.

Вы имеете в виду, что открытый файл остается только как fd и код ниже:

package main

import (
    "fmt"
    "os"
)

func main() {
    filename := "./myfile"
    if f ,err := os.OpenFile(filename,os.O_RDWR|os.O_CREATE|4, 0600);err!=nil{
        fmt.Printf("Open file %s error : %s" , filename, err.Error())
    } else if _,err:= f.WriteString("bla bla") ;err!=nil{
        fmt.Printf("writing to %s returned error : %s" , filename, err.Error())
    } else if err := os.Remove(filename);err!=nil{
        fmt.Printf("removing %s returned error : %s" , filename, err.Error())
    } else if secondF ,err := os.OpenFile(filename,os.O_RDWR|os.O_CREATE|4, 0600);err!=nil{
        fmt.Printf("reOpen file %s error : %s" , filename, err.Error())
    } else {
        secondF.Close()
    }
}

не потерпит неудачу в

} else if secondF ,err := os.OpenFile(filename,os.O_RDWR|os.O_CREATE|4, 0600);err!=nil{

после применения предложенного исправления ?

Если да, то это действительно круто!

Что касается того, следует ли изменить поведение по умолчанию или нет, я думаю, мы должны спросить, более ли вероятно, что изменение поведения исправит программу Go, написанную для работы в системах Unix и Windows, или более вероятно, что изменение поведения сломает программу Go, написанную для работы в системах Windows. Я не знаю ответа на этот вопрос.

В Windows нет ни одной problem to be fixed программы Unix. Windows обрабатывает удаление открытых файлов иначе, чем Unix. Это похоже на чувствительность к регистру имени файла: Unix чувствителен к регистру, а Windows нечувствителен к регистру. Мы ничего не можем сделать с обоими различиями.

И хотя Джон уверяет, что Microsoft пытается что-то изменить, я бы подождал, пока изменения не произойдут. И многие пользователи Windows по-прежнему используют Windows 7 и все остальные версии Windows. Джон, ты бы тоже обновил Windows 7?

Я думаю, мы должны позволить Microsoft справиться с изменением. Если мы исправим пользователей Go, все равно будут разработчики, не использующие Go. Так что Microsoft придется иметь дело с этим в любом случае.

Кроме того, у FILE_SHARE_DELETE есть свои скрипты. Например, чтение https://bugs.python.org/issue15244

На самом деле, это не совсем та же семантика, что и в Unix.

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

Однако это можно обойти, переместив файл в другое место (например, в корневой каталог) перед его удалением.

Есть, наверное, и многие другие подобные, о которых мы не знаем. Мы вообще не использовали FILE_SHARE_DELETE. Вы предлагаете изменить библиотеки Go, чтобы они использовали FILE_SHARE_DELETE по умолчанию, а затем ждать, пока пользователи будут жаловаться на их сломанные программы?

Как предполагает @mattn , также стоит спросить, что делают другие языки.

Я проверил только Mingw C — он ведет себя так же, как Go. После того, как файл открыт с помощью fopen, он не может быть удален до тех пор, пока не будет вызвана fclose. Я был бы удивлен, если бы какие-либо инструменты разработки Microsoft (C, C++, C#, VB и другие) отличались.

И я не понимаю, как мы могли бы изменить поведение по умолчанию таким образом. В какой-то программе файл может быть открыт специально, поэтому он не может быть удален другой программой. На мой взгляд, это правильный подход к Windows. Мы должны поддерживать его в будущем.

Алекс

Добавлено 7 лет назад, я считаю:
https://sourceforge.net/p/mingw-w64/code/HEAD/tree/stable/v3.x/mingw-w64-headers/include/ntdef.h#l858

До сих пор никто не опубликовал ни одного случая приложений Go, которые зависят от текущего недокументированного поведения Windows. Но мы слышали по крайней мере о 4 случаях сбоев приложений Go в Windows из-за этого.

Как указано выше, есть простое исправление для повторного использования имени файла после os.Remove(), которое мы должны задокументировать:
os.Rename(path, unique_path); os.Remove(unique_path)

До сих пор никто не опубликовал ни одного случая приложений Go, которые зависят от текущего недокументированного поведения Windows.

Эти люди даже не знают, что можно сообщать об ошибках Go.

Вы не должны ожидать, что эти пользователи будут следить за изменениями в списке задач go в надежде, что что-то их затронет. У них есть жизнь, чтобы жить. Мы обязаны не ломать инструменты, которые они используют.

Алекс

Алекс, ясно, что ты неубедителен. Затем я вижу два возможных пути вперед, чтобы люди могли писать переносимый код на Go:

  1. новый флаг согласия на Open что означает FILE_SHARE_DELETE для Windows и игнорируется для других ОС. Затем мы призываем всех, кто пишет код, ожидающий семантики POSIX, везде передавать этот флаг; или,
  2. новый пакет Go posix (где-то в каком-то репо) с одной функцией, Open , которая оборачивает CreateFile с FILE_SHARE_DELETE, но в остальном ведет себя как os.Open . На платформах, отличных от Windows, он просто вызовет os.Open напрямую. Затем мы бы призвали всех, кто пишет код, ожидающий семантики POSIX, везде использовать posix.Open вместо os.Open .

Оба этих подхода требуют, чтобы разработчики Linux предпринимали дополнительные действия для сохранения поведения POSIX в Windows, но, по крайней мере, это лучше, чем написание одноразовых оболочек CreateFile (как мы только что сделали для containerd).

Алекс, ясно, что ты неубедителен.

меня не уговаривают. Но вам не нужно уговаривать меня, вам нужно уговорить Go Team. Они будут принимать решение.

  1. новый флаг согласия на Open что означает FILE_SHARE_DELETE для Windows и игнорируется для других ОС. Затем мы призываем всех, кто пишет код, ожидающий семантики POSIX, везде передавать этот флаг; или,

Мне тоже не нравится этот вариант.

Прежде всего, предполагается, что пакет os предоставляет средства, доступные для любой ОС. Все, что специфично для ОС, должно быть включено в syscall, или golang.org/x/sys/windows, или любой другой пакет, который нам нравится.

Во-вторых, меня беспокоит, что в моей программе останется файл типа FILE_SHARE_DELETE , даже если я этого не захочу. Представьте, если я импортирую внешний пакет, который решает использовать файловый режим FILE_SHARE_DELETE . Откуда мне знать, что часть моего кода использует FILE_SHARE_DELETE ? Мне пришлось бы grep исходный код пакета. Или документация пакета должна была бы предупредить своих пользователей. Я предполагаю, что это также может произойти, если какой-то автор пакета просто использует syscall.CreateFile с FILE_SHARE_DELETE напрямую. Но, я думаю, если пакет FILE_SHARE_DELETE благословлен для пакета os, это будет более распространено. Я подозреваю, что пользователи Linux, которые ничего не знают о Windows, будут использовать этот флаг по умолчанию, чтобы сделать порт своей программы Linux "легче" для Windows.

В-третьих, нам пришлось бы задокументировать флаг FILE_SHARE_DELETE . Мы не можем просто сказать POSIX. Мы должны были бы описать, что делает флаг. Простыми словами. Также помните:

На самом деле, это не совсем та же семантика, что и в Unix.

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

Однако это можно обойти, переместив файл в другое место (например, в корневой каталог) перед его удалением.

из https://github.com/golang/go/issues/32088#issuecomment-504321027 . Мы должны были бы задокументировать и это. И все остальные функции, которые FILE_SHARE_DELETE флаг

В-четвертых, нам нужно добавить новые тесты для FILE_SHARE_DELETE . Что это будут за тесты? Что произойдет, если мы забудем протестировать какую-то функциональность, а потом обнаружим, что функциональность не может быть достигнута с помощью флага FILE_SHARE_DELETE ? Мы не можем убрать флаг FILE_SHARE_DELETE , если он нам не нравится. Как только он входит, он остается. И мы должны поддерживать его в будущем.

2. новый пакет Go posix (где-то в каком-то репо) с одной функцией, Open , которая оборачивает CreateFile с FILE_SHARE_DELETE, но в остальном ведет себя как os.Open

Я не понимаю, как это возможно. Но, если это возможно, вы должны быть в состоянии создать пакет самостоятельно. Например, как github.com/jstarks/posix. Нет? Мы могли бы добавить пакет в golang.org/x/sys/windows/posix или что-то в этом роде, но я не вижу большой разницы.

Алекс

Пакет os должен предоставлять средства, доступные для любой ОС...
нам пришлось бы задокументировать флаг FILE_SHARE_DELETE. Мы не можем просто сказать POSIX...
нам нужно будет добавить новые тесты для FILE_SHARE_DELETE. Что это будут за тесты?

Все это было рассмотрено выше.

если я импортирую внешний пакет, который решает использовать файловый режим FILE_SHARE_DELETE. Откуда мне знать

Это еще один аргумент в пользу того, чтобы сделать его значением по умолчанию и установить глобальный флаг для его отключения.

Я написал на golang-nuts, чтобы найти уязвимые приложения для Windows; Я буду пинговать его каждые несколько дней, чтобы он оставался видимым. Если это не очень заметно, то использование его по умолчанию для 1.14 должно прояснить ситуацию.
https://groups.google.com/d/topic/golang-nuts/8BiP_mPoCd4/дискуссия

Это еще один аргумент в пользу того, чтобы сделать его значением по умолчанию и установить глобальный флаг для его отключения.

Кстати. Что этот флаг должен делать в Linux? Афаиу, нам на самом деле также не хватает функциональности для открытия файлов в Linux, чтобы они не могли быть удалены другими процессами? На основании этого https://gavv.github.io/articles/file-locks/#open -file-description-locks-fcntl должно быть возможно реализовать его в современных версиях Linux.

Флаг (например, var OpenWithout_FILE_SHARE_DELETE = false ) будет определен в syscall_windows.go, так как он влияет на поведение syscall.Open().

Флаг для чего-то специфичного для Linux будет иметь другое имя и быть определен в syscall_linux.go. Навскидку я не знаю, как предотвратить удаление открытого файла в Linux, кроме как через разрешения на его каталог. Предоставленная вами ссылка описывает «рекомендательную» блокировку.

@jstarks, прежде чем мы рассмотрим возможность введения флага FILE_SHARE_DELETE , нам также нужно решить, что делать с невозможностью Go удалить исполняемый файл. Я не думаю, что флаг FILE_SHARE_DELETE может нам здесь помочь. Может это? Если мы не можем реализовать удаление исполняемого файла, который все еще работает, мы не можем претендовать на совместимость с POSIX. И флаг FILE_SHARE_DELETE выглядит половинчатым решением.

Более того, иногда нам не удается удалить исполняемый файл даже завершившегося процесса. Подробности см., например, в #25965, #19491 и #32188. В основном мы вызываем WaitForSingleObject для дескриптора процесса, пока он не подаст сигнал. Но это не гарантирует, что исполняемый файл можно будет удалить. Нам пришлось добавить 5 миллисекунд ожидания перед попыткой удалить файл. И даже это не всегда работает - https://github.com/golang/go/issues/25965#issuecomment -482037476

Тоже не относящийся к этому вопросу, но, учитывая ваше внимание, возможно, вы поможете нам оживить порт windows-arm. См. подробности в #32135. По сути, @jordanrh1 портировал Go на windows-arm, но у нас больше нет https://github.com/golang/go/wiki/PortingPolicy Я не знаю, если кто-то заинтересован в запуске программ Go в Windows 10 Iot, но у нас уже есть эта поддержка. Было бы грустно потерять его только потому, что у нас нет работающего строителя.

Спасибо.

Алекс

Обратите внимание на это предлагаемое решение для удаления временных исполняемых файлов: https://github.com/golang/go/issues/25965#issuecomment -495636291.

Нет результатов от сообщений golang-nuts; начал новую тему:
https://groups.google.com/d/topic/golang-nuts/aRvSo3iKvJY/дискуссия

По крайней мере, одна не очень гипотетическая проблема, которую, я думаю, следует исследовать, — это влияние на блокировку файлов и программы, которые на нее полагаются.

Открытие каждого файлового дескриптора с FILE_SHARE_DELETE эффективно ослабит семантику LockFileEx до семантики рекомендательных блокировок POSIX, поскольку любой файл с указанным FILE_SHARE_DELETE может быть удален, даже если блокировка снята. хранится в области этого файла. 1

Конечно, это применимо только к вызовам LockFileEx сделанным с дескриптором, возвращенным из os.File.Fd , но именно это делается в самой популярной библиотеке блокировки файлов Go (у которой в настоящее время есть 33 известных импортера , включая Docker). , gVisor и Kubernetes). Это также делается в Terraform , и LockFileEx используется во множестве другого кода Go .

Я все еще думаю, что это изменение кажется в лучшем случае опрометчивой попыткой (и CVE-фабрикой в ​​худшем случае). Я действительно не понимаю, как сомнительные преимущества перевешивают явный сбой кода, который может произойти.

Я также думаю, что большинство пользователей голанг-орехов не в полной мере осознают последствия этого изменения. С разрешения команды Go пост на golang-dev может быть более эффективным для создания дискуссии (тем более, что это основное изменение). На самом деле, технически это влияет на набор инструментов Go, который также использует LockFileEx вышеупомянутым образом .


1 Да, я знаю, что некоторые принудительные блокировки Windows будут по-прежнему сильнее, чем в POSIX, но если вы можете уничтожить файл, то использование файла блокировки для резервирования ресурсов выходит из окна.

Я отправил сообщение на golang-dev, когда открыл вопрос.
https://groups.google.com/d/topic/golang-dev/R79TJAzsBfM/дискуссия

Любая из перечисленных вами библиотек _зависит_ от_ поведения GOOS=windows? Вы неоднократно заявляли, что это изменение нарушит существующий код, но никогда не приводили никаких доказательств.

Пожалуйста, прекратите делать сенсационные необоснованные заявления. Это ничего не проясняет, и я нахожу это оскорбительным.

Я отправил сообщение на golang-dev, когда открыл вопрос. https://groups.google.com/d/topic/golang-dev/R79TJAzsBfM/дискуссия

Кажется, что все там отвергли эту идею по своим причинам (все они похожи на те, что упоминались ранее в этой ветке), но если сейчас выдвигается идея применить это изменение в Go 1.14, продолжение этого тема может быть полезной (со ссылкой на напоминание об этом обсуждении).

Зависят ли какие-либо из перечисленных вами библиотек от поведения GOOS=windows? Вы неоднократно заявляли, что это изменение нарушит существующий код, но никогда не приводили никаких доказательств.

Это потребует полной оценки безопасности ( много кода), чтобы сказать наверняка. Думаю, я предоставил достаточно доказательств того, что люди полагаются на поведение, которое заметно изменится с введением этого флага. И это всего лишь общедоступный код на GitHub.

Пожалуйста, прекратите делать сенсационные необоснованные заявления. Это ничего не проясняет, и я нахожу это оскорбительным.

Если ссылок и кода недостаточно, то я не знаю, что будет в вашей книге. Вы не найдете код, который перестанет компилироваться из-за этого изменения, но вы обязательно найдете код, который ведет себя с ним по-другому, и многие люди сочтут этот код «сломанным» из-за этих изменений в поведении.

Не принимайте мое возражение на свой счет, я думаю, что все здесь (включая вас) просто пытаются внести свой вклад в улучшение языка. Если вы собираетесь отстаивать основные изменения, вы должны ожидать здорового сопротивления.

Как я объяснил выше, удаление файла с помощью FILE_SHARE_DELETE не имеет той же функции, что и UNIX. Например, попробуйте выполнить следующие шаги.

main.c

#include <windows.h>
#include <stdio.h>

int
main() {
  // ensure delete the file
  DeleteFile("test.txt");

  // create test.txt with FILE_SHARE_DELETE
  HANDLE h = CreateFile("test.txt",
      GENERIC_READ | GENERIC_WRITE,
      FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
      NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  puts("waiting 10sec, please run watch.exe on another cmd.exe");
  Sleep(10000);
  if (DeleteFile("test.txt")) {
    puts("deleted");
  }
  getchar();
  return 0;
}

часы.c

#include <stdio.h>
#include <shlwapi.h>

int
main() {
  // waiting test.txt will be removed.
  while (PathFileExists("test.txt")) {
    puts("watching...");
    Sleep(1000);
  }
  // now test.txt should not exists. So this is possible to create new file
  system("notepad test.txt");
  return 0;
}

Makefile

all : main.exe watch.exe

watch.exe : watch.c
    gcc -o watch.exe watch.c -lshlwapi

main.exe : main.c
    gcc -o main.exe main.c

Сначала запустите main.exe, а затем watch.exe. В UNIX unlink(2) немедленно удалите файл. Таким образом, другие процессы, наблюдающие за существованием файла, могут создать новый файл с тем же именем. Но в Windows notepad.exe показывает диалоговое окно с ошибкой «нарушение прав доступа», поскольку Windows не удаляет файл сразу. Я не могу сказать вам, что произойдет с этим. Проблема безопасности? Потеряли важные файлы? Извините, я не знаю.

@havoc-io Windows не удалит открытый файл, пока он не будет закрыт, и удерживает блокировки только для открытых файлов. Таким образом, использование блокировки не означает зависимость от отказа os.Remove(). Пожалуйста, приведите примеры, которые конкретно зависят от этой ошибки.

Я пропингую ветку golang-dev на следующей неделе. На него ответили два человека; никто не читал этот выпуск и не привел примеров.

Сенсационные, необоснованные заявления не являются «здоровым сопротивлением».

@havoc-io Windows не удалит открытый файл, пока он не будет закрыт, и удерживает блокировки только для открытых файлов. Таким образом, использование блокировки не означает зависимость от отказа os.Remove(). Пожалуйста, приведите примеры, которые конкретно зависят от этой ошибки.

@networkimprov Вы

Файлы блокировки (как заблокированные с помощью LockFileEx ) обычно используются для защиты ресурса. Одним из примеров может быть обеспечение того, чтобы работал только один экземпляр демона. Если демон Windows Go открывает файл, а затем выполняет LockFileEx над файлом и успешно получает эксклюзивную блокировку, он может справедливо предположить, что файл блокировки не будет удален из-под него. Но если FILE_SHARE_DELETE включено по умолчанию, то весь существующий код, делающий такое предположение, уже неверен. Если какой-то дополнительный агент удалит этот файл блокировки (например, пользователь непреднамеренно удалит каталог), первоначальный демон все еще может работать, когда новый решит запуститься.

Файлы блокировки также можно использовать для передачи команд, если флаг LOCKFILE_FAIL_IMMEDIATELY опущен, в результате чего LockFileEx будет ждать своей очереди для получения блокировки. Если файл блокировки будет удален непреднамеренно, это может привести к одновременному выполнению нескольких команд, которые должны были быть конвейеризированы.

Сенсационные, необоснованные заявления не являются «здоровым сопротивлением».

В этой ветке никто не делает ни сенсационных, ни необоснованных заявлений. Каждый человек, не согласный с вашим предложением, включая меня, представил веские аргументы, подкрепленные кодом. Как и люди, поддержавшие ваше предложение.

Пожалуйста, ознакомьтесь с документацией WinAPI; Windows не удалит открытый файл, пока он не будет закрыт, и удерживает блокировки LockFileEx() только для открытых файлов, поэтому блокировка не может сохраняться, когда файл закрыт, независимо от удаления. Попытка открыть заблокированный (т. е. открытый) файл после удаления вызовет ошибку, поэтому вторая блокировка не может быть получена. Потоки, ожидающие блокировки, имеют открытый файл, поэтому удаление не будет прервано.

Если я неправильно прочитал документы, ваши последние два сценария не поддерживаются. И это было сенсационно и не подкреплено: «Это изменение похоже на… фабрику CVE в худшем случае. Я действительно не понимаю, как сомнительные выгоды перевешивают явный сбой кода, который может произойти».

@networkimprov Проблема в том, что файлы блокировки, ограничивающие ресурсы, обычно основаны на пути к файлу. Базовый файловый объект не может быть удален до тех пор, пока файл не будет закрыт, но если он будет удален через DeleteFile , он не будет доступен по предыдущему пути в файловой системе 1 , и это позволит создавать другие файлы блокировки. по этому пути, отрицая цель файла блокировки.

Я не думаю, что это был сенсационный комментарий — кажется совершенно очевидным, что что-то подобное может вызвать проблему безопасности, если код полагается на определенные инварианты ОС, которые больше не соответствуют действительности. И преимущества здесь кажутся довольно туманными (как прокомментировали несколько человек: это не согласовывает поведение Windows с POSIX), а неисправность кажется довольно очевидной (по крайней мере, нарушается определенное поведение блокировки).

1 По крайней мере, так кажется при тестировании и на основании этого комментария .

Относительно «в самой последней версии Windows мы обновили DeleteFile (в NTFS), чтобы выполнить удаление« POSIX », при котором файл немедленно удаляется из пространства имен» с https://github.com/golang/go/issues/ 32088#issuecomment -502850674. Насколько я знаю, эта версия Windows не выпущена. О каком тестировании вы говорите?

Что касается преимуществ, я думаю, вам следует просмотреть всю ветку.

Насколько я знаю, эта версия Windows не выпущена.

@networkimprov Я полагаю, что это уже имеет место в Windows 10. Кажется, я могу удалить файл, заблокированный с помощью LockFileEx если при открытии файла для FILE_SHARE_DELETE указано значение CreateFileW .

DeleteFileW() не выдаст ошибку в этом сценарии. Как узнать, что он был немедленно удален из файловой системы, а не удален при закрытии файла/приложения? Вы пытались открыть/воссоздать файл после его удаления?

На самом деле, технически это влияет на цепочку инструментов Go, которая _также_ использует LockFileEx вышеупомянутым образом.

В команде go нам очень нужна семантика POSIX, и мы никоим образом не должны полагаться на отсутствие FILE_SHARE_DELETE . На самом деле, я добавил пакет cmd/go/internal/robustio специально, чтобы _обходить_ способы, которыми блокировка файлов Windows отличается от POSIX.

@ianlancetaylor Я неоднократно отправлял сообщения в golang-nuts & -dev в поисках комментариев от любого проекта, основанного на текущем (недокументированном) поведении Windows syscall.Open(). Не возникло ни одного варианта использования.

Так как несколько вариантов использования, которые требуют FILE_SHARE_DELETE, появились выше (и поскольку Microsoft специально попросила нас использовать его), давайте установим этот флаг по умолчанию в предварительной версии 1.14 и повторно оценим, есть ли какие-либо варианты использования исходного поведения во время цикла.

Как предлагалось ранее в этой ветке, тот, кому нужен вызов open() без этого флага, может свернуть свой собственный, используя CreateFileW() & os.NewFile() .

Мы можем хотя бы выставить ручку для этого?

Это очень полезно для реализации ротации журналов в Windows.

Это очень полезно для реализации ротации журналов в Windows.

Я не уверен, но предполагаю, что это не сработает, так как FILE_SHARE_DELETE не позволяет создать новый файл с исходным именем файла, если дескриптор файла не закрыт.

@ Random-Liu, вы спрашиваете, как включить fsd в Open () или отключить его?

@mattn Это работает для меня. После os.Rename старого файла журнала я могу создать новый файл журнала с исходным именем и сообщить демону, чтобы он снова открыл файл журнала.

@networkimprov Мне нужен способ включить FILE_SHARE_DELETE . В настоящее время я использую syscall.CreateFile напрямую.

Моя версия Windows:

Major  Minor  Build  Revision
-----  -----  -----  --------
10     0      17763  0

@ Random-Liu Но я думаю, мы не можем использовать это для имитации поведения UNIX.

https://github.com/golang/go/issues/32088#issuecomment-510345908

Внешнее приложение не может получить доступ к исходному имени файла.

Мэттн, нет необходимости повторять этот момент. Уже было сказано, что мы должны задокументировать необходимость (в Windows) переименовать файл перед его удалением, когда имя файла должно быть повторно использовано до закрытия дескриптора.

Если он переименовывает файл перед закрытием самим приложением, я чувствую, что нет плюсов для пользователей, которые хотят делать logrotate.

@ianlancetaylor повторите попытку: https://github.com/golang/go/issues/32088#issuecomment -526074116

Я пометил эту проблему как блокировщик выпуска.

@bcmills, могу ли я заинтересовать вас публикацией CL?

Эта строка: https://golang.org/src/syscall/syscall_windows.go#L291
Всего нужно: sharemode := uint32(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)

Изменить: тест для проверки создаст файл, а затем попытается переименовать его перед закрытием.

Нам нужно это как можно скорее, чтобы успеть выявить любые проблемы до замораживания...

Также в документах os.Remove() следует упомянуть, что в Windows открытый файл должен быть переименован перед удалением, если имя файла должно быть доступно для повторного использования до закрытия файла. Windows не завершает удаление открытого файла, пока он не будет закрыт.

Похоже, последняя версия Windows соответствует https://github.com/golang/go/issues/32088#issuecomment -502850674. Подробнее см. https://github.com/papertrail/go-tail/pull/10#issuecomment-529460973 . Теперь мы можем повторно использовать имя удаленного файла, даже если все еще есть открытые дескрипторы старого файла, при условии, что эти дескрипторы были открыты в режиме общего доступа FILE_SHARE_DELETE . Не нужно переименовывать перед удалением. Возможно, наконец, имеет смысл установить FILE_SHARE_DELETE по умолчанию в Go?

Это намерение; нам просто нужно, чтобы кто-то представил CL. (Я бы хотел, но я не могу подписать CLA.)
Мои предыдущие два комментария описывают необходимые изменения...

@jstarks @jordanrh1 @thaJeztah

Вопрос:
Как добиться согласованного поведения?
Если программа работает в «последней версии Windows», это позволит создать файл с именем, похожим на файл «ожидающий удаления», более старая версия выйдет из строя при ошибке.

На самом деле это может означать: «пройдены тесты / контроль качества, но не работает на большинстве целей» сейчас, и
«обычно работает, а иногда нет» в будущем (разработчик через 2 года, получающий ошибку «файл уже вышел», не сможет воспроизвести).

Нам нужно выяснить, как идентифицировать «старые» версии и либо изменить поведение по умолчанию для них, либо исправить ошибку, если «есть ожидающее удаление» — я не уверен, как мы можем это сделать.
И если мы не можем, как мы справляемся с непоследовательностью.

@guybrand, как указано выше, мы задокументируем, что в Windows «открытый файл должен быть переименован перед удалением, если имя файла должно быть доступно для повторного использования до закрытия файла».

Это работает для старых и новых версий Windows, а также для POSIX.

Это звучит как другой подход: «Всегда переименовывать перед повторным использованием, даже в новой Windows или POSIX».
Мне это нравится - это подтверждает распространенную практику, которая будет успешной везде.

Изменение https://golang.org/cl/194957 упоминает эту проблему: syscall: allow FILE_SHARE_DELETE on syscall.Open on Windows

Тема golang-nuts находится здесь и не получила ответов. Похоже, это указывает на то, что никто из тех, кто читает golang-nuts и знает об этом поведении, не полагается на него.

Темы golang-dev находятся здесь и здесь , и последняя (более свежая) также не вызвала возражений.

@alexbrainman -2 посоветовал CL реализовать его из-за очевидного отсутствия решения, поэтому я переименовал эту проблему в NeedsDecision чтобы мы могли получить четкое направление.

Лично мне решение кажется ясным: учитывая, что люди из Microsoft в этой ветке поддерживают изменение поведения по умолчанию для использования FILE_SHARE_DELETE (https://github.com/golang/go/issues/32088 #issuecomment-502850674), и что никого на golang-nuts кажется, это не волнует, так или иначе, я думаю, мы должны это сделать.

Возражения, которые я видел в этой теме, по-видимому, делятся на две категории:

  1. Беспокойство @alexbrainman (https://github.com/golang/go/issues/32088#issuecomment-504321027) и @mattn (https://github.com/golang/go/issues/32088#issuecomment-494417146) похоже, что, поскольку поведение по-прежнему не будет соответствовать POSIX, пользователи, ожидающие семантики POSIX, могут быть сбиты с толку. (Но это уже тот случай, когда Open в Windows не обеспечивает семантику POSIX, поэтому мне кажется, что эта проблема не зависит от предлагаемого изменения.)

  2. Обеспокоенность @havoc-io (https://github.com/golang/go/issues/32088#issuecomment-510314947), по-видимому, заключается в том, что FILE_SHARE_DELETE уменьшит инварианты, предоставляемые пакетами блокировки файлов при работе в Windows. . Но этот аргумент кажется мне немного запутанным, потому что конкретный пакет, приведенный в качестве примера ( github.com/gofrs/flock ), явно отмечает, что «поведение блокировки не обязательно будет одинаковым на каждой платформе», и из быстрого аудита ни один из конкретных примеров, по-видимому, не опирается на характерное для Windows взаимодействие между блокировкой и удалением файла.

Однако комментарий в https://github.com/golang/go/issues/32088#issuecomment -498690757 меня заинтриговал:

Я согласен с @ianlancetaylor, изменение всего поведения цепочки инструментов, как это предлагается в https://codereview.appspot.com/8203043/ , не является хорошей идеей. Одна непосредственная проблема, которая приходит на ум, это предложение при использовании дескриптора файла в качестве файла блокировки. Мы используем это поведение.

@maruel , не могли бы вы подробнее рассказать о том, как ваша программа использует специфичное для Windows взаимодействие между блокировкой файлов и удалением или переименованием файлов?

Мы в Chrome Infra используем наличие файла в качестве механизма блокировки. Я бы сказал, чтобы не слишком беспокоиться об этом варианте использования. На практике от того, что я понимаю O_CREATE + FILE_FLAG_DELETE_ON_CLOSE семантика достаточно для этого нужно , и это может быть заменен мьютекса в Global\\ имен, которые мы уже делаем в Python кода.

Это решение не упоминается:

path := "delete-after-open"
fd, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0600)
if err != nil { ... }
// defer statements are executed in LIFO
defer os.Remove(path)            // or defer os.Rename(path, path+"2")
if err != nil { ... }
defer fd.Close()

У него есть два недостатка:

  • Close() не сразу после Open()
  • Восстановить код ошибки сложнее

@iwdgo Это решение предназначено для использования в одном процессе. Большая часть приведенного выше обсуждения касается одновременного доступа из нескольких процессов. Вот что делает флаг важным.

Чтобы дополнить повторный обзор проблемы @bcmills , мы рассмотрели три других кроссплатформенных инструмента разработки:

Mingw-w64 сделал это по умолчанию семь лет назад:
https://sourceforge.net/p/mingw-w64/code/HEAD/tree/stable/v3.x/mingw-w64-headers/include/ntdef.h#l858

Erlang сделал его по умолчанию более шести лет назад: erlang /

Python не мог принять его из-за ограничений среды выполнения MSVC в то время, когда они его рассматривали: https://bugs.python.org/issue15244.

Вторая ветка golang-nuts также не вызвала возражений:
https://groups.google.com/d/topic/golang-nuts/aRvSo3iKvJY/дискуссия

Вы хотите сделать FILE_SHARE_DELETE по умолчанию для Open()? Наша дискуссия не должна быть такой.

  1. Обеспокоенность @alexbrainman ( #32088 (комментарий) ) ... похоже, что, поскольку поведение все еще не соответствует POSIX ...

Меня больше всего беспокоит слом программ людей. Подобно тому, что сказал @minux на https://codereview.appspot.com/8203043/#msg10

другая причина: люди, использующие Windows, признают, что не могут переименовывать или
удалить открытый файл это норма и уже привык к этому факту.

Мы не можем молча изменить поведение программы Go 1.0 здесь, возможно, люди
на самом деле зависят от этого.

Что случилось с обещанием совместимости с Go 1.0? Где в https://golang.org/doc/go1compat говорится, что можно изменить поведение открытого файла, если вы объявите о своем намерении в бешенстве?

Может быть, мы сможем изменить это поведение, как только представим Go 2? Можем ли мы использовать модули для защиты существующего кода от взлома? Я ошибаюсь, полагая, что именно для этого были разработаны модули?

Лично мне решение кажется ясным: учитывая, что люди из Microsoft в этой ветке поддерживают изменение поведения по умолчанию для использования FILE_SHARE_DELETE ...

Если Microsoft является для вас авторитетом в этом вопросе, то вам следует взглянуть на то, что делают инструменты разработки Microsoft.

Я использовал эту программу https://play.golang.org/p/4ZPmV6Df3SD, аналогичную той, что @mattn опубликовал на https://github.com/golang/go/issues/32088#issuecomment -493753714. Я построил ее с помощью последнего компилятора C.

Оптимизирующий компилятор Microsoft (R) C/C++ версии 19.16.27030.1 для x64
Авторское право (C) Microsoft Corporation. Все права защищены.

И когда я запускал программу, я не мог удалить файл C:\Users\Alex\AppData\Local\Temp\a.txt пока программа не существовала.

То же самое с аналогичной программой C # https://play.golang.org/p/SuM2iWYpZir. Я использовал последние

Компилятор Microsoft (R) Visual C# версии 2.10.0.0 (b9fb1610)
Авторское право (C) Microsoft Corporation. Все права защищены.

Точно так же эта программа C https://play.golang.org/p/6HgxePzEW_W, созданная с помощью последнего Mingw

gcc (x86_64-win32-seh-rev0, создан проектом MinGW-W64) 7.3.0
Авторское право (C) 2017 Free Software Foundation, Inc.

имеют тот же эффект. Таким образом, ни один из этих инструментов не имеет установленного по умолчанию флага FILE_SHARE_DELETE.

И лично я не вижу никакой пользы от этого изменения. Это включает в себя мой вклад в Go. Мы по-прежнему не сможем удалять исполняемые файлы во время работы процесса. У нас по-прежнему будут проблемы с удалением каталогов, если какой-либо процесс установил там свои текущие каталоги.

Алекс

Вы хотите сделать FILE_SHARE_DELETE по умолчанию для Open()?

Цель состоит в том, чтобы заставить os.Open использовать FILE_SHARE_DELETE по умолчанию. Это https://golang.org/cl/194957 и есть план.

Алекс

Моя главная забота - сломать программы людей

Пока что никто не может указать ни одного примера программы, которая могла бы сломаться, потому что она полагается на получение ошибки при попытке переименовать/удалить файл, открытый с помощью Go. Но есть задокументированные случаи, когда текущее поведение нарушает работу приложений Go в Windows.

Что случилось с обещанием совместимости с Go 1.0?

Документы Go для os.Rename() и .Remove() ничего не говорят об этой функции Windows, но содержат 15 упоминаний о другом поведении, специфичном для Windows. Кстати, Go 2 уже отправлен: https://blog.golang.org/go2-here-we-come

gcc (x86_64-win32-seh-rev0, создан проектом MinGW-W64) 7.3.0

Чью C-lib вы используете с gcc — возможно, Microsoft? Mingw-w64 имеет версию 6.0 и включает FILE_SHARE_DELETE в FILE_SHARE_VALID_FLAGS.

Как описано выше в отношении Python, в прошлом среда выполнения MSVC запрещала определенные комбинации флагов CreateFile() по неизвестным причинам. Я не знаю, так ли это до сих пор, но это может быть причиной того, что MS не изменила свою C-lib fopen().

Я не вижу никакой пользы от этого изменения.

Преимущества неоднократно объяснялись выше несколькими людьми; особенно позволяя os.Rename() в открытом файле.

Пометка как предложение, потому что дискуссия не сходится легко.

Чтобы попытаться обобщить проблему и обсуждение до сих пор:

  • В системах Unix, если один процесс открывает файл, второй процесс может удалить этот файл. Исходный процесс может продолжать использовать файл (у него больше нет имени), включая чтение и запись, которые завершатся успешно. Содержимое файла не удаляется с диска до тех пор, пока не исчезнет последняя открытая ссылка.

  • В системах Windows по умолчанию, если один процесс открывает файл, второй процесс _не может_ удалить этот файл. Файл можно удалить, только если он не открыт ни одним другим процессом. Флаг FILE_SHARE_DELETE изменяет это поведение: если все открытые ссылки на файл были открыты с помощью FILE_SHARE_DELETE, тогда другой процесс может вызвать DeleteFile, и он завершится успешно, а не с ошибкой.

Поведение Windows с FILE_SHARE_DELETE все еще немного отличается от поведения Unix. На странице MSDN написано:

Функция DeleteFile дает сбой, если приложение пытается удалить файл, у которого есть другие дескрипторы, открытые для обычного ввода-вывода, или как файл с отображением в память (должен быть указан FILE_SHARE_DELETE при открытии других дескрипторов).

Функция DeleteFile помечает файл для удаления при закрытии. Поэтому удаление файла не происходит до тех пор, пока не будет закрыт последний дескриптор файла. Последующие вызовы CreateFile для открытия файла завершаются ошибкой ERROR_ACCESS_DENIED.

То есть файл _имя_ не удаляется из файловой системы до тех пор, пока не исчезнут открытые ссылки. Это отличается от Unix, где имя исчезает до закрытия открытых ссылок. Так что, если вы удаляли файл, готовясь к воссозданию файла с тем же именем, это работает в Unix, но не в Windows, даже с FILE_SHARE_DELETE.

Первоначальное предложение состояло в том, чтобы изменить syscall.Open, чтобы всегда устанавливать этот флаг. Учитывая, что системный вызов должен быть близок к интерфейсу ядра, добавление этого флага кажется неуместным. Но мы могли бы вместо этого рассмотреть, следует ли устанавливать его во время os.Open / os.OpenFile.

@alexbrainman привел аргумент, что Unix и Windows разные, они останутся разными, даже если мы установим этот флаг, и поэтому мы не должны запутывать дело, внося это изменение за спиной пользователей.

@jstarks из Microsoft говорит, что Microsoft предпочла бы, чтобы мы автоматически установили FILE_SHARE_DELETE, и что «самая последняя версия» Windows (неясно, какая или выпущена ли она) изменяет DeleteFile, чтобы семантика имени Unix исчезала при возврате из DeleteFile. Так что, если бы мы сделали это в последней версии Windows, мы действительно получили бы одинаковое поведение в Unix и Windows.

@networkimprov утверждает, что мы должны внести изменения и что нет реальных примеров программ, которые могут сломаться.

@alexbrainman все еще кажется

@mattn также предложил, чтобы мы не устанавливали флаг автоматически, отчасти потому, что (по крайней мере, до тех пор, пока все не будут использовать последнюю

В целом, package os пытается предоставить переносимый API, и кажется, что — особенно с изменением Windows — автоматическая установка флага поможет в этом. Есть и другие флаги, которые мы автоматически устанавливаем в os.Open, чтобы сделать поведение менее неожиданным для пользователей, в частности, O_CLOEXEC (close-on-exec). Любая программа, которая ломается в Windows из-за установки этого флага в os.Open, обязательно сломается и в Unix, верно?

Тем более, что по крайней мере один инженер в Microsoft говорит, что мы должны установить его, кажется, что было бы нормально установить в package os. Люди, которые не хотят, чтобы флаг был установлен, все равно могут вернуться к пакетному системному вызову.

@alexbrainman и @mattn предположим, что мы установили флаг в os.Open. Мы знаем, что некоторые программы будут работать лучше. У вас есть конкретные примеры реальных (или вероятных) программ, которые могут сломаться? Не будут ли эти программы уже сломаны в Unix? Спасибо.

@rsc , спасибо за ваш вклад. Включение этого в пакете «os» работает для меня. Если это принято, для полноты необходимо предоставить флаг syscall.Open() для включения FILE_SHARE_DELETE.

Чтобы уточнить ваше резюме, границы процесса не являются фактором; эффекты одинаковы в рамках одного процесса. И этот флаг также влияет на переименование файла; с флагом он завершается немедленно, в отличие от удаления файла. (И это позволяет обходной путь для доступности имени файла сразу после удаления.)

Спасибо @networkimprov. Да, я понимаю, что границы процесса не имеют строгого значения; это просто облегчает описание ситуаций.

Я изменил заголовок и пересмотрел план действий в тексте вопроса:

а) os.Create/Open/OpenFile() всегда должен включать file_share_delete в Windows,
b) syscall.Open() в Windows должен принимать флаг, разрешающий file_share_delete, и
c) системный вызов в Windows должен экспортировать константу для нового флага.

В документах для os.Remove() также следует отметить, что для повторного использования имени файла после удаления открытого файла в Windows необходимо выполнить: os.Rename(path, unique_path); os.Remove(unique_path) .

Нельзя отрицать возможность того, что есть пользователь, который реализует эксклюзивную обработку с использованием текущего поведения Go os.Open.

Но есть задокументированные случаи, когда текущее поведение нарушает работу приложений Go в Windows.

Что это? Какие номера выпусков? И что вы подразумеваете под словом break ? Когда они сломались?

Чью C-lib вы используете с gcc — возможно, Microsoft? Mingw-w64 имеет версию 6.0 и включает FILE_SHARE_DELETE в FILE_SHARE_VALID_FLAGS.

Я не знаю. Я только что скачал файл x86_64-7.3.0-release-win32-seh-rt_v5-rev0.7z из Интернета. Вы можете найти его в Google. Это версии 7.3.

Я не вижу никакой пользы от этого изменения.

Преимущества были неоднократно объяснены выше, ...

Вы убрали слово personally из цитаты. Я говорю о своих потребностях. И это включает в себя мой вклад в проект Go.

Чтобы попытаться обобщить проблему и обсуждение до сих пор:

Вы не сказали, что инструменты разработчика Microsoft в Windows не предоставляют этот флаг по умолчанию. Смотрите мой комментарий https://github.com/golang/go/issues/32088#issuecomment -531713562

Вы также проигнорировали мой комментарий о совместимости с Go 1.

Что случилось с обещанием совместимости с Go 1.0? Где в https://golang.org/doc/go1compat говорится, что можно изменить поведение открытого файла, если вы объявите о своем намерении в бешенстве?

Как это изменение вписывается в обещание совместимости с Go 1.0?

В целом, package os пытается предоставить переносимый API, и кажется, что — особенно с изменением Windows — автоматическая установка флага поможет в этом.

Я согласен, что это изменение направлено на людей, переносящих программное обеспечение Unix на Windows.

К сожалению за счет пользователей Windows. Это изменение будет неожиданным для пользователей и разработчиков Windows, потому что никакие другие программы или инструменты в Windows не работают таким образом. Может быть, в будущем, но не сейчас.

И даже для людей, переносящих программное обеспечение Unix в Windows, это изменение, ИМХО, делает довольно плохую работу. См. Часть We still won't be able to delete executable files while process is running. We will still have problems deleting directories if any process have their current directories set there. из моего комментария https://github.com/golang/go/issues/32088#issuecomment -531713562

Есть и другие флаги, которые мы автоматически устанавливаем в os.Open, чтобы сделать поведение менее неожиданным для пользователей, в частности, O_CLOEXEC (close-on-exec). Любая программа, которая ломается в Windows из-за установки этого флага в os.Open, обязательно сломается и в Unix, верно?

Я не знаю, что вы имеете в виду здесь. O_CLOEXEC в Windows означает предотвращение перехода дескрипторов файлов в дочерний процесс. И O_CLOEXEC всегда устанавливается в os.Open в Windows. Таким образом, ни один файл, открытый с помощью os.Open, не может быть доступен дочернему процессу в Windows. Ну и что? Звучит разумно.

Не будут ли эти программы уже сломаны в Unix?

Я не понимаю вашего вопроса.

У вас есть конкретные примеры реальных (или вероятных) программ, которые могут сломаться?

Не знаю, есть ли у меня такие программы. Невозможно сказать. Я не помню все программы, которые я написал. Я не помню, что они делают.

Но я мог представить, что служба Windows открывает предопределенный файл, скажем c:\mysvc.txt , и держит его открытым. Представьте себе другую программу, которая проверяет работоспособность службы и при необходимости перезапускает ее. Эта другая программа может просто попытаться удалить c:\mysvc.txt , и, если файл исчез, она может с уверенностью предположить, что сервисный процесс мертв.

Кроме того, я могу себе представить, что некоторые программы могут захотеть запретить пользователям удалять или перемещать свои файлы - https://stackoverflow.com/questions/11318663/prevent-a-user-from-deleting-moving-or-renameing-a-file

Алекс

Вот точка данных о повторном развертывании Go в Windows:
Win8 вышла 7 лет назад.
Об этой ошибке Go, затрагивающей Win8+, сообщалось всего 5 месяцев назад: #31528.

@rsc любая программа, которая ожидает получить ошибку при попытке переименовать/удалить файл, открытый с помощью Go, не работает в Unix. Например, сценарий Алекса c:\mysvc.txt .

Если кто-то разрабатывает в Windows и развертывает в Linux/и т. д. (или наоборот), это может быть источником ошибок.

Наш вариант использования: у нас есть регистратор, который записывает данные в файл.
Файл журнала поддерживает ротацию (например, следующая запись, если файл журнала > maxSize, затем ротация).
У нас также есть программы чтения, которые откроют эти файлы и оставят их открытыми, пока они читаются.

Даже без возможности установить FILE_SHARE_DELETE мы не можем выполнить ротацию (переименовать и/или удалить), пока есть активные читатели.

@маттн :

Нельзя отрицать возможность того, что есть пользователь, который реализует эксклюзивную обработку с использованием текущего поведения Go os.Open.

Может быть, но такой код в любом случае некорректен в Unix. Пакет os предназначен для переносимого интерфейса. Также нас больше интересуют _фактические_ программы, чем возможности.

@alexbrainman :

Вы не сказали, что инструменты разработчика Microsoft в Windows не предоставляют этот флаг по умолчанию. См. мой комментарий #32088 (комментарий)

Да, но это C; мы говорим о Го. Мы не имитируем C API в целом. Мы пытаемся предоставить переносимую абстракцию в пакете os. Дело в том, что O_CLOEXEC заключается в том, что в системах Unix os.Open устанавливает O_CLOEXEC, даже если он не установлен по умолчанию в соответствующем вызове C. Мы _делаем_ пытаемся предоставить полезные значения по умолчанию, которые ведут себя примерно одинаково во всех системах.

Что касается совместимости с Go 1, изменение поведения крайних случаев в одной ОС для лучшего согласования с другими ОС кажется нормальным. Мы не обещаем навсегда сохранить все межсистемные несовместимости.

Пользователи Windows, которые настаивают на специфичном для Windows поведении, всегда имеют возможность использовать пакетный системный вызов, что ясно показывает, что требуемое поведение действительно применимо только к Windows.

Опять же, у вас есть конкретные примеры реальных программ, которые ломаются?

@cpuguy83 cpuguy83 , спасибо за реальный пример использования программы, которая может помочь.

@rsc Как вы сказали, я еще не понял, к каким последствиям приводит FILE_SHARE_DELETE. Точно так же не было найдено подтверждения того, что logrotate можно реализовать с помощью этого изменения.

Однако, как только Go включит это изменение, которое может удалить файл, пользователь будет ожидать такого поведения, даже если Go не может реализовать logrotate в Windows с помощью этого изменения. Так что мы должны тщательно обдумать это изменение. По сути, Windows не может удалять файлы, которые не помечены FILE_SHARE_DELETE. Пользователи могут подумать, что это изменение позволит Go удалять любые файлы. В частности, файлы, созданные с помощью os.NewFile с дескриптором, могут манипулировать методами структуры os.File, даже если этот флаг не установлен. Программист должен знать, что файл может быть удален на винде или нет, сам по себе, я думаю. Я предпочитаю механизм, который позволяет передавать FILE_SHARE_DELETE по флагу для os.OpenFile, а не по умолчанию. Я думаю, что поведение по умолчанию должно соответствовать поведению ОС.

Да, но это C; мы говорим о Го.

И С# тоже. Я не исследовал, но я уверен, что любой другой инструмент разработки в Windows ведет себя так.

Что касается совместимости с Go 1, изменение поведения крайних случаев в одной ОС для лучшего согласования с другими ОС кажется нормальным.

Это крайний случай для пользователей, не являющихся пользователями Windows. Для пользователей Windows такое поведение является стандартным.

Опять же, у вас есть конкретные примеры реальных программ, которые ломаются?

Рассмотрим мой пример службы выше конкретного.

@cpuguy83 cpuguy83 , спасибо за реальный пример использования программы, которая может помочь.

@ cpuguy83, можешь ли ты рассказать больше о своей проблеме? Я хотел бы посмотреть, как это изменение решит вашу проблему, поэтому я хотел бы увидеть реальный код. Если вы можете предоставить небольшую программу, которая сейчас сломана, но будет работать после решения этой проблемы, это будет оценено. Его не нужно строить, но, по крайней мере, он должен обеспечить достаточно, чтобы каждый мог судить. Спасибо.

По сути, Windows не может удалять файлы, которые не помечены FILE_SHARE_DELETE. Пользователи могут подумать, что это изменение позволит Go удалять любые файлы.

Я согласен, что это хороший момент. Удалять можно было только файлы, открытые программами Go. Но все остальные файлы, открытые программами, отличными от Go, по-прежнему останутся неудаляемыми. Это сделает работу os.Remove непредсказуемой — иногда она будет работать, а иногда нет.

Алекс

У меня есть код, вызывающий исправленный syscall.Open(), который переименовывает открытые файлы. Вот как вы реализуете ротацию журналов; оно работает.

// several processes or goroutines do this
   fd, err := os.OpenFile("log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, ...)
   _, err = fd.Write(log_entry) // one or more times
   err = fd.Close()

// log rotator process does this
   err := os.Rename("log", "log_old") // active writers not disturbed
   fmt.Println(err) // "sharing violation" without FILE_SHARE_DELETE

Но вам не нужно было, чтобы кто-то говорил вам об этом :-/

Естественно, os.File из os.NewFile() может иметь множество других свойств, чем свойства из os.Create/Open/OpenFile(), это особенность, а не сюрприз.

Вы утверждаете, что поведение Microsoft C fopen() является поведением Windows по умолчанию; это не правда. Нет режима совместного использования CreateFile() по умолчанию; это не FILE_SHARE_READ | FILE_SHARE_WRITE .

Вы утверждаете, что развернутые программы Windows обычно предполагают, что __программы других поставщиков__ всегда открывают файлы определенным образом. Если бы это было так, Microsoft не попросила бы нас включить file_share_delete по умолчанию.

@networkimprov

Но вам не нужно было, чтобы кто-то говорил вам об этом :-/

Я хочу знать, что внешний процесс может правильно удалить и переименовать файл в любых случаях.

Что я хочу знать, так это то, что после включения этого изменения файл журнала выводится из Go, logrotate, выполняемый внешним процессом, работает нормально, и он работает как готовый к работе без каких-либо ошибок.

@alexbrainman

К сожалению, код сложен, но это настоящий реальный код: https://github.com/moby/moby/blob/master/daemon/logger/loggerutils/logfile.go.

Предлагаемое смягчение в настоящее время состоит в том, чтобы просто разветвить os.OpenFile и syscall.Open: https://github.com/moby/moby/pull/39974/files.

Чтобы было ясно, просто иметь возможность пройти через FILE_SHARE_DELETE из OpenFile или даже к syscall.Open было бы достаточно, чтобы справиться с нашим случаем.

К сожалению, код сложен, но это настоящий реальный код: https://github.com/moby/moby/blob/master/daemon/logger/loggerutils/logfile.go.

Предлагаемое смягчение в настоящее время состоит в том, чтобы просто разветвить os.OpenFile и syscall.Open: https://github.com/moby/moby/pull/39974/files.

@ cpuguy83 спасибо за указатель.

К сожалению, мой краткий обзор не дает достаточно информации, чтобы судить, является ли эта текущая проблема решением. Мне пришлось бы начать с репро (вероятно, с этого https://github.com/moby/moby/issues/39274#issuecomment-497966709) и взять его оттуда. Я не уверен, когда у меня будет время потратить на это. Если у вас есть лучшее предложение, чем мой план, дайте мне знать.

Алекс

Ниже приведена рабочая демонстрация ротации журнала стандартной практики, в которой 100 горутин регистрируют 50 строк с интервалом 0,1-2 секунды каждый раз, когда они открывают файл журнала, а другая горутина ротирует журнал с интервалом в 1 минуту. Вчера я запускал это несколько часов на ноутбуке с Win7 без ошибок.

Он построен с патчем для syscall.Open(), который включает FILE_SHARE_DELETE; в противном случае это не удается. Можно было бы придумать более сложную схему регистрации без этого флага, но у него есть много других применений; мой собственный код переименовывает открытые файлы по разным причинам.

@rsc , я считаю, что это завершает аргументы в пользу вашего предложения, если еще были какие-то сомнения. (И, как отмечалось выше, я неоднократно писал в golang-dev и golang-nuts с просьбой о проектах, которые полагаются на текущее поведение, но безрезультатно.)

package main

import (
   "fmt"
   "os"
   "math/rand"
   "syscall"
   "time"
)

const kLogFile = "winrotate"

func main() {
   syscall.Open_FileShareDelete = true
   rand.Seed(time.Now().UnixNano())

   for a := 0; a < 100; a++ {
      go runLog()
   }

   fmt.Println("rotating to "+ kLogFile +"N.txt")
   for a := 0; true; a++ {
      if a >= 5 {
         a = 0
      }
      time.Sleep(60 * time.Second)
      err := os.Rename(kLogFile +".txt", kLogFile + string('0'+a) +".txt")
      if err != nil {
         fmt.Println(err)
         os.Exit(1)
      }
   }
}

const kLineVal = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

func runLog() {
   aLine := make([]byte, 102)
   for {
      aFd, err := os.OpenFile(kLogFile +".txt", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
      if err != nil {
         fmt.Println(err)
         return
      }
      for _, aV := range kLineVal {
         for a := range aLine {
            aLine[a] = byte(aV)
         }
         aEnd := aV - ('z' - 100) // limit line len to 100
         copy(aLine[aEnd:], []byte{'\r','\n'})
         _, err = aFd.Write(aLine[:aEnd + 2])
         if err != nil {
            fmt.Println(err)
            return
         }
         time.Sleep(time.Duration(100 + rand.Intn(1900)) * time.Millisecond)
      }
      err = aFd.Close()
      if err != nil {
         fmt.Println(err)
         return
      }
   }
}

Патч для syscall_windows.go:

diff --git a/src/syscall/syscall_windows.go b/src/syscall/syscall_windows.go
index de05840..e1455d5 100644
--- a/src/syscall/syscall_windows.go
+++ b/src/syscall/syscall_windows.go
@@ -245,6 +245,8 @@ func makeInheritSa() *SecurityAttributes {
    return &sa
 }

+var Open_FileShareDelete = false
+
 func Open(path string, mode int, perm uint32) (fd Handle, err error) {
    if len(path) == 0 {
        return InvalidHandle, ERROR_FILE_NOT_FOUND
@@ -270,6 +272,9 @@ func Open(path string, mode int, perm uint32) (fd Handle, err error) {
        access |= FILE_APPEND_DATA
    }
    sharemode := uint32(FILE_SHARE_READ | FILE_SHARE_WRITE)
+   if Open_FileShareDelete {
+       sharemode |= FILE_SHARE_DELETE
+   }
    var sa *SecurityAttributes
    if mode&O_CLOEXEC == 0 {
        sa = makeInheritSa()

@networkimprov Файл не переименовывается внешним процессом. Если переименование файла выполняется в том же процессе, есть и другие способы его реализации.

https://github.com/mattn/go-sizedwriter/blob/master/_example/example.go#L14

Ниже приведена рабочая демонстрация ротации журналов стандартной практики.

Спасибо за ваш пример, @networkimprov . Теперь я вижу, что вы пытаетесь сделать.

Единственная проблема с вашим кодом заключается в том, что если какой-то внешний процесс откроет файлы, в которые вы пишете, без FILE_SHARE_DELETE, у вас будет та же проблема, что и сейчас, независимо от изменений, которые мы вносим в Go.

@cpuguy83 Я не буду рассматривать вашу проблему (как я и обещал здесь https://github.com/golang/go/issues/32088#issuecomment-536233195), потому что пример

Алекс

Алекс, можно зациклить попытку переименования, если разрешен доступ извне, или ограничить активный лог-файл, если нет.

Mattn, переименование с поворотом обычно выполняется отдельным процессом.

Кажется, здесь нет однозначного согласия. Разработчики с опытом работы в Unix, кажется, поддерживают это, но двое из наших самых активных разработчиков Windows ( @alexbranman и @mattn) против. Это изменение также по-настоящему унифицирует только последнюю версию Windows, которая имеет новое Unix-подобное поведение для FILE_SHARE_DELETE.

Возможно, стоит вернуться к этой теме через несколько лет, чтобы увидеть, насколько широко доступно новое поведение флага и пришли ли другие языки к общему поведению. Если кто-то захочет подать новый вопрос, скажем, через два года или около того, чтобы переосмыслить это, это будет хорошо.

Но на данный момент, учитывая отсутствие консенсуса, это кажется вероятным снижением .

Оставляем открытым на неделю для окончательных комментариев.

Есть ли разногласия по поводу того, чтобы сделать эту опцию доступной?

Если нам нужно больше голосов, чтобы вмешаться, я определенно буду за то, чтобы сделать _something_. Прямо сейчас единственное решение — буквально скопировать кучу кода из стандартной библиотеки Go, изменить одну строку, а затем поддерживать этот форк навсегда. Каждая реализация монитора журнала или живого «хвоста» в конечном итоге делает это.

Для примера см. https://github.com/elastic/beats/blob/master/libbeat/common/file/file_windows.go#L85 -L103.

Если я правильно понял, это предложение на самом деле состоит из двух частей:

  1. Разрешить использование флага FILE_SHARE_DELETE при открытии файла -
    реализация должна быть простой и безопасной, разработчик должен явно
    запрашивать этот режим при открытии нового файла
  2. Включите этот флаг по умолчанию — это может быть рискованно и поддерживается только
    при использовании последней сборки Windows 10.

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

В среду, 2 октября 2019 г., 19:32 Марк Дашер, уведомления@github.com написал:

Если мы ищем больше голосов, чтобы вмешаться, я определенно буду за
делать что-то . На данный момент единственным решением является буквальное копирование
кучу кода из стандартной библиотеки Go, измените одну строку, а затем
сохранить эту вилку навсегда. Каждая реализация монитора журнала или живого
«хвост» в конце концов, кажется, делает это.

Для примера см.
https://github.com/elastic/beats/blob/master/libbeat/common/file/file_windows.go#L85 -L103


Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/golang/go/issues/32088?email_source=notifications&email_token=ABNEY4VFQLSYVI66ENT6G4LQMTLJ7A5CNFSM4HNPNYIKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEAFRRIQ#issuecomment-537
или заглушить тему
https://github.com/notifications/unsubscribe-auth/ABNEY4U5L3WFZYNIUOSEY2TQMTLJ7ANCNFSM4HNPNYIA
.

Однозначно за то, чтобы _хотя бы_ иметь 1. (Разрешить использование флага FILE_SHARE_DELETE при открытии файла)

Просто чтобы вернуться к моему предыдущему предложению и дополнить пример реализации :

Флаг syscall.FILE_SHARE_DELETE прекрасно вписался бы в параметр os.OpenFile flag и обеспечил бы тривиальный механизм для включения этого поведения по требованию. Он не конфликтует с какими-либо другими флагами os.O_* в Windows (и это может быть реализовано/спроектировано) и предоставляет идиоматический способ указать характерное для системы поведение при открытии файлов (поскольку os ' POSIX-ориентированный интерфейс можно считать идиоматическим в Windows). Это тот же маршрут, который уже использовался для передачи специфичного для системы поведения открытия файлов на платформах POSIX (например, syscall.O_DIRECTORY в Linux).

Даже если будет принято решение не включать его по умолчанию (что я считаю правильным), я согласен с тем, что у флага есть свои варианты использования (в том числе указанные @networkimprov) и что было бы полезно в состоянии включить его, не прибегая к вызовам CreateFileW и копированию стандартного кода.

Пожалуйста, рассмотрите альтернативу [1] выше, которая позволяет разработчикам при необходимости устанавливать FILE_SHARE_DELETE и предотвращать разветвление фрагментов кода стандартной библиотеки Go.

__Rust__ также устанавливает этот флаг по умолчанию и позволяет вам _выключать_ любую из опций FILE_SHARE_*.
https://doc.rust-lang.org/std/os/windows/fs/trait.OpenOptionsExt.html

@rsc, могу я спросить, какие из технических аргументов, выдвинутых против предложения, не были опровергнуты?

@networkimprov после прочтения всего предложения и обсуждения, я думаю, вам нужно закрыть это и повторно открыть новую проблему, явно касающуюся добавления нового флага, специфичного для Windows, в пакет ossyscall. Это гарантировало бы, что новое поведение является согласием, а не отказом. Текущее предложение подразумевает изменение существующего кода, что беспокоит людей.

@rsc, могу я спросить, какие из технических аргументов, выдвинутых против предложения, не были опровергнуты?

Извините, но процесс подачи перенос Windows (а именно,

Я подал #34681, чтобы предоставить более простой способ открывать файлы с помощью FILE_SHARE_DELETE в (все еще вероятном) случае, когда этот файл будет отклонен. Казалось более полезным начать новую ветку, ограниченную этой идеей, чем продолжать эту, которая стала очень длинной.

@rsc , прежде чем вы отклоните это, давайте послушаем, что Алекс думает о вашем предложении O_ALLOW_DELETE.

В начале этого обсуждения он был против нового флага os.OpenFile(), который устанавливает file_share_delete, по тем же причинам, по которым он не согласился с вашим предложением os.Create/Open/OpenFile(). Он обеспокоен тем, что другие программы предполагают, что никто никогда не открывает файлы таким образом, потому что MSVC fopen() не может этого сделать. Таким образом, эти (еще неуказанные) программы будут ломать программы Go, которые устанавливают флаг.

Алекс, можно зациклить попытку переименования, если доступ извне разрешен,...

Если вы готовы зациклить переименование, вам не нужно ничего менять в коде репозитория Go. Ваша программа будет работать так же хорошо, как сейчас.

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

Я думаю, что копирование кода в отдельный пакет — это нормально. При исследовании https://github.com/moby/moby/pull/39974 у меня возникла та же идея. Я не думаю, что там много кода, который нужно поддерживать. На самом деле я реализовал именно это

https://github.com/alexbrainman/goissue34681

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

@rsc , прежде чем вы отклоните это, давайте послушаем, что Алекс думает о вашем предложении O_ALLOW_DELETE.

Пожалуйста, попробуй

https://github.com/alexbrainman/goissue34681

первый. Если вы по какой-то причине не удовлетворены, мы можем обсудить добавление новых функций в репозиторий Go.

Спасибо.

Алекс

Я действительно разочарован этим ответом.

Копирование этого большого куска кода, который в значительной степени даже не понят (как
в почему это делает то, что он делает) просто так мы можем добавить один вариант
что поддерживает сама система, только что Go, скорее всего, я предполагаю, что нет
даже преднамеренно, не дает возможности передать опцию даже
syscall.Open кажется смешной ситуацией.

Почему я должен переписать syscall.Open, чтобы передать эту опцию?

В субботу, 5 октября 2019 г., в 18:43 Алекс Брейнман, [email protected] написал:

Алекс, можно зациклить попытку переименования, если доступ извне разрешен,...

Если вы готовы зациклить переименование, вам не нужно ничего менять
в коде репозитория Go. Ваша программа будет работать так же хорошо, как сейчас.

На данный момент единственное решение — буквально скопировать кучу кода из
Стандартная библиотека Go, измените одну строку, а затем сохраните эту вилку навсегда.

Я думаю, что копирование кода в отдельный пакет — это нормально. Во время расследования
moby/moby#39974 https://github.com/moby/moby/pull/39974 у меня то же самое
идея. Я не думаю, что там много кода, который нужно поддерживать. На самом деле я
реализовано именно это

https://github.com/alexbrainman/goissue34681

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

@rsc https://github.com/rsc , прежде чем отклонить это, давайте послушаем, что
Алекс думает о вашем предложении O_ALLOW_DELETE.

Пожалуйста, попробуй

https://github.com/alexbrainman/goissue34681

первый. Если вы не удовлетворены по какой-либо причине, мы могли бы обсудить добавление новых
функциональность для репозитория Go.

Спасибо.

Алекс


Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/golang/go/issues/32088?email_source=notifications&email_token=AAGDCZXHULQEMHAPTO6ZUJTQNFGE7A5CNFSM4HNPNYIKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEAN7QIQ#issuecomment-5388
или заглушить тему
https://github.com/notifications/unsubscribe-auth/AAGDCZWYUNMCTAGO567AV73QNFGE7ANCNFSM4HNPNYIA
.

>

  • Брайан Гофф

Я действительно разочарован этим ответом.

Мне жаль, что ты так себя чувствуешь. Но вы действительно пытались использовать мой пакет?

Алекс

@rsc , поскольку Алекс также выступает против флага os.OpenFile(), мы должны ничего не делать?

Как насчет того, чтобы поместить эту функцию за флагом сборки?

Что касается того, что «Go работал нормально в Windows в течение почти 10 лет», это определенно не так, если вам нужно было переименовать открытый файл. (Он также был сломан на ноутбуках с Windows 8/10 в течение последних 7 лет.)

У меня есть собственный форк os.OpenFile и syscall.Open уже в moby/moby.

Почему мы здесь такие пренебрежительные?

Во вторник, 8 октября 2019 г., в 01:47 Алекс Брейнман, [email protected] написал:

Я действительно разочарован этим ответом.

Мне жаль, что ты так себя чувствуешь. Но вы действительно пытались использовать мой пакет?

Алекс


Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/golang/go/issues/32088?email_source=notifications&email_token=AAGDCZRV72GY4IJQJVJWMYTQNRCIHA5CNFSM4HNPNYIKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEATNATA#issuecomment-5394
или заглушить тему
https://github.com/notifications/unsubscribe-auth/AAGDCZRNYSMIE6BX77XJVWDQNRCIHANCNFSM4HNPNYIA
.

>

  • Брайан Гофф

Я предлагаю отклонить это конкретное предложение. Мы не собираемся представлять кучу ошибок безопасности TOCTOU для пользователей Windows, которые полагались на существующее поведение os.OpenFile.

Более важный вопрос здесь заключается в следующем: «Как мы можем предоставить пользователям Go множество интересных флагов для функций Windows CreateFile и NtCreateFile ?» Я ожидаю, что мы сможем рассмотреть их в разных предложениях, но, конечно же, не включив их все по умолчанию, как здесь предлагается.

@zx2c4 ты читал всю

Прошла неделя с https://github.com/golang/go/issues/32088#issuecomment -537590826, и до сих пор нет четкого консенсуса, а это значит, что мы должны отклонить это.

Отклоненный.

Я разместил сводку вариантов с плюсами и минусами в https://github.com/golang/go/issues/34681#issuecomment -565853605.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги