LINUX.ORG.RU

readdir vs readdir_r на древних системах

 ,


0

2

Насколько у меня отложилось в памяти, на каких-то древних системах readdir() возвращал данные в статическом буфере, общем на весь процесс, то есть код вида

{
  DIR *dp1, *dp2;
  struct dirent *a, *b;
  //....
  a = readdir(dp1);
  printf("name1 = %s\n", a->de_name);
  b = readdir(dp2);
  printf("name2 = %s\n", b->de_name);
  printf("name1 = %s\n", a->de_name);
  //....
Мог запороть a->de_name после второго вызова. Это сказывается на возможности рекурсивно обходить файловые деревья (надо сохранять de_name директории куда-то в другое место если оно нужно после старта её вложенного обхода). Функция readdir_r предлагалась как удобное решение этой проблемы.

Кто-нить помнит о каких системах речь и где об этом почитать? Или это всё чисто теория и в реальности таких систем не было? Или я вообще всё это выдумал и нигде такого не упоминалось?

★★★★★

Последнее исправление: firkax (всего исправлений: 1)

В POSIX написано, что readdir() function need not be reentrant. A function that is not required to be reentrant is not required to be thread-safe.

В glibc readdir_r() помечен как deprecated; причины следующие:

• On systems where NAME_MAX is undefined, calling readdir_r() may be unsafe because the interface does not allow the caller to specify the length of the buffer used for the returned directory entry.

• On some systems, readdir_r() can't read directory entries with very long names. When the glibc implementation encounters such a name, readdir_r() fails with the error ENAMETOOLONG after the final directory entry has been read. On some other systems, readdir_r() may return a success status, but the returned d_name field may not be null terminated or may be truncated.

Говорят, «используйте readdir() вместо readdir_r() — он всё равно у нас потокобезопасный, ну и мы вангуем, что когда-нибудь в POSIX тоже объявят readdir() потокобезопасным».

Всем не угодишь. Типичный юниксово-позиксный майндфак, как с strerror_r() и PATH_MAX.

shdown
()
Ответ на: комментарий от shdown

Про текущую ситуацию я в курсе, проблемы readdir решены, а у readdir_r обнаружилась другая и неисправимая. Речь именно про древние системы (думаю 90-е или раньше, но это не точно).

И речь не про потокобезопасность (это другая тема), в приведённом примере мультитреда нет.

firkax ★★★★★
() автор топика
Ответ на: комментарий от shdown

Речь про вот это

(freebsd)

The readdir() function returns a pointer to the next directory entry.
The directory entry remains valid until the next call to readdir() or closedir() on the same directory stream.

(glibc)

The data returned by readdir() may be overwritten by subsequent calls to readdir() for the same directory stream.

В openbsd мане кстати ничего похожего я не нашёл, но скорее всего там также т.к. иначе депрекейтить readdir_r было бы глупостью.

firkax ★★★★★
() автор топика
Ответ на: комментарий от shdown

Что именно запрещает? Портить dirent от другой директории? Или наоборот рассчитывать что он не испортится?

Ну и надо учитывать что древние ОС были без учёта современного POSIX-а.

firkax ★★★★★
() автор топика
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от shdown

А, понял. Не, POSIX это запрещает:

https://pubs.opengroup.org/onlinepubs/007904875/functions/readdir.html

The pointer returned by readdir() points to data which may be overwritten by another call to readdir() on the same directory stream. This data is not overwritten by another call to readdir() on a different directory stream.

shdown
()

Мог запороть a->de_name после второго вызова.

Так в линуксе и сейчас же так, нет?

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

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

i-rinat ★★★★★
()

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

си не предназначен для юзерспейс кода в современном мире

lovesan ★★★
()

Или я вообще всё это выдумал и нигде такого не упоминалось?

Это.

Результат readdir привязан к struct dirent * и перетирает его.

Функция readdir_r предлагалась как удобное решение этой проблемы.

Нет, она предлагалась в рамках общих попыток заменить все статические данные в libc на аллоцируемые пользователем и таким образом сделать полностью потокобезопасное API. Не шмогли.

anonymous
()
Ответ на: комментарий от firkax

Кого перетирает? Тип?

Результат. Результат перетирает.

а какую-то область памяти.

Результат - данные в структуре в области памяти инциализированной определёнными значениями, доступными через struct dirent * и валидными до следующего вызова. Перетирается ли область памяти или нет - это тридесятое дело. Важен инвариант API. Он таков, что результат вызова (в любой форме) валиден до следующего вызова, и после повторного вызова в отношении данных, доступных через ранее возвращённый struct dirent * никаких гарантий нет.

anonymous
()