I found this code on the web which is able to lock a file and it works.
/*
** lockdemo.c -- shows off your system's file locking. Rated R.
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
/* l_type l_whence l_start l_len l_pid */
struct flock fl = { F_WRLCK, SEEK_SET, 0, 0, 0 };
int fd;
fl.l_pid = getpid();
if (argc > 1)
fl.l_type = F_RDLCK;
if ((fd = open("lockdemo.c", O_RDWR)) == -1) {
perror("open");
exit(1);
}
printf("Press <RETURN> to try to get lock: ");
getchar();
printf("Trying to get lock...");
if (fcntl(fd, F_SETLKW, &fl) == -1) {
perror("fcntl");
exit(1);
}
printf("got lock\n");
printf("Press <RETURN> to release lock: ");
getchar();
fl.l_type = F_UNLCK; /* set to unlock same region */
if (fcntl(fd, F_SETLK, &fl) == -1) {
perror("fcntl");
exit(1);
}
printf("Unlocked.\n");
close(fd);
}
For my use case it's not enough.
While the file is locked, I am able to append data to lockdemo.c and I'd like to prevent that.
How can I do?
Regards
You can't.
Unix file locking is strictly "advisory," meaning that it only interacts with itself. Any process can open, read, write, truncate, etc. a locked file and as long as it doesn't call one of the locking functions it won't even notice that the lock exists.
This was an intentional design decision, made many years ago -- people can and do argue about whether it was the right design decision, but it's not going to be changed now.
If you tell us more about your larger problem, we might be able to suggest alternative approaches.
Related
My English is poor so you may get confused from my description below.
In Linux, multiple processes were requesting a file lock (flock or fcntl lock), then the previous exclusive file lock was released. I think which process can gain the lock is random (not specified).
But every time I try, it always seems like in FIFO (like the following photo). (And I have already tried many times).
I want to figure out is something wrong with my code or anything else?
#include <sys/file.h>
#include <fcntl.h>
#include <string.h>
#include "tlpi_hdr.h"
char *currTime(const char *format);
int main(int argc, char *argv[])
{
int fd;
struct flock fl;
fd = open("./file", O_RDWR); /* Open file to be locked */
if (fd == -1)
errExit("open");
fl.l_len = 0;
fl.l_start = 0;
fl.l_whence = SEEK_SET;
fl.l_type = F_WRLCK;
if (fcntl(fd, F_SETLKW, &fl) == -1)
{
if (errno == EAGAIN || errno == EACCES)
printf("already locked");
else if (errno == EDEADLK)
printf("dead lock");
else
errExit("fcntl");
}
else
printf("PID %ld: have got write lock at %s\n", (long)getpid(), currTime("%T"));
sleep(atoi(argv[1]));
exit(EXIT_SUCCESS); // close fd and this cause unlock flock's lock
}
I used a FIFO for a simple read/write programme where the input from user is written to standard output by the writer function. The question is however, am I able to run this program without creating a child process (with the fork() operation). From what I see from examples about FIFOs, most read/write programmes with a named pipe/FIFO are done with 2 files - one for reading and one for writing. Could I do these all in a file?
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
/* read from user */
void reader(char *namedpipe) {
char c;
int fd;
while (1) {
/* Read from keyboard */
c = getchar();
fd = open(namedpipe, O_WRONLY);
write(fd, &c, 1);
fflush(stdout);
}
}
/* writes to screen */
void writer(char *namedpipe) {
char c;
int fd;
while (1) {
fd = open(namedpipe, O_RDONLY);
read(fd, &c, 1);
putchar(c);
}
}
int main(int argc, char *argv[]) {
int child,res;
if (access("my_fifo", F_OK) == -1) {
res = mkfifo("my_fifo", 0777);
if (res < 0) {
return errno;
}
}
child = fork();
if (child == -1)
return errno;
if (child == 0) {
reader("my_fifo");
}
else {
writer("my_fifo");
}
return 0;
}
You'll need to put a lock on the file, or else you could attempt to be reading when someone else is writing. You'll also want to flush the write buffer, or your changes to the fifo might actually not be recorded until the kernel write buffer fills and then writes to the file (in linux, write doesn't guarantee a write happens at that exact moment. i see you're flushing stdout, but you should also fsync on the file descriptor. This will cause the file to lock during any write operation so that no one else can write. In order to lock the file for reading, you might have to use a semaphore.
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#define __USE_GNU
#include <sched.h>
void init_lock(struct flock *f)
{
f->l_type = F_WRLCK; /* write lock set */
f->l_whence = SEEK_SET;
f->l_start = 0;
f->l_len = 0;
f->l_pid = getpid();
}
int lock(int fd, struct flock *f)
{
init_lock(f);
if(fcntl(fd, F_SETLKW, f) == -1) {
fprintf(stderr,"fcntl() failed: %s\n", strerror(errno));
return -1;
}
return 0;
}
int unlock(int fd, struct flock *f)
{
f->l_type = F_UNLCK;
if(fcntl(fd, F_SETLK, f) == -1) {
fprintf(stderr, "fcntl() failed: %s\n", strerror(errno));
return -1;
}
return 0;
}
int file_op(void *arg)
{
char buff[256];
int fd = (int) arg, n;
struct flock my_lock;
printf("Trying to get lock\n");
if(lock(fd, &my_lock) == -1) { /* lock acquired by a thread */
return -1;
}
printf("Got lock: %d\n", getpid()); /* I am printing thread id after lock() */
printf("Enter string to write in file : ");
scanf("%s", buff);
if((n=write(fd, &buff, strlen(buff))) == -1) {
fprintf(stderr, "write() failed: %s\n", strerror(errno));
}
if(unlock(fd, &my_lock) == -1) {
return -1;
}
printf("Lock Released: %d\n", getpid());
return 0;
}
int main()
{
char *stack;
int fd, i=0, cid, stacksize;
if((fd = open("sample.txt", O_CREAT | O_WRONLY | O_APPEND, 0644)) == -1) {
printf("Error in file opening\n");
exit(1);
}
stacksize = 3*1024*1024;
for(i=0; i<5; i++) {
stack = malloc(stacksize);
if((cid = clone(&file_op, stack + stacksize, CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD, (void *) fd)) == -1) {
fprintf(stderr,"clone() failed: %s\n", strerror(errno));
break;
}
}
sleep(30);
close(fd);
return 0;
}
I want that every clone() will wait for lock.
But Output of this code (something like this):
Trying to get lock
Trying to get lock
Trying to get lock
Got lock: Got lock: 10287
Got lock: Got lock: 10287
Enter string to write in file : Trying to get lock
Enter string to wriGot lock: 10287
Got lock: 10287
Got lock: 10287
Enter string to write in file : Trying to get lock
Got lock: 10287
Got lock: Enter string to write in file :
But when i am removing CLONE_FILES field set from clone(2), it goes all well. Other clone threads will wait for a lock().
Output of that:
Trying to get lock
Got lock: 10311
Trying to get lock
Trying to get lock
Trying to get lock
Trying to get lock
Any other alternatives (with CLONE_FILES)? And Why this kind of behavior?
Beginner in this field.
The locking provided by flock is per process, not per thread.
From http://linux.die.net/man/2/flock (emphasis mine):
A call to flock() may block if an incompatible lock is held by another process.
Subsequent flock() calls on an already locked file will convert an existing lock to the new lock mode.
Locks created by flock() are associated with an open file table entry.
Although threads are not explicitly mentioned multiple threads share a file table entry whereas multiple processes do not. Passing CLONE_FILES to clone causes your 'processes' to share file tables.
A solution might be to call dup to make more file descriptors. From the documentation:
If a process uses open(2) (or similar) to obtain more than one descriptor for the same
file, these descriptors are treated independently by flock(). An attempt to lock the file
using one of these file descriptors may be denied by a lock that the calling process has
already placed via another descriptor.
i executed the following c code on my ubuntu machine...i have read about fcntl()'s use to lock a file and even reading will not be allowed if F_WRLCK opton is set...so i started this program and before relinquishing the lock by pressing enter i tried to open the file in two ways- by directly double clicking on file1.cpp and by running a different c program in a new terminal...both the time the file was opened...so how did fcntl() allow opening of these files when F_WRLCK is set...
int main(int argc, char *argv[])
{
struct flock fl = {F_WRLCK, SEEK_SET, 0, 0, 0 };
int fd;
fl.l_pid = getpid();
if ((fd = open("/home/file1.cpp", O_WRONLY)) == -1)
{
perror("open");
exit(1);
}
if (fcntl(fd, F_SETLKW, &fl) == -1)
{
perror("fcntl");
exit(1);
}
printf("Press <RETURN> to release lock: ");
getchar();
fl.l_type = F_UNLCK; /* set to unlock same region */
if (fcntl(fd, F_SETLK, &fl) == -1)
{
perror("fcntl");
exit(1);
}
printf("Unlocked.\n");
close(fd);
return 0;
}
fcntl locks are purely advisory locks. Their only effect is to cause fcntl F_SETLK calls to block when the lock cannot be obtained. They have absolutely no effect on IO operations. It's up to your program to obtain the locks it needs before performing IO when synchronization is necessary.
This is completely analogous to using mutexes to protect objects in memory. The mutex will not prevent you from reading or writing memory addresses; it's just a protocol you use to make sure your program only reads and writes and proper times.
I was looking into below examples for understanding files locking on windows and linux. The program 1 is working on both windows and linux with gcc. But the second one is only working on Linux. Especially problem in winodws GCC is coming in the structure flock declaration. I dont know if I am missing any thing here. Also Even after I close and unlink the file in 1st example for the next run the file is not unlocked.
Program 1: Working on Windows with GCC
Source: http://www.c.happycodings.com/Gnu-Linux/code9.html
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
if((fd = open("locked.file", O_RDWR|O_CREAT|O_EXCL, 0444)) == -1)
{
printf("[%d]: Error - file already locked ...\n", getpid());
}
else
{
printf("[%d]: Now I am the only one with access :-)\n", getpid());
close(fd);
unlink("locked.file");
}
Program 2: Working on Linux with GCC
Source: http://beej.us/guide/bgipc/output/html/multipage/flocking.html
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
/* l_type l_whence l_start l_len l_pid */
struct flock fl = {F_WRLCK, SEEK_SET, 0, 0, 0 };
int fd;
fl.l_pid = getpid();
if (argc > 1)
fl.l_type = F_RDLCK;
if ((fd = open("lockdemo.c", O_RDWR)) == -1) {
perror("open");
exit(1);
}
printf("Press <RETURN> to try to get lock: ");
getchar();
printf("Trying to get lock...");
if (fcntl(fd, F_SETLKW, &fl) == -1) {
perror("fcntl");
exit(1);
}
printf("got lock\n");
printf("Press <RETURN> to release lock: ");
getchar();
fl.l_type = F_UNLCK; /* set to unlock same region */
if (fcntl(fd, F_SETLK, &fl) == -1) {
perror("fcntl");
exit(1);
}
printf("Unlocked.\n");
close(fd);
return 0;
}
Can you please help with this and if possible provide guidelines for portable code in these scenarios?
It will likely be difficult to get protabiltiy with this kind of operation using the C Runtime LIbrary. You really need to use OS specific code for this kind of thing.
But, you may be able to get this to work by inspecting and understanding the underlying C Runtime Library implimentations. The source code to both the GCC run times and the Microsofot run times come with the tools. Just go look and see how they are implimented.
Note that, on Windows, you can use the CRT file I/O APIs with Windows handles. Just go look at the source.
I would look into XPDEV specifically the file wrapper methods... they implement reasonable cross-platform locking.