2017-04-25 14:41:48 +02:00
|
|
|
/*
|
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
|
* flock.c --
|
|
|
|
|
*
|
|
|
|
|
* File opening with file locks (used by utils/path.c and database/DBio.c)
|
|
|
|
|
*
|
|
|
|
|
* Original implementation by Michael Godfrey, Stanford University.
|
|
|
|
|
* Implementation using "lockf" by Stefan Jones, MultiGiG, Inc.,
|
|
|
|
|
* September 2005 (magic 7.3.102_lockf, finally corrected and implemented
|
|
|
|
|
* as magic 7.3.119)
|
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
Played around with the file locking and discovered to my chagrin that
whenever a process writes a cell to disk, it immediately releases the
file lock it had on that cell, which is clearly not the intent of file
locking. Fixed this issue. On a related topic, revised the "cellname
writeable" command so that it can make a cell editable even if the cell
has an advisory lock and cannot be made writeable. Perhaps there should
be a clearer distinction here between "writeable" and "editable". Also:
Reconsidered the previous commit, which removed the "--disable-locking"
from the configuration options. Because some operating systems may not
implement fnctl()-based file locking (Cygwin, for one, apparently doesn't),
it is still useful to be able to completely remove the function, in case
the operating system will fail to recognize the fnctl() values in the
code. Now, file locking behavior can be permanently removed through the
configuration option, or temporarily disabled from the command line.
2022-01-01 22:53:46 +01:00
|
|
|
#ifdef FILE_LOCKS
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <fcntl.h>
|
|
|
|
|
|
|
|
|
|
#include "utils/magic.h"
|
|
|
|
|
#include "utils/hash.h"
|
|
|
|
|
#include "utils/geometry.h"
|
|
|
|
|
#include "tiles/tile.h"
|
|
|
|
|
#include "database/database.h"
|
|
|
|
|
#include "windows/windows.h"
|
|
|
|
|
#include "utils/malloc.h"
|
2024-10-04 21:07:03 +02:00
|
|
|
#include "utils/magic_zlib.h"
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2022-10-10 11:50:15 +02:00
|
|
|
/* C99 compat */
|
|
|
|
|
#include "textio/textio.h"
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/*
|
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
|
* Below are the service routines for file locking.
|
|
|
|
|
*
|
|
|
|
|
* The original file locking mechanism (Michael Godfrey) used common files.
|
|
|
|
|
* However, this has the drawback of requiring extraneous directories, and
|
|
|
|
|
* orphaned lock files will remain after a program crash.
|
|
|
|
|
*
|
|
|
|
|
* The lockf solution (Stefan Jones) has the drawback that in order to
|
|
|
|
|
* hold a lock, the file descriptor must be left open. This leaves the
|
|
|
|
|
* possibility that we may exceed the maximum number of file descriptors
|
|
|
|
|
* per process. Because this solution is the simplest, it is implemented
|
|
|
|
|
* here.
|
|
|
|
|
*
|
|
|
|
|
* For future reference, the best file locking system (?) would use
|
|
|
|
|
* signaling via fcntl(F_NOTIFY). The locking mechanism goes like this:
|
|
|
|
|
*
|
|
|
|
|
* 1. process X requests an attributes notification on each directory that
|
|
|
|
|
* it reads a file from.
|
|
|
|
|
*
|
|
|
|
|
* 2. process X reads file A for editing.
|
|
|
|
|
*
|
|
|
|
|
* 3. process Y requests an attributes notification on each directory that
|
|
|
|
|
* it reads a file from.
|
|
|
|
|
*
|
|
|
|
|
* 4. process Y reads file A, thus changing its access time attribute.
|
|
|
|
|
* process Y sets the cell as read/write.
|
|
|
|
|
*
|
|
|
|
|
* 5. process X receives a signal. It checks the cell that is being
|
|
|
|
|
* accessed. If the cell has been modified, it sets a lock on
|
|
|
|
|
* file A. Opening file A to set the lock modifies its access time.
|
|
|
|
|
* If the cell has not been modified, it sets the cell to read-only.
|
|
|
|
|
*
|
|
|
|
|
* 6. process Y receives a signal. It checks the cell that is being
|
|
|
|
|
* accessed. If the cell has not yet been modified, it sets the
|
|
|
|
|
* cell to read-only. If the cell has been modified (less likely,
|
|
|
|
|
* but possible if process X was doing something time-consuming and
|
|
|
|
|
* uninterruptable and didn't process the signal for a while), then
|
|
|
|
|
* process Y attempts to set a lock. If the lock fails, then a
|
|
|
|
|
* warning is issued.
|
|
|
|
|
*
|
|
|
|
|
* It is possible for either X or Y to win the race if both processes
|
|
|
|
|
* modified the file right after opening. However, this rare condition
|
|
|
|
|
* is unlikely to be a serious problem, and prevents a process from
|
|
|
|
|
* having to hold open many file descriptors.
|
2020-05-23 23:13:14 +02:00
|
|
|
*
|
2017-04-25 14:41:48 +02:00
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <fcntl.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
|
2022-11-02 22:12:46 +01:00
|
|
|
#ifdef HAVE_ZLIB
|
|
|
|
|
|
|
|
|
|
#include <zlib.h>
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
|
* flock_zopen --
|
|
|
|
|
*
|
|
|
|
|
* Open a compressed file with "lockf" file locking method.
|
|
|
|
|
*
|
|
|
|
|
* Results --
|
|
|
|
|
* Pointer to FILE that was opened, or NULL if an error occurred.
|
|
|
|
|
*
|
|
|
|
|
* Side effects --
|
|
|
|
|
* System I/O
|
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
gzFile flock_zopen(filename, mode, is_locked, fdp)
|
|
|
|
|
char *filename;
|
|
|
|
|
char *mode;
|
|
|
|
|
bool *is_locked;
|
|
|
|
|
int *fdp;
|
|
|
|
|
{
|
|
|
|
|
int fd;
|
|
|
|
|
gzFile f = NULL;
|
|
|
|
|
struct flock fl;
|
|
|
|
|
char *fname;
|
|
|
|
|
|
|
|
|
|
if (is_locked) *is_locked = FALSE;
|
|
|
|
|
|
|
|
|
|
/* Check if file is compressed (has a .gz extension) */
|
|
|
|
|
|
|
|
|
|
fname = PaCheckCompressed(filename);
|
|
|
|
|
|
|
|
|
|
/* If is_locked is NULL, then a lock is not requested, so just do */
|
|
|
|
|
/* a normal gzopen() and return. */
|
|
|
|
|
|
|
|
|
|
if (is_locked == NULL)
|
|
|
|
|
{
|
|
|
|
|
int oflag = 0;
|
|
|
|
|
|
|
|
|
|
if (mode[0] == 'r')
|
|
|
|
|
oflag = (mode[1] == '+') ? O_RDWR : O_RDONLY;
|
|
|
|
|
else if (mode[0] == 'w')
|
|
|
|
|
oflag = (mode[1] == '+') ? O_APPEND : O_WRONLY;
|
|
|
|
|
|
|
|
|
|
fd = open(fname, oflag);
|
|
|
|
|
if (fdp != NULL) *fdp = fd;
|
2024-10-12 09:05:28 +02:00
|
|
|
freeMagic(fname);
|
2022-11-02 22:12:46 +01:00
|
|
|
return gzdopen(fd, mode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Diagnostic */
|
|
|
|
|
/* TxPrintf("Opening file <%s>\n", fname); */
|
|
|
|
|
|
|
|
|
|
fd = open(fname, O_RDWR);
|
|
|
|
|
if (fd < 0)
|
|
|
|
|
{
|
|
|
|
|
if (is_locked) *is_locked = TRUE;
|
|
|
|
|
fd = open(fname, O_RDONLY);
|
|
|
|
|
f = gzdopen(fd, "r");
|
|
|
|
|
goto done;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fl.l_len = 0;
|
|
|
|
|
fl.l_start = 0;
|
|
|
|
|
fl.l_whence = SEEK_SET;
|
|
|
|
|
fl.l_type = F_WRLCK;
|
|
|
|
|
fl.l_pid = getpid();
|
|
|
|
|
|
|
|
|
|
if (fcntl(fd, F_GETLK, &fl))
|
|
|
|
|
{
|
|
|
|
|
perror(fname);
|
|
|
|
|
f = gzdopen(fd, mode);
|
|
|
|
|
goto done;
|
|
|
|
|
}
|
|
|
|
|
close(fd);
|
|
|
|
|
fd = -1;
|
|
|
|
|
|
|
|
|
|
if (fl.l_type == F_UNLCK)
|
|
|
|
|
{
|
|
|
|
|
fl.l_len = 0;
|
|
|
|
|
fl.l_start = 0;
|
|
|
|
|
fl.l_whence = SEEK_SET;
|
|
|
|
|
fl.l_type = F_WRLCK;
|
|
|
|
|
fl.l_pid = getpid();
|
|
|
|
|
|
|
|
|
|
fd = open(fname, O_RDWR);
|
|
|
|
|
if (fcntl(fd, F_SETLK, &fl))
|
|
|
|
|
{
|
|
|
|
|
perror(fname);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* Diagnostic */
|
|
|
|
|
/* TxPrintf("Obtained lock on file <%s> (fd=%d)\n", fname, fd); */
|
|
|
|
|
}
|
|
|
|
|
f = gzdopen(fd, mode);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* Don't know why PID is not set by F_GETLK as advertised? */
|
|
|
|
|
if (fl.l_pid == 0)
|
|
|
|
|
TxPrintf("File <%s> is already locked by another process."
|
|
|
|
|
" Opening read-only.\n", fname);
|
|
|
|
|
else
|
|
|
|
|
TxPrintf("File <%s> is already locked by pid %d. Opening read-only.\n",
|
|
|
|
|
fname, (int)fl.l_pid);
|
|
|
|
|
if (is_locked) *is_locked = TRUE;
|
|
|
|
|
fd = open(fname, O_RDONLY);
|
|
|
|
|
f = gzdopen(fd, "r");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
done:
|
|
|
|
|
if (fdp != NULL) *fdp = fd;
|
2024-10-12 09:05:28 +02:00
|
|
|
freeMagic(fname);
|
2022-11-02 22:12:46 +01:00
|
|
|
return f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif /* HAVE_ZLIB */
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/*
|
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
|
* flock_open --
|
|
|
|
|
*
|
|
|
|
|
* Open a file with "lockf" file locking method.
|
|
|
|
|
*
|
|
|
|
|
* Results --
|
|
|
|
|
* Pointer to FILE that was opened, or NULL if an error occurred.
|
|
|
|
|
*
|
|
|
|
|
* Side effects --
|
2022-11-02 22:12:46 +01:00
|
|
|
* fdp pointer value filled with the file descriptor.
|
2017-04-25 14:41:48 +02:00
|
|
|
* System I/O
|
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
2022-11-02 22:12:46 +01:00
|
|
|
FILE *flock_open(filename, mode, is_locked, fdp)
|
2017-04-25 14:41:48 +02:00
|
|
|
char *filename;
|
|
|
|
|
char *mode;
|
|
|
|
|
bool *is_locked;
|
2022-11-02 22:12:46 +01:00
|
|
|
int *fdp;
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
FILE *f = NULL, *tmp;
|
|
|
|
|
struct flock fl;
|
|
|
|
|
|
2022-11-02 22:12:46 +01:00
|
|
|
if (fdp != NULL) *fdp = -1;
|
2017-04-25 14:41:48 +02:00
|
|
|
if (is_locked) *is_locked = FALSE;
|
|
|
|
|
|
|
|
|
|
/* If is_locked is NULL, then a lock is not requested, so just do */
|
|
|
|
|
/* a normal fopen() and return. */
|
|
|
|
|
|
2022-11-02 22:12:46 +01:00
|
|
|
if (is_locked == NULL)
|
|
|
|
|
{
|
|
|
|
|
f = fopen(filename, mode);
|
|
|
|
|
if ((fdp != NULL) && (f != NULL)) *fdp = fileno(f);
|
|
|
|
|
return f;
|
|
|
|
|
}
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/* Diagnostic */
|
|
|
|
|
/* TxPrintf("Opening file <%s>\n", filename); */
|
|
|
|
|
|
|
|
|
|
tmp = fopen(filename, "r+");
|
|
|
|
|
if (tmp == NULL)
|
|
|
|
|
{
|
|
|
|
|
if (is_locked) *is_locked = TRUE;
|
|
|
|
|
f = fopen(filename, "r");
|
2020-05-23 23:13:14 +02:00
|
|
|
goto done;
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fl.l_len = 0;
|
|
|
|
|
fl.l_start = 0;
|
|
|
|
|
fl.l_whence = SEEK_SET;
|
|
|
|
|
fl.l_type = F_WRLCK;
|
|
|
|
|
fl.l_pid = getpid();
|
|
|
|
|
|
|
|
|
|
if (fcntl(fileno(tmp), F_GETLK, &fl))
|
|
|
|
|
{
|
|
|
|
|
perror(filename);
|
|
|
|
|
f = fopen(filename, mode);
|
|
|
|
|
goto done;
|
|
|
|
|
}
|
|
|
|
|
fclose(tmp);
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
if (fl.l_type == F_UNLCK)
|
|
|
|
|
{
|
|
|
|
|
fl.l_len = 0;
|
|
|
|
|
fl.l_start = 0;
|
|
|
|
|
fl.l_whence = SEEK_SET;
|
|
|
|
|
fl.l_type = F_WRLCK;
|
|
|
|
|
fl.l_pid = getpid();
|
|
|
|
|
|
|
|
|
|
f = fopen(filename, "r+");
|
|
|
|
|
if (fcntl(fileno(f), F_SETLK, &fl))
|
|
|
|
|
{
|
|
|
|
|
perror(filename);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* Diagnostic */
|
|
|
|
|
/* TxPrintf("Obtained lock on file <%s> (fd=%d)\n", filename, fileno(f)); */
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* Don't know why PID is not set by F_GETLK as advertised? */
|
|
|
|
|
if (fl.l_pid == 0)
|
|
|
|
|
TxPrintf("File <%s> is already locked by another process."
|
|
|
|
|
" Opening read-only.\n", filename);
|
|
|
|
|
else
|
|
|
|
|
TxPrintf("File <%s> is already locked by pid %d. Opening read-only.\n",
|
|
|
|
|
filename, (int)fl.l_pid);
|
|
|
|
|
if (is_locked) *is_locked = TRUE;
|
|
|
|
|
f = fopen(filename, "r");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
done:
|
2022-11-02 22:12:46 +01:00
|
|
|
if ((fdp != NULL) && (f != NULL)) *fdp = fileno(f);
|
2017-04-25 14:41:48 +02:00
|
|
|
return f;
|
|
|
|
|
}
|
Played around with the file locking and discovered to my chagrin that
whenever a process writes a cell to disk, it immediately releases the
file lock it had on that cell, which is clearly not the intent of file
locking. Fixed this issue. On a related topic, revised the "cellname
writeable" command so that it can make a cell editable even if the cell
has an advisory lock and cannot be made writeable. Perhaps there should
be a clearer distinction here between "writeable" and "editable". Also:
Reconsidered the previous commit, which removed the "--disable-locking"
from the configuration options. Because some operating systems may not
implement fnctl()-based file locking (Cygwin, for one, apparently doesn't),
it is still useful to be able to completely remove the function, in case
the operating system will fail to recognize the fnctl() values in the
code. Now, file locking behavior can be permanently removed through the
configuration option, or temporarily disabled from the command line.
2022-01-01 22:53:46 +01:00
|
|
|
|
|
|
|
|
#endif /* FILE_LOCKS */
|