Обычный C: открытие каталога с помощью fopen()
У меня есть программа, которая открывает файл и проверяет его длину.
FILE* fd = fopen(argv[1], "rb");
fseek(fd, 0, SEEK_END);
size_t flen = ftell(fd);
if (flen == ((size_t)-1)) {
printf("%s is a directory.\n", argv[1]);
fclose(fd);
exit(1);
}
Теперь, по крайней мере, под Linux, fopen()
возвращает действительный дескриптор файла при открытии каталога. Это приводит к возвращению операции поиска -1
(или, как size_t
без знака, 0xFFFFFFFFFFFFFFFF
= 264-1 в 64-битной системе).
К сожалению, условие в приведенном выше коде (flen == ((size_t)-1)
) не поймает тот случай, и не делает flen == 0xFFFFFFFF
(РЕДАКТИРОВАТЬ: должно быть 0xFFFFFFFFFFFFFFFF
). printf()
-Команды с %x
ога %d
в качестве строки формата показано, что обе стороны сравнения должны иметь одинаковое значение.
Почему оператор сравнения ведет себя так странно, даже когда обе стороны имеют одинаковый тип (size_t
)? Я использую GCC 4.8.1 в качестве компилятора.
2 ответа
Каталоги не существуют в стандарте C99 (или в C2011). Итак, по определению, fopen
-ing каталог является специфическим для реализации или неопределенным поведением.
fopen (3) может потерпеть неудачу (давая NULL
результат). fseek (3) также может потерпеть неудачу (возвращая -1). И тогда вам лучше проверить errno (3) или использовать perror (3)
ftell
задокументировано, чтобы вернуть long
, а также -1L
на провал. На 64-битном Linux это 0xffffffffffffffff
,
Ваш код должен быть вместо
FILE* fd = fopen(argv[1], "rb");
if (!fd)
{ perror(argv[1]); exit(EXIT_FAILURE); };
if (fseek(fd, 0, SEEK_END)<0)
{ perror("fseek"); exit(EXIT_FAILURE); };
long flen = ftell(fd);
if (flen == -1L)
{ perror("ftell"); exit(EXIT_FAILURE); };
Кстати, в Linux/Debian/Sid/AMD64 с ядром libc-2.17 и 3.10.6 эти коды работают нормально, когда argv[1]
является /tmp
; surprizingly, flen
является LONG_MAX
т.е. 0x7fffffffffffffff
Кстати, в Linux каталоги - это особый случай файлов. Используйте stat(2) для пути к файлу (и fstat
в файловом дескрипторе, возможно, полученном с помощью fileno (3) из некоторых FILE*
) узнать больше метаданных о каком-либо файле, включая его "тип" (через его режим). Вы хотите, чтобы opendir (3), readdir (3) и closedir (3) работали с содержимым каталога. Смотрите также inode (7).
С http://pubs.opengroup.org/onlinepubs/7908799/xsh/fopen.html:
The fopen() function will fail if:
[EISDIR] The named file is a directory and mode requires write access.
По крайней мере, на Linux, если вы пытаетесь fopen("dirname", "wb")
Вы получаете ошибку EISDIR. Я также попытался с каталогом с d-------- правами доступа, и я все еще получаю EISDIR (а не EACCES.)