Open a file only if not a directory - c

On Linux, open(filename, O_RDONLY) appears to succeed if given the name of the directory instead of a regular file (though subsequent read() calls appear to fail, which is only to be expected).
What's the best way to check if you are trying to open, or have just opened, a directory, for the purpose of failing with 'oops, wrong filename' instead of 'panic, we have a file but read isn't working'?

Call fstat() on the file descriptor and check the mode of the file:
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
// ...
int fd = open(filename, O_RDONLY)
if (fd == -1) {
// open() failed.
}
struct stat buf;
if (fstat(fd, &buf) != 0) {
// fstat() failed.
}
if (S_ISDIR(buf.st_mode)) {
// It's a directory.
}
This is all portable POSIX code.
Note that you could use stat() on filename before calling open(). But this can lead to the situation of the file changing between the stat() and open() calls, meaning you would open() a different file then the one you examined with stat(). Using fstat() provides resilience against that, since file descriptors will still point to the correct file even if it gets deleted.

Related

reopen a directory using openat

As it seems, it is possible to use openat() to reopen an already opened directory. For instance on my Linux system I can do the following:
#define _GNU_SOURCE
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main(void) {
int fd1 = open(".", O_PATH);
if (fd1 == -1) {
perror("open");
return 1;
}
int fd2 = openat(fd1, ".", O_RDONLY);
if (fd2 == -1) {
perror("openat");
close(fd1);
return 1;
}
close(fd1);
// do fancy things with fd2, now opened
// with access mode read-only
return 0;
}
I could not find this documented anywhere and it feels a bit like an edge case. Also I didn't find other code doing this. Is this well-defined behavior?
EDIT: changed the title: file -> directory
This is just the same as calling open twice on the same file, which you are allowed to do:
int fd1 = open("filename", flags1);
int fd2 = open("filename", flags2);
where filename refers to an existing file (of any type) and flags1 and flags2 are any set of O_ flags that can be validly applied to that type of file and won't destroy its contents. (In particular, we assume that they do not include O_CREAT, O_TRUNC, or O_EXCL.)
fd1 and fd2 will refer to separate "open file descriptions", so for instance lseek on one will not affect the other, flock on one will block flock on the other, etc.
With openat(), the first argument, fd, should be the file descriptor for a directory — such as the one you obtained from opening "." — or the special value AT_FDCWD (which means open relative paths relative the current directory). Note that the O_PATH option you use is a Linux-only extension to openat().
So, because you're using a valid file descriptor for a directory, the call to openat() should succeed. You now have two file descriptors both pointing (independently — with separate open file descriptions) to the current directory. In general, it is possible to open the same file multiple times in a single process (or in multiple processes — ensuring access by a single process is actually very hard on Unix-like (POSIX) systems).
There isn't a lot else you can do with those descriptors other than use them in *at() system calls. Either of the file descriptors would have been sufficient; opening both was overkill.

Duplicating file descriptor and seeking through both of them independently

I have an open file descriptor which I want to duplicate in order to perform reading and seeking through both of them independently. I looked at the
int dup(int old_fd)
syscall. The problem is it does not really fit here. Man page states the following
http://man7.org/linux/man-pages/man2/dup.2.html :
After a successful return, the old and new file descriptors
may be used interchangeably. They refer to the same
open file description (see open(2)) and thus share file
offset and file status flags; for example, if the file offset is
modified by using lseek(2) on one of the file descriptors,
the offset is also changed for the other.
Is there a way to duplicate a file descriptor so they are completely independent?
In Linux, opening /proc/<pid>/fd/<n> opens the file that's currently open at fd N, but this is a new copy, not a linked duplicate like the one you get with dup() and friends.
This should create a file that contains bar, a bunch of zero bytes, then foo. Contrast with the version using dup().
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(void)
{
int fd1, fd2;
char buffer[50];
fd1 = open("testfile", O_CREAT | O_TRUNC | O_RDWR, 0600);
sprintf(buffer, "/proc/self/fd/%d", fd1);
#ifndef USE_DUP
fd2 = open(buffer, O_RDWR);
if (fd2 == -1) {
perror("open");
}
#else
fd2 = dup(fd1);
#endif
if (lseek(fd1, 16, SEEK_SET) == -1) {
perror("lseek");
}
if (write(fd1, "foo", 3) == -1) {
perror("write(fd1)");
}
if (write(fd2, "bar", 3) == -1) {
perror("write(fd2)");
}
}
No — at least, not in POSIX-defined mechanisms.
If you want complete independence of the file descriptors, you need to avoid the shared open file description, which means an independent open() or equivalent.
There's a chance that there's a Linux-specific mechanism that does the job that I've not heard of. However, looking through the system calls for Linux at http://man7.org/linux/man-pages/man2/ didn't provide enlightenment.

How to tell if FILE* is referring to a directory?

I just discovered that a FILE* can not only refer to a regular file, but also to a directory. If the latter is the case, fread will fail with errno set to 21 (Is a directory).
Minimal repro can be tested here
#include <stdio.h>
#include <fcntl.h>
#include <assert.h>
#include <errno.h>
int main() {
char const* sz = ".";
int fd = open(sz, O_RDONLY | O_NOFOLLOW); // all cleanup omitted for brevity
FILE* file = fdopen(fd, "rb");
// I would like to test in this line if it is a directory
char buffer[21];
int const n = fread(buffer, 1, 20, file);
if (0 < n) {
buffer[n] = 0;
printf(buffer);
} else {
printf("Error %d", errno); // 21 = Is a directory
}
}
What is the proper way to detect early that my FILE* is referring to directory without trying to read from it?
EDIT to repel the duplicate flags:
I want to test on the FILE*, not the filename. Testing on filename only and then opening it later is a race condition.
Assuming a POSIX-like environment, if you have just the file stream (FILE *fp), then you are probably reduced to using fileno() and fstat():
#include <sys/stat.h>
struct stat sb;
if (fstat(fileno(fp), &sb) != 0)
…oops…
if (S_ISDIR(sb.st_mode))
…it is a directory…
else
…it is not a directory…
Assuming you are on a POSIX-based system, use stat() (if you wish to use the filename in sz before the call to open()) or fstat() (if you wish to use the descriptor fd after calling open()) to get a file status structure from the OS. The member of the structure named st_mode can be used with the POSIX API S_ISDIR(st_mode) to see if the file is a directory.
For more information, see: http://man7.org/linux/man-pages/man2/stat.2.html
Checking The fcntl.h man page:
header shall define the following symbolic constants as
file creation flags for use in the oflag value to open() and
openat(). The values shall be bitwise-distinct and shall be suitable
for use in #if preprocessing directives.
And the flag :
O_DIRECTORY Fail if not a directory.

Check if input file is a valid file in C

I am trying to open a file in c using open() and I need to check that the file is a regular file (it can't be a directory or a block file). Every time I run open() my returned file discriptor is 3 - even when I don't enter a valid filename!
Here's what I have
/*
* Checks to see if the given filename is
* a valid file
*/
int isValidFile(char *filename) {
// We assume argv[1] is a filename to open
int fd;
fd = open(filename,O_RDWR|O_CREAT,0644);
printf("fd = %d\n", fd);
/* fopen returns 0, the NULL pointer, on failure */
}
Can anyone tell me how to validate input files?
Thanks!
Try this:
int file_isreg(const char *path) {
struct stat st;
if (stat(path, &st) < 0)
return -1;
return S_ISREG(st.st_mode);
}
This code will return 1 if regular, 0 if not, -1 on error (with errno set).
If you want to check the file via its file descriptor returned by open(2), then try:
int fd_isreg(int fd) {
struct stat st;
if (fstat(fd, &st) < 0)
return -1;
return S_ISREG(st.st_mode);
}
You can find more examples here, (specifically in the path.c file).
You should also include the following headers in your code (as stated on stat(2) manual page):
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
For future reference, here is an excerpt of the stat(2) manpage regarding the POSIX macros available for st_mode field validations:
S_ISREG(m) is it a regular file?
S_ISDIR(m) directory?
S_ISCHR(m) character device?
S_ISBLK(m) block device?
S_ISFIFO(m) FIFO (named pipe)?
S_ISLNK(m) symbolic link? (Not in POSIX.1-1996.)
S_ISSOCK(m) socket? (Not in POSIX.1-1996.)
int isValidFile(char *filename) {
// We assume argv[1] is a filename to open
int fd;
fd = open(filename,O_RDWR|***O_CREAT***,0644);
printf("fd = %d\n", fd);
/* fopen returns 0, the NULL pointer, on failure */
}
you are using 0_CREAT which prompts the function to create if the file doesn't exist.this in the table its number is 3 (0,1,2 being std input std output and std error)
Wrong: check if the file is OK, then if it is, go open it and use it.
Right: go open it. If you can't, report the problem and bail out. Otherwise, use it (checking and reporting errors after each opetation).
Why: you have just checked that a file is OK. That's fine, but you cannot assume it will be OK in 0.000000017 seconds from now. Perhaps the disk wil overheat and break down. Perhaps some other process will mass-delete your entire file collection. Perhaps your cat will trip over the network cable. So let's just check if it's OK again, and then go open it. Wow, what a great idea! No wait...

open a windows file directory for reading/writing in c

I'm trying to write the contents of a windows directory to a file using c. For example, if I had a directory of jpegs (i.e. a directory that contains multiple jpegs) and wanted to convert them to a .raw file, I have something like this:
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
typedef uint8_t BYTE;
#define BLOCK 512*sizeof(BYTE);
int main(void)
{
FILE * fd = fopen("C:\\jpegs", "r");
if (fd == NULL) {
fprintf(stderr, "Error opening device file.\n");
return EXIT_FAILURE;
}
int block = BLOCK;
FILE * fn = fopen("new.raw", "w+");
void * buff = malloc(block);
while(feof(fd) == 0) {
fread(buff,block,1,fd);
fwrite(buff,block,1,fn);
}
free(buff);
fclose(fd);
fclose(fn);
return 0;
}
The problem is I don't think windows directories are terminated with EOF. Does anyone have any ideas about how to solve this?
On Unix systems, although you can open a directory for reading, you can't really read from it unless you use the opendir(), readdir(), closedir() family of calls. You can't write to a directory on Unix; even superuser (root) can't do that. (The main reason for opening a directory, more usually with open() than fopen(), is so that you can use chdir() followed by fchdir() to get back to where you started, or use the various *at() functions, such as openat(), to reference the directory.)
On Windows, you'd at minimum need to use "rb" mode, but frankly, I'd not expect you to be able to do much with it. There are probably analogues to the Unix opendir() functions in the Windows API, and you should use those instead.

Resources