open with O_CREAT - was it opened or created? - c

I have 10 processes which try open the same file more or less at the same time using open(O_CREAT) call, then delete it. Is there any robust way to find out which process actually did create the file and which did open already create file, for instance, if I want to accurately count how many times that file was opened in such scenario.
I guess I could put a global mutex on file open operation, and do a sequence of open() calls using O_CREAT and O_EXCL flags, but that doesn't fit my definition of "robust".

Use O_EXCL flag with O_CREAT. This will fail if the file exists and errno will be set to EEXIST. If it does fail
then attempt open again without O_CREAT and without O_EXCL modes.
e.g.
int fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0644);
if ((fd == -1) && (EEXIST == errno))
{
/* open the existing file with write flag */
fd = open(path, O_WRONLY);
}

Based roughly on your comments, you want something along the lines of this function:
/* return the fd or negative on error (check errno);
how is 1 if created, or 0 if opened */
int create_or_open (const char *path, int create_flags, int open_flags,
int *how) {
int fd;
create_flags |= (O_CREAT|O_EXCL);
open_flags &= ~(O_CREAT|O_EXCL);
for (;;) {
*how = 1;
fd = open(path, create_flags);
if (fd >= 0) break;
if (errno != EEXIST) break;
*how = 0;
fd = open(path, open_flags);
if (fd >= 0) break;
if (errno != ENOENT) break;
}
return fd;
}
This solution is not bullet proof. There may be cases (symbolic links maybe?) that would cause it to loop forever. Also, it may live-lock in certain concurrency scenarios. I'll leave resolving such issues as an exercise. :-)
In your edited question, you pose:
I have 10 processes which try open the same file more or less at the same time using open(O_CREAT) call, then delete it.
A hack-ish, but more bullet proof, solution would be to give each process a different user ID. Then, just use the regular open(path, O_CREAT|...) call. You can then query the file with fstat() on the file descriptor, and check the st_uid field of the stat structure. If the field equals the processes' user ID, then it was the creator. Otherwise, it was an opener. This works since each process deletes the file after opening.

Related

Creating a file atomically

I came across this piece of code and it is not supposed to work according to the author, however, it runs successfully. The author recommends the use of O_CREAT which does the same thing but guarantees atomicity. In my opinion whether two processes are running concurrently or not, the code should still work?
if((fd=open("filename.dat", O_WRONLY) < 0)){
if(errno != ENOENT){
perror("open error");
exit(1);
}
else if((fd=open("filename.dat", O_WRONLY | O_CREAT)) < 0){
perror("creation error");
exit(1);
}
}
Atomicity is provided by the operating system: either the file exists, or it doesn't, however, between the calls to e.g. access() to check for existence and open() to create the file, another process may have created the file. So atomically creating a file must be done in one call:
if ((fd=open("filename.dat", O_WRONLY | O_CREAT | O_EXCL, mode))<0) {
// file exists or other error
O_EXCL results in the call failing when the file exists.
mode is a parameter required with O_CREAT to specify the file's access/sharing permission. The flags differ between Linux and Windows.

Read and Write operations get bad fd and no such file

I have this C program.
I have two processes, father and son, and use semaphores to make them synchronize one at time.
The father has to write (n) numbers, ten in this case, always in the first byte of the opened file and the son has to read it.
The problem is that when I print the results, I get bad file descriptor for the write (father) and no such file for the read(the son).
Can you help me, please?? Thank you
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#define FILENAME "test.txt"
#define MUTEX "/mutex"
#define READ "/read"
#define WRITE "/write"
int main(int argc, char *argv[]){
int i, pid, n=10, fd, x;
int nread, nwrite;
char c = 'a';
sem_t *mutex, *reader, *writer;
//fd = open(FILENAME, O_CREAT | O_TRUNC, 0666);
mutex = sem_open(MUTEX, O_CREAT, 0666, 1);
reader = sem_open(READ, O_CREAT, 0666, 0);
writer = sem_open(WRITE, O_CREAT, 0666, 1);
pid = fork();
fd = open(FILENAME, O_CREAT | O_TRUNC, 0777);
if(fd < 0){
perror("Open FILE error");
exit(-1);}
if(pid == 0){ // son
do{
sem_wait(reader); // si può leggere???
sem_wait(mutex);
lseek(fd, 0, SEEK_SET);
nread = read(fd, &x, sizeof(int));
if(nread <=0)
perror("Read error");
printf("Son has read (%d byte) = %d\n", nread, x);
fflush(NULL);
sem_post(mutex);
sem_post(writer);
}
while(x != (n-1));
exit(0);
}
else{
for(i=0; i<n; i++){
sem_wait(writer); // can I write??
sem_wait(mutex);
lseek(fd, 0, SEEK_SET);
nwrite = write(fd, &c, sizeof(char));
if(nwrite <= 0)
perror("nwrite error");
printf("Father has written (%d byte) %d\n", nwrite, i);
fflush(NULL);
sem_post(mutex);
sem_post(reader); // it's possible to read
}
//wait(NULL);
}
sem_unlink(MUTEX);
sem_unlink(READ);
sem_unlink(WRITE);
//remove(FILENAME);
exit(0);
}
First, you opened the file without specifying an o_flag. That's actually undefined behavior ("Applications shall specify exactly one of .... O_RDONLY .... O_WRONLY .... O_RDWR"), but for practical purposes means the file was opened read only.
Thus the parent's write operation fails with EBADF. Can't write to a read only file!
Second, the child's error checking is incorrect. read() may return zero on success, in which case errno, consulted by perror(), is not guaranteed to be meaningful. You mean to check for a return value of less than zero, not of less than or equal to zero.
Your open() call is opening the file for read only. You have:
fd = open(FILENAME, O_CREAT | O_TRUNC, 0777);
Because you don't explicitly say O_WRONLY or O_RDWR, and because the traditional value for O_RDONLY is 0, you are effectively opening the file read-only.
The 0777 permissions are suspect too. You are not creating an executable; you should not be giving the file executable permissions. In my book, you probably shouldn't be giving others write permission on the file. In fact, I'd probably go with 0600 permissions.
Your program is a bit strange.
First off, you are asking for trouble by having parent and child processes race to be the one to create the target file. It would be better for the parent to create and open the file before forking, and for (only) the child to open it after. Be aware that in that case the child should first close (its copy of) the file descriptor opened by the parent. Alternatively, the way you're doing things, if the parent opened the file then it would probably be sufficient for the child to just use the file descriptor it inherits, without opening the file itself at all.
With that said, it would be more usual for parent and child processes to communicate via a pipe than via a physical file. That has the particular advantage that you do not need to synchronize access via a semaphore; ordinary blocking I/O does the job.
Additionally, I don't see what your mutex semaphore is doing for you. Even with a design that requires you to manually synchronize writing and reading, it looks like your reader and writer semaphores will serve that purpose without help.
Importantly, your parent process is writing in sizeof(char)-byte uints, whereas your child process is trying to read in sizeof(int)-byte units. This is unlikely to have the result you want.
Furthermore, the read() and write() functions may return successfully without having transferred the full number of bytes requested (unless that number is 1 and the file is open in blocking mode). You need to account for that by being prepared to use multiple I/O operations to transfer multi-byte data, if necessary.
Finally, it would be best for just one process to unlink your semaphores. It is ok for that to happen while the other process is still running.

Call to open() to create a file fails if the file exists

I'm trying to create a C code which will create a file for which I can read or write from/to. This file can already exist, or need to be created from scratch. If it already exists within the directory, I want it to created from scratch, in other words delete all the contents.
FD = open("p.txt", O_RDWR | O_CREAT | O_TRUNC);
I've tried using that for the time being. I encounter a problem though. If the file doesn't exist, it creates it and returns a positive file descriptor.
If the file however already exists, a -1 FD is returned. So I must be missing a flag?
I assumed O_TRUNC would be enough to clear the contents of a file?
FD = open("p.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);
When a Unix call returns -1, check the value of the errno variable. It contains the reason for the error. Don't speculate as to what might be the problem until you've seen the error code. You can call strerror or perror to get a message describing the numerical value stored in errno.
Also, as others have noted, when you pass O_CREAT to open, you must pass a third argument which determines the file's permission if it's created. (If you don't )
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
int main() {
int fd = open("p.txt", O_RDWR | O_CREAT | O_TRUNC, 0666);
if (fd == -1) {
perror("opening p.txt");
exit(1);
}
/* … */
}

create and open returning two different fd descriptors

if((fd = creat(file_name,O_RDWR|S_IRWXU|S_IRWXG|S_IRWXO)) < 0){
perror("Create failed!");
return -1;
}
if((fd = open(file_name,O_CREAT | O_RDWR))< 0){
perror("Open failed!");
return -1;
}
write(fd,buff,100);
Why do the fd's are different for creat and open, as its opening the same file.
I want to create , open and write a file.
I should not use the open(filename,O_CREAT|modes);
The creat function opens the file after creating it, so from your description, simply calling creat() is sufficient for your needs.
To answer your question, it gives you multiple file descriptors because you've performed multiple opens (creat is an open with creation). You can access the same file using both descriptors (and your current location within the file can differ for each descriptor).
Literally creat() function is equivalent to open(pathname, O_RWONLY | O_CREAT | O_TRUNCATE, mode) so your second open() is redundant.
You should also keep in mind that the fd is just a handler of your current process. Different handler may point to the same file as we call it "File Sharing".
Another suggestion is, try
if (write(fd, buff, 100) != 100)
/*Error handling codes*/
to make sure the write() works correctly.

Open new device descriptor with same options as reference descriptor

I have an open device descriptor, where I don't know the device name and the options
passed to open(...).
I want to open a new device descriptor with the same options passed to open.
int newFd = copy(referenceFd);
Where copy would do the job. dup() is certainly the wrong choice as a further ioctl() on newFd would also alter the referenceFd, therefore I want to open a new descriptor.
Is there a system call which provides such functionality?
I have not been able to find something yet.
You can probably do it with a series of fcntl calls:
F_GETFD - Get the file descriptor flags defined in that are associated with the file descriptor fildes.
F_GETFL - Get the file status flags and file access modes, defined in , for the file description associated with fildes.
I linked the SUSv4 page above; you might also be interested in the Linux version.
First, get the descriptor flags of file descriptor fd using
int flags = fcntl(fd, F_GETFL);
Then, reopen the descriptor using the Linux-specific /proc/self/fd/fd entry:
int newfd;
char buffer[32];
if (snprintf(buffer, sizeof buffer, "/proc/self/fd/%d", fd) < sizeof buffer) {
do {
newfd = open(buffer, flags & ~(O_TRUNC | O_EXCL), 0666);
} while (newfd == -1 && errno == EINTR);
if (newfd == -1) {
/* Error: Cannot reopen file. Error in errno. */
}
} else {
/* Error: the path does not fit in the buffer. */
}
The reopened file descriptor is then in newfd.
Note that you most likely want to make sure O_TRUNC (truncate the file) and O_EXCL (fail if exists) are not in the flags, to avoid the cases where reopening using the exact original flags would cause unwanted results.
You do not want to use lstat(), because that opens up a race condition with respect to rename -- a user renaming the target file between the lstat() and the open() in above code. The above avoids that completely.
If you don't want to be Linux-specific, add code that tries the same with /dev/fd/fd if the above fails. It is supported in some Unix systems (and most Linux distros too).

Resources