Check if input file is a valid file in C - 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...

Related

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.

How to detect if a C filestream points to a file or a serial device?

I'm working on a C application that evaluates data from a USB laser scanner, which acts as a serial device. For testing purpose, I'm also allowing test data to be read from a file, because it is not convenient to always have the scanner connected.
I open the file/device like this:
FILE *fp = fopen(argv[1], "a+b");
And depending on whether I want to read from a file or the device, I pass a file path or something like /dev/cu.usbmodemfd121 (I'm on a Mac).
This works fine as long as I've previously initialized the laser scanner, but I'd rather have my application do that. In order to do that, though, I must first figure out if I'm reading from a file or the device. How can I do that, given the FILE * returned by fopen?
I've tried to use fseek(fp, 1, SEEK_END) which I expected to fail for the scanner, since it's stream doesn't have an "end", but for some reasons fseek does not fail..
You could get the file descriptor using fileno and then do a fstat on it. The struct stat it populates contains thinks like st_mode which shows the type of fd. I am guessing for your non-file device S_ISCHR will be true or at least S_ISREG will be false.
If you have control over it, don't do fopen at all. Use open directly to get the file descriptor and then use fdopen if you really want C streams.
#cnicutar's solution worked just fine. Here's what I ended up with, in case it helps somebody (error checking removed for clarity):
#include <fcntl.h> /* open syscall */
#include <sys/stat.h>
int fd = -1;
int status;
FILE *fp = NULL;
struct stat fd_stat;
bool serial_device = false;
fd = open("/foo/bar/baz", O_RDONLY);
fp = fdopen(fd, "rb");
status = fstat(fd, &fd_stat);
printf("S_ISCHR: %d\n", S_ISCHR(fd_stat.st_mode));
printf("S_ISREG %d\n", S_ISREG(fd_stat.st_mode));
if(!S_ISREG(fd_stat.st_mode)) {
serial_device = true;
}

Linux/ Open directory as a file

I've been reading Brian Kernighan and Dennis Ritchie - The C Programming Language and chapter 8.6 is about directory listing under UNIX OS. They say that everything and even directory is a file. This means that I should be able to open directory as a file? I've tried it using stdio functions and it didn't work. Now, I'm trying it with UNIX system functions. Of course, I'm not using UNIX, I'm using Ubuntu linux. Here is my code:
#include <syscall.h>
#include <fcntl.h>
int main(int argn, char* argv[]) {
int fd;
if (argn!=1) fd=open(argv[1],O_RDONLY,0);
else fd=open(".",O_RDONLY,0);
if (fd==-1) return -1;
char buf[1024];
int n;
while ((n=read(fd,buf,1024))>0)
write(1,buf,n);
close (fd);
return 0;
}
This writes nothing even when argn is 1 (no parameters) and I'm trying to read current directory.
Any ideas/explanations? :)
Files are also called regular files to distinguish them from special files.
Directory or not a regular file. The most common special file is the directory. The layout of a directory file is defined by the filesystem used.
So use opendir to open diretory.
Nachiket's answer is correct (as indeed is sujin) but they don't clear up the mystery as to why open works and not read. Out of curiosity I made some changes to the given code to find out exactly what was going on.
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
int main(int argc, char* argv[]) {
int fd = -1;
if (argc!=1) fd=open(argv[1],O_RDONLY,0);
else fd=open(".",O_RDONLY,0);
if (fd < 0){
perror("file open");
printf("error on open = %d", errno);
return -1;
}
printf("file descriptor is %d\n", fd);
char buf[1024];
int n;
if ((n=read(fd,buf,1024))>0){
write(1,buf,n);
}
else {
printf("n = %d\n", n);
if (n < 0) {
printf("read failure %d\n", errno);
perror("cannot read");
}
}
close (fd);
return 0;
}
The result of compiling and running this:
file descriptor is 3
n = -1
read failure 21
cannot read: Is a directory
That settles it, though I'd have expected open to fail, since the correct system function for opening directories is opendir().
Though everything in unix is a file (directory also) but still filetype is concept is present in unix and applicable to all files.
there are file types like regular file,directory etc and certain operations and functions are allowed/present for every file type.
In your case readdir is applicable for reading contents of directory.
If you want to see the files in a directory you have to use the opendir and readdir functions.
K&R were correct for the original UNIX. I remember doing it back when UNIX file systems had a 14 character length limit for filenames. The opendir(), readdir(), ... stuff happened about the time that longer file names became common (around 1990?)

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.

How can I use Linux's splice() function to copy a file to another file?

here's another question about splice(). I'm hoping to use it to copy files, and am trying to use two splice calls joined by a pipe like the example on splice's Wikipedia page. I wrote a simple test case which only tries to read the first 32K bytes from one file and write them to another:
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
int main(int argc, char **argv) {
int pipefd[2];
int result;
FILE *in_file;
FILE *out_file;
result = pipe(pipefd);
in_file = fopen(argv[1], "rb");
out_file = fopen(argv[2], "wb");
result = splice(fileno(in_file), 0, pipefd[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
printf("%d\n", result);
result = splice(pipefd[0], NULL, fileno(out_file), 0, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
printf("%d\n", result);
if (result == -1)
printf("%d - %s\n", errno, strerror(errno));
close(pipefd[0]);
close(pipefd[1]);
fclose(in_file);
fclose(out_file);
return 0;
}
When I run this, the input file seems to be read properly, but the second splice call fails with EINVAL. Anybody know what I'm doing wrong here?
Thanks!
From the splice manpage:
EINVAL Target file system doesn't support splicing; target file is
opened in append mode; neither of the descriptors refers to a
pipe; or offset given for non-seekable device.
We know one of the descriptors is a pipe, and the file's not open in append mode. We also know no offset is given (0 is equivalent to NULL - did you mean to pass in a pointer to a zero offset?), so that's not the problem. Therefore, the filesystem you're using doesn't support splicing to files.
What kind of file system(s) are you copying to/from?
Your example runs on my system when both files are on ext3 but fails when I use an external drive (I forget offhand if it is DOS or NTFS). My guess is that one or both of your files are on a file system that splice does not support.
The splice(2) system call is for copying between files and pipes and not between files, so it can not be used to copy between files, as has been pointed out by the other answers.
As of Linux 4.5 however a new copy_file_range(2) system call is available that can copy between files. In the case of NFS it can even cause server side copying.
The linked man page contains a full example program.

Resources