C system calls open / read / write / close and O_CREAT|O_EXCL - c

Given the following code (it's supposed to write "helloworld" in a "helloworld" file, and then read the text):
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#define FNAME "helloworld"
int main(){
int filedes, nbytes;
char buf[128];
/* Creates a file */
if((filedes=open(FNAME, O_CREAT | O_EXCL | O_WRONLY | O_APPEND,
S_IRUSR | S_IWUSR)) == -1){
write(2, "Error1\n", 7);
}
/* Writes hello world to file */
if(write(filedes, FNAME, 10) != 10)
write(2, "Error2\n", 7);
/* Close file */
close(filedes);
if((filedes = open(FNAME, O_RDONLY))==-1)
write(2, "Error3\n", 7);
/* Prints file contents on screen */
if((nbytes=read(filedes, buf, 128)) == -1)
write(2, "Error4\n", 7);
if(write(1, buf, nbytes) != nbytes)
write(2, "Error5\n", 7);
/* Close file after read */
close(filedes);
return (0);
}
The first time I run the program, the output is:
helloworld
After that every time I to run the program, the output is:
Error1
Error2
helloworld
I don't understand why the text isn't appended, as I've specified the O_APPEND file.
Is it because I've included O_CREAT?
It the file is already created, shouldn't O_CREAT be ignored?

O_EXCL forces the file to be created. If the file already exists, the call fails.
It is used to ensure that the file has to be created, with the given permissions passed in the third parameter. In short, you have these options:
O_CREAT: Create the file with the given permissions if the file doesn't already exist. If the file exists, it is opened and permissions are ignored.
O_CREAT | O_EXCL: Create the file with the given permissions if the file doesn't already exist. If the file exists, it fails. This is useful in order to create lockfiles and guarantee exclusive access to the file (as long as all programs which use that file follow the same protocol).
O_CREAT | O_TRUNC: Create the file with the given permissions if the file doesn't already exist. Otherwise, truncate the file to zero bytes. This has more of the effect we expect when we think "create a new blank file". Still, it keeps the permissions already present in the existing file.
More information from the manual page:
O_EXCL
When used with O_CREAT, if the file
already exists it is an error and
the open() will fail. In this context,
a symbolic link exists, regardless of
where it points to. O_EXCL is broken
on NFS file systems; programs which
rely on it for performing locking
tasks will contain a race condition.
The solution for performing atomic
file locking using a lockfile is to
create a unique file on the same file
system (e.g., incorporating hostname
and pid), use link(2) to make a link
to the lockfile. If link() returns 0,
the lock is successful. Otherwise, use
stat(2) on the unique file to check if
its link count has increased to 2, in
which case the lock is also
successful.

Related

writing files at a low level

I was reading the GNU C PROGRAMMING TUTORIAL online, and get some confusion on the code example for low level read & write.
The code is as below:
#include <stdio.h>
#include <fcntl.h>
int main()
{
char my_write_str[] = "1234567890";
char my_read_str[100];
char my_filename[] = "snazzyjazz.txt";
int my_file_descriptor, close_err;
/* Open the file. Clobber it if it exists. */
my_file_descriptor = open (my_filename, O_RDWR | O_CREAT | O_TRUNC);
/* Write 10 bytes of data and make sure it's written */
write (my_file_descriptor, (void *) my_write_str, 10);
fsync (my_file_descriptor);
/* Seek the beginning of the file */
lseek (my_file_descriptor, 0, SEEK_SET);
/* Read 10 bytes of data */
read (my_file_descriptor, (void *) my_read_str, 10);
/* Terminate the data we've read with a null character */
my_read_str[10] = '\0';
printf ("String read = %s.\n", my_read_str);
close (my_file_descriptor);
return 0;
}
I compiled the code with gcc without issue. And run the first time, it is also ok. Output as below:
$ ./lowLevelWrite
String read = 1234567890.
The problem comes when i run the program second time:
$ ./lowLevelWrite
String read = .
Seems the code fails to write the string "1234567890" to the file second time. As we know from the GNU C manual, O_RDWR | O_CREAT | O_TRUNC these flag should allow us to truncate the file to 0 every time and then write to the file. I am not sure why it fails from second time execution.
Can anybody help me out of this confusion?
When you're creating a file with open() you need to pass a third argument, the permission modes:
my_file_descriptor = open (my_filename, O_RDWR | O_CREAT | O_TRUNC, 0664);
0664 is the permissions rw-rw-r--: readable and writable by the owner and group, readable by everyone else. These permissions will be further masked by your umask.
Since you didn't pass this argument, open() used random stack garbage, and this probably didn't include write permission. So you couldn't open the file for writing when it already exists.

C: Executing and outputing shell commands in C

Aside from using popen() (as was discussed in this question) is this a valid way of doing it ?
Say we had a program who's name is hexdump_dup and wanted the program to output the exact output of the hexdump command.
#include <fcntl.h>
#include <unistd.h>
int main(void)
{
int fd;
fd = open("hexdump_dup", O_CREAT | O_TRUNC | O_WRONLY, 0755); // (line 8)
write(fd, "/usr/bin/hexdump $#;", 20); // (line 9)
close(fd);
return (0);
}
Also could someone briefly explain what line 8 and 9 do, and how afterwards the command gets executed ? Like when, where does it say to execute the command or what makes the command execute ?
After this
fd = open("hexdump_dup", O_CREAT | O_TRUNC | O_WRONLY, 0755); // (line 8)
write(fd, "/usr/bin/hexdump $#;", 20);
you need to execute hexdump_dup executable, for that you need to use either system() or exec() family function. For e.g
system("./hexdump_dup 1 2 3"); /* after creating binary file(hexdump_dup) & writing command into it, you need to run it, for that use system() or exec() */
This
fd = open("hexdump_dup", O_CREAT | O_TRUNC | O_WRONLY, 0755);
will create the hexdump_dup binary if it doesn't exist before & if exists before it will truncate its content to 0. You can refer the man page of open() , it says
int open(const char *pathname, int flags, mode_t mode);
The argument flags must include one of the following access
modes: O_RDONLY, O_WRONLY, or O_RDWR. These request opening
the file read-only, write-only, or read/write, respectively.
O_CREAT
If the file does not exist it will be created. The
owner (user ID) of the file is set to the effective
user ID of the process.
O_TRUNC
If the file already exists and is a regular file and
the open mode allows writing (i.e., is O_RDWR or
O_WRONLY) it will be truncated to length 0. If the
file is a FIFO or terminal device file, the O_TRUNC
flag is ignored.
Lastly this
write(fd, "/usr/bin/hexdump $#;", 20);
writes 20 bytes containing array of characters /usr/bin/hexdump $#; in this case into a file where fd points i.e it will put this into hexdump_dup file.
Here $# means when you execute hexdump_dup like
./hexdump_dup 1 2 3
it will take all the parameters to be passed.

C open() function fails. Are my parameters wrong?

This function fails to open the file. Are my parameters wrong or what could be causing this problem?
int CreateFile(const char *filename){
char filepath[strlen(filename) + 3];
sprintf(filepath, "./%s", filename);
int fd = open(filepath, O_CREAT, O_APPEND, S_IWGRP);
if(fd == -1) printf("file read failed\n");
return fd;
}
Xcode prints only "file read failed" to the console. I tried to run this via Terminal aswell but that didn't help either.
I fixed an issue pointed by NetMage:
int CreateFile(const char *filename){
char filepath[strlen(filename) + 3];
sprintf(filepath, "./%s", filename);
int fd = open(filepath, O_CREAT|O_APPEND, S_IWGRP);
if(fd == -1) printf("file read failed\n");
return fd;
}
Unfortunately that didn't fix the issue
Step 1 - Verify that filepath is being set correctly, either by printing it to the terminal or examining it in a debugger.
Step 2 - Verify that the file exists in that path, and that its permissions are set so that you can open it. If filepath is "./foo", then a file named foo had better exist in the current working directory (the directory from which you ran the program), and it needs to have at least read permission.
Step 3 - If the file does not exist, verify that you have permission to create new files in the current working directory.
Step 4 - If after doing all of that you still get an error, check errno. It will give you some additional information beyond "it didn't work."
#include <errno.h>
...
if(fd == -1)
{
switch( errno )
{
case EACCESS: // permission issues
handle_permission_issue();
break;
case EEXIST: // file already exists and you used O_CREAT and O_EXCL
handle_already_exists_issue();
break;
case EFAULT: // bad path
handle_bad_path_issue()
break;
...
}
printf("file read failed\n");
}
NetMage has pointed out one problem - your flags need to be bitwise-OR'd together, rather than listed as separate arguments. Surprised the compiler didn't yell at you over that.
The open function takes only one parameter for oflags, which must be bit-ored together:
#include <errno.h>
#include <string.h>
int fd = open(filepath, O_CREAT|O_APPEND, S_IWGRP);
if (fd == -1) printf("file read failed: %s\n", strerror(errno));
Per the POSIX documentation for open() (somewhat reformatted, and note the bolded text):
SYNOPSIS
#include <sys/stat.h> #include <fcntl.h>
int open(const char *path, int oflag, ...);
...
Values for oflag are constructed by a bitwise-inclusive OR of flags
from the following list, defined in . Applications shall
specify exactly one of the first five values (file access modes)
below in the value of oflag:
O_EXEC
Open for execute only (non-directory files). The result is unspecified if this flag is applied to a directory.
O_RDONLY
Open for reading only.
O_RDWR
Open for reading and writing. The result is undefined if this flag is applied to a FIFO.
O_SEARCH
Open directory for search only. The result is unspecified if this flag is applied to a non-directory file.
O_WRONLY
Open for writing only.
...
You need to include at least one of those five flags, perhaps like:
int fd = open(filepath, O_WRONLY|O_CREAT|O_APPEND, S_IWGRP);
Note that other failures may still occur. As noted in the comments, you're prepending "./" to the file name, which may cause problems if, for example, you get passed "/tmp/filename" and the tmp directory doesn't exist in your current working directory, as open() will not create missing directories in any path.

C on linux How to check if file exists and create new if it does not exist - in symlink tolerant fashion

I need to write c code on linux that will check if
1) a file exists and
2) if it does not exist then I need to open the file and write into the file.
Being a multi thread / multi process environment, I want to ensure that 1 and two above happen in one atomic operation
I looked at What's the best way to check if a file exists in C? (cross platform) and considered solution suggested by #Dan Lenski. So please dont mark this as duplicate without reading completely . The problem with that is the combination of O_CREAT | O_WRONLY | O_EXCL flags in an open system call as mentioned in open man page does not tolerate symbolic links. Sounds like it has problems with NFS partitions as well
We do use symbolic links in our paths ( Debatable - why but we do end up using )
so I want to do something akin Dan's suggested and cited in his code fragment reproduced below but what can tolerate a path that points to a symbolic link
#include <fcntl.h>
#include <errno.h>
fd = open(pathname, O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR);
if (fd < 0) {
/* failure */
if (errno == EEXIST) {
/* the file already existed */
...
}
} else {
/* now you can use the file */
}

C: creating named pipe using mknod() not working

Language: C
OS: Ubuntu
I'm simply trying to create a FIFO named pipe using the command:
state = mknod("pipe.txt", S_IFIFO | 0666, 0);
the problem is i always get the state's value to be -1 (meaning it has failed) instead of 0.
perror returns 'pipe.txt: File exists'
i have no idea how should i debug such issue or what could be the reason, hope anyone code guide me what's wrong.
(note: the file pipe.txt exist on same path as source file.)
Read: int mknod(const char *path, mode_t mode, rdev_t dev_identifier);
General Description:
Creates a new character special file or FIFO special file (named pipe), with the path name specified in the path argument.
If file already exists then it will fails with error: File exists
To avoid this error, remove(unlink()) the file, As I am doing in my below code(read comment):
int main() {
char* file="pipe.txt";
unlink(file); // Add before mknod()
int state = mknod(file, S_IFIFO | 0666, 0);
if(state < 0){
perror("mknod() error");
}
return 0;
}
You should examine errno to see what the error is but it's probably EEXIST since I believe that's what happens if the file already exists.
From the Linux documentation for mknod:
If pathname already exists, or is a symbolic link, this call fails with an EEXIST error.
However, if the file already exists and is the pipe you created in an earlier run, you can safely reopen it. All mknod (and its often preferred cousin, mkfifo) does is actually create the FIFO, you still have to open it at both ends to get the data transfer happening.

Resources