What is the C equivalent of Perl's file test operators? - c

Perl has a list of file test operators for checking if a file is readable -r, writable -w, or executable -x to the effective user of the process (http://perldoc.perl.org/functions/-X.html). How do I do this in C?
if (-w $file) {
open FH, "+<", $file or die "$!\n";
}
else {
open FH, "<", $file or die "$!\n";
}
I know libc has a function called access(), but that is for the real user of the process and only useful for setuid programs.

On POSIX systems, use the stat function, and then use the S_XXX macros to test the flags, e.g.:
struct stat st;
if (stat(file, &st) == 0 && (st.st_mode & S_IWUSR)) {
// file exists, and is writable by "user"
...
}
I'm not aware of any "standard" C function to perform the same writability test, other than to actually attempt to open the file and check the resulting error if the file isn't writable.
POSIX does also have access() specifically for testing readability and writability, but I mentioned stat() first because that's the specific function that Perl's tests attempt to emulate (in most cases).

I can't use stat() because attempting to mimic file access is too complicated and will never be portable.
I can't use access() because if I run the program under sudo, I might get back that I can't write a file, when I definitely can.
The only option left is to use open():
int fd = open(file, O_RDWR);
if (fd == -1) {
fd = open(file, O_RDONLY);
}
if (fd == -1) {
perror("open");
}

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.

Using stat() after fopen() to Avoid TOCTOU Problems?

Title says it all: can one use stat() after fopen() to avoid Time of Check to Time of Use (TOCTOU) race conditions?
Some details:
I am writing a C program that only reads files, but needs to error properly when asked to read a directory. As of right now, it uses open() (with O_RDWR) to generate an error and then checks errno for EISDIR, like so:
int fd = open(path, O_RDWR);
if (fd == -1) {
if (errno == EISDIR) return PATH_IS_DIR;
else return FILE_ERR;
}
The problem with the above solution is that this program only needs to read files, so by opening a file with O_RDWR, I might wrongly get a permissions error if the user has read permissions, but not write permissions.
Is it possible to do the following to avoid TOCTOU race conditions?
struct stat pstat;
FILE *f = fopen(path, "r");
if (!f) return FILE_ERR;
if (stat(path, &pstat) == -1) {
fclose(f);
return FILE_ERR;
}
if (S_ISDIR(pstat.st_mode)) {
fclose(f);
return PATH_IS_DIR;
}
If it is not possible, is there another solution to prevent TOCTOU bugs and also wrong permission errors?
No, the code presented in the question does not avoid a TOCTOU race.
Testing after use is prone to exactly the same errors as testing before use. In both cases, the name is resolved at two different times, with possibly different results. This is the cause of the race, and it can happen whichever access happens first.
The only way to avoid this is to open the file once, and use the file descriptor so obtained for any other checks you need. Modern OSes provide interfaces such as fstat() for exactly this purpose.
If you want to use C's buffered I/O, you can get the file descriptor from a FILE* using fileno() - or you can create a FILE* from a file descriptor using fdopen().
It requires a very small change to your code:
# Untested
struct stat pstat;
FILE *f = fopen(path, "r");
if (!f) return FILE_ERR;
if (fstat(fileno(f), &pstat) == -1) {
// ^^^^^^^^^^^^^^^ <-- CHANGED HERE
fclose(f);
return FILE_ERR;
}
if (S_ISDIR(pstat.st_mode)) {
fclose(f);
return PATH_IS_DIR;
}
EDIT (2018-10-25): Toby Speight's answer is better.
There is a solution: use open(), then
fstat().
An example:
struct stat pstat;
int fd = open(path, O_RDONLY);
if (fd == -1) return FILE_ERR;
if (fstat(fd, &pstat) == -1) {
close(fd);
return FILE_ERR;
}
if (S_ISDIR(pstat.st_mode)) {
close(fd);
return PATH_IS_DIR;
}
I found this while checking that I had covered all of my bases before asking this question.

Best practices for determining binary regular file size in POSIX compliant C program

I need to determine the file size in bytes of binary regular files under POSIX. I'm aware of how to use this with lseek() and fstat():
#include <sys/stat.h> // for open() and fstat()
#include <fcntl.h> // for O_RDONLY
#include <unistd.h> // for lseek()
int fd = open("something.bin", O_RDONLY);
if (fd == -1)
{
perror("Unable to open file to read");
return EXIT_FAILURE;
}
// Using lseek()
const off_t size = lseek(fd, 0, SEEK_END);
if (size == (off_t) -1)
{
perror("Unable to determine input file size");
return EXIT_FAILURE;
}
// Don't forget to rewind
if (lseek(fd, 0, SEEK_SET) != 0)
{
perror("Unable to seek to beginning of input file");
return EXIT_FAILURE;
}
...
// Using fstat()
struct stat file_stat;
int rc = fstat(fd, &file_stat);
if (rc != 0 || S_ISREG(file_stat.st_mod) == 0)
{
perror("fstat failed or file is not a regular file");
return EXIT_FAILURE;
}
const off_t size = file_stat.st_size;
Why would I prefer one solution over the other?
Does one approach do more (and perhaps unnecessary) than the other?
Are there other POSIX compliant or standard C solutions that should be preferred?
Normally stat(), fstat() will read the metadata of the file to retrieve the file properties for the user. Mechanism to store metadata of files may vary from file system to file system but in general designed to give optimum speed/time complexity.
'file size' is one of the file properties stored in metadata and is updated at various file operations (e.g. write/append etc). Further fstat() doesn't require you to 'open()' the file.
On the other hand, Every 'open()' and 'lseek()' operations together could involve disk activity if the file is not present in the page cache of the operating system and could be exponentially more expensive.
Therefore I would recommend fstat().
I recommend using stat(2) or fstat(2) to get the size of a regular file (and in my opinion, the definition of a file size is what stat tells in .st_size field).
Some regular files are not that regular. For example /proc/self/status or /proc/self/maps on a Linux system (read proc(5)), even if stat or ls tells that they are "regular files". See this.
On such /proc/ pseudofiles, there is no simple way to get their "real" size (because stat(2) is telling 0).
However, I believe that the file size is, almost by definition, what stat(2) tells you (and the fact that it is "lying" on /proc/ is IMHO a deficiency of the /proc/ file system; actually /proc/self/maps behave nearly like a pipe(7) read end, not like a regular file).
Think also of the weird cases where another process is changing a file (e.g. write(2)-ing or ftruncate(2)-ing it). Then doing several syscalls might not work very well.
At last, stat is the simplest (and often the fastest) way to get a file size. So why bother using something else?

problems with the open() function in different file permissions

I am implementing the virtual disk system in C, which includes handling the file system as well. I just want to know, why the open function in C returns -1 when I try to open a file with some group permission or otherspermissions.
Lets say we have file mode that is 040 (Read permission bit for the group owner of the file):
int main(){
int filedes;
filedes = open(filename, O_RDWR, 040);
if(filedes < 0)
return -1;
printf("Open success\n");
}
This snippet return without printing open success. Where this code with file mode 0644 works perfectly fine
int main(){
int filedes;
filedes = open(filename, O_RDWR, 0644);
if(filedes < 0)
return -1;
printf("Open success\n");
}
I don't understand why is this happening?
040 specifically disallows the owner of said file to do anything with it. Even though your group can, you've explicitly defined that your own user can't use it. It may seem weird, but OS only does what you've told it to do.

How to set output file mode permissions (rw-rw-rw-)

I've created a code that should be able to copy a file a user suggests. What I am wondering is this: how do I set the output file mode and how do I determine what the output file mode permissions will be in this code?
#include <stdio.h>
#include <stdlib.h>
int main()
{
char c;
char source_file, target_file;
FILE *in, *out;
printf("Enter name of file to copy\n");
gets(source_file);
printf("Enter name of file to copy to\n");
gets(target_file);
in = (source, O_RDONLY);
out = (target_file, O_CREAT|WRONLY, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
/* error handing */
if( in == NULL )
{
printf("Error. \n");
exit(0);
}
printf("Enter the copied file name \n");
gets(target_file);
out = fopen(target_file, "w");
/*error handing*/
if( out == NULL )
{
fclose(in);
printf("File opening error.\n");
exit(0);
}
while(( c = fgetc(in) ) != EOF )
fputc(c,out);
fclose(in);
fclose(out);
return 0;
}
Controlling file permissions using standard I/O
One of the demerits of the standard I/O library is that you can't control the permissions on the files that are created, primarily because such permissions are rather platform-specific (more so than the C standard allows for, anyway). The POSIX open() function allows you to control the permissions on the file as it is created.
With a POSIX-like system, you can use the chmod() or fchmod() system calls. You need to know that your rw-rw-rw- pattern is octal 0666.
chmod(target_file, 0666);
fchmod(fileno(out), 0666);
The functions can fail; you should check that they don't.
You can also use the umask() function or (with care) the umask command to influence the default permissions. For example, setting umask 022 in the shell means that files will not be created that are writable by group or others.
Revising the modified code
You don't need to worry about the permissions on a file you open for reading (or, at least, you seldom need to do so).
Worrying about the permissions on the file you write to is more normal.
Your current code proposal is:
in = (source, O_RDONLY);
out = (target_file, O_CREAT|WRONLY, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
This does not invoke open(), and assigns an integer value to the two FILE * variables, which should be generating compiler warnings. Note that the comma expressions evaluate the LHS and then the RHS of the expression, yielding the RHS as the overall value. O_RDONLY is classically 0; the combination of S_IRUSR etc terms is not zero.
If you're going to open the file with those options, then you need something like:
int fd_i = open(source_file, O_RDONLY);
if (fd_i < 0)
…report error opening source_file…
FILE *in = fdopen(fd_i, "r");
if (in == 0)
…report error creating file stream for source_file…
int fd_o = open(target_file, O_CREAT|WRONLY, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
if (fd_o < 0)
…report error opening target_file…
FILE *out = fdopen(fd_o, "w");
if (out == 0)
…report error creating file stream for target_file…
However, I would probably not use fdopen() for the input file — I'd use fopen() directly as you did originally — but I might use it for the output file.

Resources