Flock (): is it possible to simply check if the file has already been locked, but has not actually acquired a lock, if not?

My use case is as follows: I have a program that provides that only one instance can be started at any given time, so when it starts, it always tries to capture the lock file in a standard location and exits if the file is already locked. This all works fine, but now I want to improve the program with a new command line option, which, when indicated, will cause the program to simply print a status report for the program and then terminate (before the main blocking protection mechanism is described above) , which will include whether the locked file is already locked or not, what is the pid of the running process (if one exists) and some state of the program is requested from the database.

So, as you can see, when I call the β€œstatus report” in this mode, my program should not actually acquire a lock, if it is available. I just want to know if the file is locked or not, so I can inform the user as part of the status report.

From my search, there seems to be no way to do this. Most likely, the only possible solution is to call flock() with a non-blocking flag, and then if you really acquired the lock, you can immediately release it. Something like that:

 if (flock(fileno(lockFile), LOCK_EX|LOCK_NB ) == -1) { if (errno == EWOULDBLOCK) { printf("lock file is locked\n"); } else { // error } // end if } else { flock(fileno(lockFile), LOCK_UN ); printf("lock file is unlocked\n"); } // end if 

I believe that it is not so important to acquire a lock and then immediately release it, but I was wondering if there is a better solution that does not involve short-term and unnecessary obtaining of a lock?

Note. There are already several similar questions, the names of which may seem similar to this question, but from the content of these questions it is clear that the OP is interested in actually writing the file after acquiring the lock, so this is a special question:

+6
source share
3 answers

You cannot do it reliably . Processes are asynchronous: when you cannot get a lock, there is no guarantee that the file will be locked by the time it prints locked status. Similarly, if you manage to get a lock, you will immediately release it, so by the time you print the status unlocked , the file my has been locked by another process. If there are many opponents trying to lock this file, the likelihood that the status message is not synchronized is high. Attackers can take advantage of this approach to infiltrate systems.

If you had to rely on this check in a script to do any kind of parallel work, all bets are disabled. If it just creates an informative status, you should use the elapsed time in status messages:

 if (flock(fileno(lockFile), LOCK_EX|LOCK_NB) == -1) { if (errno == EWOULDBLOCK) { printf("lock file was locked\n"); } else { // error } } else { flock(fileno(lockFile), LOCK_UN); printf("lock file was unlocked\n"); } 
+8
source

I do not see what is wrong with the approach to file locking and immediate release. In my opinion, you do it the way I would do it.

However, on Unix there is another blocking API: fcntl locks. See man fcntl on Linux. It has F_SETLK to get or release the lock, and F_GETLK to check whether the lock can be set. fcntl locks are somewhat different from what flock locks: they are write protection locks placed in the file area, and not for the entire file.

There is also a third api: lockf(3) . You can use F_LOCK to lock the file and F_TEST to check if the area of ​​the file can be locked. The lockf(3) API was implemented as a shell on top of fcntl(2) locks on Linux, but this may not be true on other operating systems.

+3
source

Do not use flock() . This does not work reliably if the lock file directory is a network file system (for example, NFS) and the OS you are using does not implement flock() with fcntl() block messages.

(For example, in modern Linux systems, flock() and fcntl() locks are locked separately and do not interact with local files, but interact with files located in NFS file systems. It is not so strange to have /var/lock in the NFS file system in clusters servers, especially fault tolerance systems and the web server, so this is, in my opinion, a real problem that you should consider.)

Edited to add: if for some external reason you need to use flock() , you can use flock(fd, LOCK_EX|LOCK_NB) to try to get an exclusive lock. This call will never block (wait until the lock is released), but with a -1 error and errno == EWOULDBLOCK if the file is already locked. Like the fcntl() locking scheme described in detail below, you are trying to get an exclusive lock (without locking); if successful, you keep the lock file descriptor open and allow the operating system to automatically release the lock when you exit the process. If the lock is non-blocking, you must choose whether to interrupt or continue anyway.

You can accomplish your tasks using the POSIX.1 and fcntl() functions to block warning entries (covering the entire file). Semantics is standard across all POSIXy systems, so this approach will work on all POSIX and unified systems.

The features of fcntl() locks are simple but unintuitive. When any descriptor related to the lock file is closed, warning locks of this file are issued. When the process ends, warning locks on all open files are automatically released. Locks are supported through exec*() . Locks are not inherited via fork() , and they are not released in the parent (even if they are marked close-on-exec). (If the descriptors are close-on-exec, then they will be automatically closed in the child process. Otherwise, the child process will have an open file descriptor, but not fcntl() locks. Closing the descriptors in the child process will not affect the parent lock of the file.)

Therefore, the correct strategy is very simple: open the lock file exactly once and use fcntl(fd,F_SETLK,&lock) to place an exclusive console lock without locking: if there is a conflicting lock, it will immediately fail, and not lock until the lock is received. Keep the handle open and let the operating system automatically release the lock when the process exits.

For instance:

 #define _POSIX_C_SOURCE 200809L #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> /* Open and exclusive-lock file, creating it (-rw-------) * if necessary. If fdptr is not NULL, the descriptor is * saved there. The descriptor is never one of the standard * descriptors STDIN_FILENO, STDOUT_FILENO, or STDERR_FILENO. * If successful, the function returns 0. * Otherwise, the function returns nonzero errno: * EINVAL: Invalid lock file path * EMFILE: Too many open files * EALREADY: Already locked * or one of the open(2)/creat(2) errors. */ static int lockfile(const char *const filepath, int *const fdptr) { struct flock lock; int used = 0; /* Bits 0 to 2: stdin, stdout, stderr */ int fd; /* In case the caller is interested in the descriptor, * initialize it to -1 (invalid). */ if (fdptr) *fdptr = -1; /* Invalid path? */ if (filepath == NULL || *filepath == '\0') return errno = EINVAL; /* Open the file. */ do { fd = open(filepath, O_RDWR | O_CREAT, 0600); } while (fd == -1 && errno == EINTR); if (fd == -1) { if (errno == EALREADY) errno = EIO; return errno; } /* Move fd away from the standard descriptors. */ while (1) if (fd == STDIN_FILENO) { used |= 1; fd = dup(fd); } else if (fd == STDOUT_FILENO) { used |= 2; fd = dup(fd); } else if (fd == STDERR_FILENO) { used |= 4; fd = dup(fd); } else break; /* Close the standard descriptors we temporarily used. */ if (used & 1) close(STDIN_FILENO); if (used & 2) close(STDOUT_FILENO); if (used & 4) close(STDERR_FILENO); /* Did we run out of descriptors? */ if (fd == -1) return errno = EMFILE; /* Exclusive lock, cover the entire file (regardless of size). */ lock.l_type = F_WRLCK; lock.l_whence = SEEK_SET; lock.l_start = 0; lock.l_len = 0; if (fcntl(fd, F_SETLK, &lock) == -1) { /* Lock failed. Close file and report locking failure. */ close(fd); return errno = EALREADY; } /* Save descriptor, if the caller wants it. */ if (fdptr) *fdptr = fd; return 0; } 

The reason mentioned above does not accidentally use the standard descriptor, because I was bitten by it in a very rare case. (I wanted to execute a user-specified process, holding the lock, but redirecting standard input and output to the current control terminal.)

Using is very simple:

  int result; result = lockfile(YOUR_LOCKFILE_PATH, NULL); if (result == 0) { /* Have an exclusive lock on YOUR_LOCKFILE_PATH */ } else if (result == EALREADY) { /* YOUR_LOCKFILE_PATH is already locked by another process */ } else { /* Cannot lock YOUR_LOCKFILE_PATH, see strerror(result). */ } 

Edited to add: I used internal binding ( static ) for the above function out of habit. If the lock file is user-dependent, it should use ~/.yourapplication/lockfile ; if it is system-wide, it should use, for example, /var/lock/yourapplication/lockfile . I have a habit of saving functions related to this type of initialization, including determining / creating a lock path, etc., As well as an automatic plug-in registration function (using opendir() / readdir() / dlopen() / dlsym() / closedir() ), in the same file; the lockfile function is usually called internally (a function that builds the path to the lock file) and thus has an internal connection.

Feel free to use, reuse or modify the function as you wish; I believe that it is in the public domain or licensed under CC0 , where devotion to the public domain is not possible.

The handle "leaked" intentionally, so that it will be closed (and locked when it is released) by the operating system when the process ends, but not earlier.

If there are many post-work corrections that your process makes during which you want to allow another copy of this process, you can save the handle and just close(thatfd) in the place where you want to release the lock.

+1
source

Source: https://habr.com/ru/post/983837/


All Articles