При изучении этого вопроса я столкнулся с тем, что в POSIX (и Linux) просто нет системного вызова truncateat
.
Некоторые системные вызовы, например, unlink
имеют эквивалентный альтернативный метод с добавленным суффиксом at
в конце их имен, то есть unlinkat
.Разница между этими методами заключается в том, что варианты с суффиксом at
принимают дополнительный аргумент, дескриптор файла, указывающий на каталог.Поэтому относительный путь, переданный в unlinkat
, не относится к текущему рабочему каталогу, а относится к предоставленному дескриптору файла (открытый каталог).Это действительно полезно при определенных обстоятельствах.
Глядя на truncate
, рядом с ним находится только ftruncate
.truncate
работает с путями - абсолютными или относительно текущего рабочего каталога.ftruncate
напрямую работает с дескриптором открытого файла - без указания пути.truncateat
.
Многие библиотеки (различные "альтернативные" C-библиотеки) делают то, что я делал, и имитируют tuncateat
, используя openat
- ftruncate
- close
-последовательность.Это работает, в большинстве случаев, за исключением ...
Я столкнулся со следующей проблемой.Мне понадобились месяцы, чтобы понять, что происходит.Проверено на Linux, различных ядрах 3.X и 4.X.Представьте себе два процесса (не потоки):
Теперь представьте следующую последовательность событий (псевдокод)):
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')
не решает эту проблему.