9

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.

  • Don't you mean `sizeof(size_t)-1` ? – Scotty Bauer Aug 12 '13 at 17:36
  • @ScottyBauer No, he doesn't. –  Aug 12 '13 at 17:39
  • 7
    Also, `FILE fd` shall be `FILE *fd`. And `ftell()` doesn't return `size_t` but `long`. And -1 is not `0xFFFFFFFF` on 64-bit (assuming 2's complement), but `0xFFFFFFFFFFFFFFFF`. –  Aug 12 '13 at 17:40
  • @H2CO3 I am not familiar with that statement, what does it mean? Specifically the `flen == ((size_t)-1)` – Scotty Bauer Aug 12 '13 at 17:47
  • 1
    @ScottyBauer Google "C type casting". –  Aug 12 '13 at 18:03
  • 2
    You can `open()` a directory (and hence `fopen()` it), but that's about all you can do with it. The intent is to allow you to use `fchdir()` on the file descriptor. For reading the directory, you need to use `opendir()`, `readdir()`, etc. For finding its size, you could use `fstat(fileno(fd))` — though using the name `fd` for a `FILE *` instead of a file descriptor strikes me as rotten naming (I'd use `FILE *fp = fopen("file", "r"); int fd = fileno(fp);`). – Jonathan Leffler Aug 12 '13 at 20:35

2 Answers2

9

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.)

MJ93
  • 4,966
  • 8
  • 32
  • 50
user3689693
  • 91
  • 1
  • 1
8

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).

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547