Как реализовать что-то похожее на «truncateat»? - PullRequest
0 голосов
/ 19 апреля 2019

При изучении этого вопроса я столкнулся с тем, что в POSIX (и Linux) просто нет системного вызова truncateat.

Некоторые системные вызовы, например, unlink имеют эквивалентный альтернативный метод с добавленным суффиксом at в конце их имен, то есть unlinkat.Разница между этими методами заключается в том, что варианты с суффиксом at принимают дополнительный аргумент, дескриптор файла, указывающий на каталог.Поэтому относительный путь, переданный в unlinkat, не относится к текущему рабочему каталогу, а относится к предоставленному дескриптору файла (открытый каталог).Это действительно полезно при определенных обстоятельствах.

Глядя на truncate, рядом с ним находится только ftruncate.truncate работает с путями - абсолютными или относительно текущего рабочего каталога.ftruncate напрямую работает с дескриптором открытого файла - без указания пути.truncateat.

Многие библиотеки (различные "альтернативные" C-библиотеки) делают то, что я делал, и имитируют tuncateat, используя openat - ftruncate - close -последовательность.Это работает, в большинстве случаев, за исключением ...

Я столкнулся со следующей проблемой.Мне понадобились месяцы, чтобы понять, что происходит.Проверено на Linux, различных ядрах 3.X и 4.X.Представьте себе два процесса (не потоки):

  • Процесс "A"
  • Процесс "B"

Теперь представьте следующую последовательность событий (псевдокод)):

A: fd = open(path = 'filename', mode = write)
A: ftruncate(fd, 100)
A: write(fd, 'abc')
B: truncate('filename', 200)
A: write(fd, 'def')
A: close(fd)

Вышеописанное работает просто отлично.Сразу после того, как процесс «А» открыл файл, установил его размер 100 и записал в него что-то, процесс «В» заново устанавливает его размер на 200. Затем процесс «А» продолжается.В конце файл имеет размер 200 и в его начале содержится «abcdef», за которым следуют ноль байтов.

Теперь давайте попробуем имитировать что-то вроде truncateat:

A: fd_a = open(path = 'filename', mode = write)
A: ftruncate(fd_a, 100)
A: write(fd_a, 'abc')
B: fd_b = openat(dirfd = X, path = 'filename', mode = write | truncate)
B: ftruncate(fd_b, 200)
B: close(fd_b)
A: write(fd_a, 'def')
A: close(fd_a)

Мой файл имеет длину 200, хорошо.Он начинается с трех нулевых байтов, не в порядке, затем «def», затем снова с нулевыми байтами.Я только что потерял первую запись из процесса «A», в то время как «def» технически приземлился в правильном положении (три байта, как если бы я позвонил seek(fd_a, 3) перед записью).

Я могу работатьс первой последовательностью операций просто отлично.Но в моем случае использования я не могу полагаться на пути относительно текущего рабочего каталога в том, что касается процесса "B".Я действительно хочу работать с путями относительно файлового дескриптора.Как этого добиться - не сталкиваясь с проблемой, продемонстрированной во второй последовательности операций?Вызов fsync из процесса "A" сразу после write(fd_a, 'abc') не решает эту проблему.

1 Ответ

1 голос
/ 19 апреля 2019

Причина, по которой ваш второй случай перезаписывает все с нулями, заключается в том, что mode = truncate (т.е. openat(.., O_TRUNC)) сначала усекает файл до длины 0.

Если вместо этого вместо ftruncate до 200 сразу, без первогос усечением до 0 существующие данные до этой точки останутся нетронутыми.

...