I have a program which opens a file and checks its length.
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);
}
Now, at least under Linux, fopen()
returns a valid file descriptor when opening a directory. This results in the seek operation returning -1
(or, as size_t
is unsigned, 0xFFFFFFFFFFFFFFFF
=264-1 on a 64-bit system).
Unfortunately, the condition in the above code (flen == ((size_t)-1)
) does not catch that case, neither does flen == 0xFFFFFFFF
(EDIT: should be 0xFFFFFFFFFFFFFFFF
). printf()
-Commands with %x
ord %d
as format string show that both sides of the comparison should have the same value.
Why does the comparison operator behave in such a strange way, even when both sides are of the same type (size_t
)? I am using gcc 4.8.1 as compiler.
The fopen can't be used to create directories. This is because fopen function doesn't create or open folders, it only works with files. The above code creates a path to the file named 'filename'. The directory of the 'filename' is obtained using the 'dirname' function.
To access a directory, use the opendir() function. It's prototyped in the dirent. h header file as: DIR *opendir(const char *filename);
The key difference between the fopen() and the open() function in the Linux operating system is that the open() function is a low-level call, where the fopen() when called simply calls the open() function in the background and it returns a Filepointer directly.
The DIR data type represents a directory stream. You shouldn't ever allocate objects of the struct dirent or DIR data types, since the directory access functions do that for you. Instead, you refer to these objects using the pointers returned by the following functions. Directory streams are a high-level interface.
From 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.
At least on Linux, if you try to fopen("dirname", "wb")
you get an EISDIR error. I also tried with a directory with d-------- access rights, and I still get EISDIR (and not EACCES.)
Directories do not exist in the C99 standard (or the C2011 one). So by definition, fopen
-ing a directory is either implementation specific or undefined behavior.
fopen(3) can fail (giving a NULL
result). fseek(3) can also fail (by returning -1). And then you should preferably check errno(3) or use perror(3)
ftell
is documented to return a long
, and -1L
on failure. On 64 bits Linux this is 0xffffffffffffffff
.
You code should be instead
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); };
BTW, On Linux/Debian/Sid/AMD64 with libc-2.17 and 3.10.6 kernel, that codes runs ok when argv[1]
is /tmp
; surprizingly, flen
is LONG_MAX
i.e. 0x7fffffffffffffff
BTW, on Linux, directories are a special case of files. Use stat(2) on a file path (and fstat
on a file descriptor, perhaps obtained with fileno(3) from some FILE*
) to know more meta data about some file, including its "type" (thru its mode). You want opendir(3), readdir(3) & closedir(3) to operate on directory contents. See also inode(7).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With