/*
* lockfile . c Safely creates a lockfile , also over NFS .
* This file also holds the implementation for
* the Svr4 maillock functions .
*
* Copyright ( C ) Miquel van Smoorenburg and contributors 1997 - 2021 .
*
* This library is free software ; you can redistribute it and / or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation ; either
* version 2 of the License , or ( at your option ) any later version .
*/
#include "autoconf.h"
#include <sys/types.h>
#if HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#include <sys/stat.h>
#include <sys/wait.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include "lockfile.h"
#include "maillock.h"
#ifdef HAVE_UTIME
#include <utime.h>
#endif
#ifdef LIB
static char *mlockfile;
static int islocked = 0 ;
#endif
#ifdef MAILGROUP
/*
* Get the id of the mailgroup , by statting the helper program .
* If it is setgroup - id , then the group is the mailgroup .
*/
static int mailgid()
{
struct stat st;
if (stat(LOCKPROG, &st) != 0 )
return (gid_t)-1 ;
if ((st.st_mode & 02000 ) == 0 )
return (gid_t)-1 ;
return st.st_gid;
}
/*
* Is this a lock for a mailbox ? Check :
* - is the file in / path / to / USERNAME . lock format
* - is / path / to / USERNAME present and owned by us
* - is / path / to writable by group mail
*
* To be safe in a setgid program , chdir ( ) into the lockfile
* directory first , then pass in the basename of the lockfile .
*/
#ifdef LIB
static
#endif
int is_maillock(const char *lockfile)
{
struct stat st;
gid_t gid;
char tmp[1024 ];
char *p;
/* remove .lock suffix */
strncpy(tmp, lockfile, sizeof (tmp) - 1 );
tmp[sizeof (tmp) - 1 ] = 0 ;
if ((p = strrchr(tmp, '.' )) == NULL || strcmp(p, ".lock" ) != 0 )
return 0 ;
*p = 0 ;
/* file to lock must exist, and must be owned by us */
if (lstat(tmp, &st) != 0 ||
(st.st_mode & S_IFMT) != S_IFREG || st.st_uid != getuid())
return 0 ;
/* Directory this file is in must be writable by group mail. */
if ((gid = mailgid()) == (gid_t)-1 )
return 0 ;
if ((p = strrchr(tmp, '/' )) != NULL)
*p = 0 ;
else
strncpy(tmp, "." , sizeof (tmp));
if (stat(tmp, &st) != 0 || st.st_gid != gid || (st.st_mode & 0020 ) == 0 )
return 0 ;
return 1 ;
}
#ifdef LIB
/*
* Call external program to do the actual locking .
*/
static int run_helper(char *opt, const char *lockfile, int retries, int flags)
{
sigset_t set, oldset;
char buf[8 ];
pid_t pid, n;
int st;
/*
* Better safe than sorry .
*/
if (geteuid() == 0 )
return L_ERROR;
/*
* Block SIGCHLD . The main program might have installed
* handlers we don ' t want to call .
*/
sigemptyset(&set);
sigaddset(&set, SIGCHLD);
sigprocmask(SIG_BLOCK, &set, &oldset);
/*
* Fork , execute locking program and wait .
*/
if ((pid = fork()) < 0 )
return L_ERROR;
if (pid == 0 ) {
/* drop privs */
if (setuid(geteuid()) < 0 ) {
perror("setuid" );
_exit (L_ERROR);
}
snprintf(buf, sizeof (buf), "%d" , retries % 1000 );
execl(LOCKPROG, LOCKPROG, opt, "-r" , buf, "-q" ,
(flags & L_PID) ? "-p" : "-N" , lockfile, NULL);
_exit (L_ERROR);
}
/*
* Wait for return status - do something appropriate
* if program died or returned L_ERROR .
*/
while ((n = waitpid(pid, &st, 0 )) != pid)
if (n < 0 && errno != EINTR)
break ;
if (!sigismember(&oldset, SIGCHLD))
sigprocmask(SIG_UNBLOCK, &set, NULL);
if (n < 0 )
return L_ERROR;
if (!WIFEXITED(st) || WEXITSTATUS(st) == L_ERROR) {
errno = EINTR;
return L_ERROR;
}
return WEXITSTATUS(st);
}
#endif /* LIB*/
#endif /* MAILGROUP */
#define TMPLOCKSTR ".lk"
#define TMPLOCKSTRSZ strlen(TMPLOCKSTR)
#define TMPLOCKPIDSZ 5
#define TMPLOCKTIMESZ 1
#define TMPLOCKSYSNAMESZ 23
#define TMPLOCKFILENAMESZ (TMPLOCKSTRSZ + TMPLOCKPIDSZ + \
TMPLOCKTIMESZ + TMPLOCKSYSNAMESZ)
static int lockfilename(const char *lockfile, char *tmplock, size_t tmplocksz)
{
char sysname[256 ];
char *p;
#ifdef MAXPATHLEN
/*
* Safety measure .
*/
if (strlen(lockfile) + TMPLOCKFILENAMESZ > MAXPATHLEN) {
errno = ENAMETOOLONG;
return L_ERROR;
}
#endif
if (strlen(lockfile) + TMPLOCKFILENAMESZ + 1 > tmplocksz) {
errno = EINVAL;
return L_ERROR;
}
/*
* Create a temp lockfile ( hopefully unique ) and write
* either our pid / ppid in it , or 0 \ 0 for svr4 compatibility .
*/
if (gethostname(sysname, sizeof (sysname)) < 0 )
return L_ERROR;
if ((p = strchr(sysname, '.' )) != NULL)
*p = 0 ;
/* strcpy is safe: length-check above, limited at snprintf below */
strcpy(tmplock, lockfile);
if ((p = strrchr(tmplock, '/' )) == NULL)
p = tmplock;
else
p++;
if (snprintf(p, TMPLOCKFILENAMESZ, "%s%0*d%0*x%s" , TMPLOCKSTR,
TMPLOCKPIDSZ, (int )getpid(),
TMPLOCKTIMESZ, (int )time(NULL) & 15 ,
sysname) < 0 ) {
// never happens but gets rid of gcc truncation warning.
errno = EOVERFLOW;
return L_ERROR;
}
return 0 ;
}
/*
* Create a lockfile .
*/
static int lockfile_create_save_tmplock(const char *lockfile,
char *tmplock, size_t tmplocksz,
volatile char **xtmplock,
int retries, int flags, const struct lockargs_s_ *args)
{
struct stat st, st1;
char pidbuf[40 ];
pid_t pid = 0 ;
int sleeptime = 0 ;
int statfailed = 0 ;
int fd;
int i, e, pidlen;
int dontsleep = 1 ;
int tries = retries + 1 ;
/* process optional flags that have arguments */
if (flags & L_INTERVAL_D_) {
sleeptime = args->interval;
}
/* decide which PID to write to the lockfile */
if (flags & L_PID)
pid = getpid();
if (flags & L_PPID) {
pid = getppid();
if (pid == 1 ) {
/* orphaned */
return L_ORPHANED;
}
}
pidlen = snprintf(pidbuf, sizeof (pidbuf), "%d\n" , pid);
if (pidlen < 0 || pidlen > (int ) sizeof (pidbuf) - 1 ) {
errno = EOVERFLOW;
return L_ERROR;
}
/* create temporary lockfile */
if ((i = lockfilename(lockfile, tmplock, tmplocksz)) != 0 )
return i;
if (xtmplock)
*xtmplock = tmplock;
fd = open(tmplock, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, 0644 );
if (fd < 0 ) {
/* permission denied? perhaps try suid helper */
#if defined (LIB) && defined (MAILGROUP)
if (errno == EACCES && is_maillock(lockfile))
return run_helper("-l" , lockfile, retries, flags);
#endif
return L_TMPLOCK;
}
i = write(fd, pidbuf, pidlen);
e = errno;
if (close(fd) != 0 ) {
e = errno;
i = -1 ;
}
if (i != pidlen) {
unlink(tmplock);
tmplock[0 ] = 0 ;
errno = i < 0 ? e : EAGAIN;
return L_TMPWRITE;
}
/*
* Now try to link the temporary lock to the lock .
*/
for (i = 0 ; i < tries && tries > 0 ; i++) {
if (!dontsleep) {
if (!(flags & L_INTERVAL_D_))
sleeptime += 5 ;
if (sleeptime > 5 ) sleeptime = 5 ;
#ifdef LIB
sleep(sleeptime);
#else
if ((e = check_sleep(sleeptime, flags)) != 0 ) {
unlink(tmplock);
tmplock[0 ] = 0 ;
return e;
}
#endif
}
dontsleep = 0 ;
/*
* Now lock by linking the tempfile to the lock .
*
* KLUDGE : some people say the return code of
* link ( ) over NFS can ' t be trusted .
* EXTRA FIX : the value of the nlink field
* can ' t be trusted ( may be cached ) .
*/
(void )!link(tmplock, lockfile);
if (lstat(tmplock, &st1) < 0 ) {
tmplock[0 ] = 0 ;
return L_ERROR; /* Can't happen */
}
if (lstat(lockfile, &st) < 0 ) {
if (statfailed++ > 5 ) {
/*
* Normally , this can ' t happen ; either
* another process holds the lockfile or
* we do . So if this error pops up
* repeatedly , just exit . . .
*/
e = errno;
(void )unlink(tmplock);
tmplock[0 ] = 0 ;
errno = e;
return L_MAXTRYS;
}
continue ;
}
/*
* See if we got the lock .
*/
if (st.st_rdev == st1.st_rdev &&
st.st_ino == st1.st_ino) {
(void )unlink(tmplock);
tmplock[0 ] = 0 ;
return L_SUCCESS;
}
statfailed = 0 ;
/*
* If there is a lockfile and it is invalid ,
* remove the lockfile .
*/
if (lockfile_check(lockfile, flags) == -1 ) {
if (unlink(lockfile) < 0 && errno != ENOENT) {
/*
* we failed to unlink the stale
* lockfile , give up .
*/
return L_RMSTALE;
}
dontsleep = 1 ;
/*
* If the lockfile was invalid , then the first
* try wasn ' t valid either - make sure we
* try at least once more .
*/
if (tries == 1 ) tries++;
}
}
(void )unlink(tmplock);
tmplock[0 ] = 0 ;
errno = EAGAIN;
return L_MAXTRYS;
}
#ifdef LIB
static
#endif
int lockfile_create_set_tmplock(const char *lockfile, volatile char **xtmplock, int retries, int flags, const struct lockargs_s_ *args)
{
char *tmplock;
int r, e;
size_t l;
l = strlen(lockfile)+TMPLOCKFILENAMESZ+1 ;
if ((tmplock = (char *)malloc(l)) == NULL)
return L_ERROR;
tmplock[0 ] = 0 ;
r = lockfile_create_save_tmplock(lockfile,
tmplock, l, xtmplock, retries, flags, args);
if (xtmplock)
*xtmplock = NULL;
e = errno;
free(tmplock);
errno = e;
return r;
}
#ifdef LIB
int lockfile_create(const char *lockfile, int retries, int flags)
{
/* check against unknown flags */
if (flags & ~(L_PID|L_PPID)) {
errno = EINVAL;
return L_ERROR;
}
return lockfile_create_set_tmplock(lockfile, NULL, retries, flags, NULL);
}
#ifdef STATIC
int lockfile_create2(const char *lockfile, int retries,
int flags, struct lockargs_s_ *args, int args_sz)
{
#define FLAGS_WITH_ARGS (L_INTERVAL_D_)
#define KNOWN_FLAGS (L_PID|L_PPID|L_INTERVAL_D_)
/* check if size is the same (version check) */
if (args != NULL && sizeof (struct lockargs_s_) != args_sz) {
errno = EINVAL;
return L_ERROR;
}
/* some flags _must_ have a non-null args */
if (args == NULL && (flags & FLAGS_WITH_ARGS)) {
errno = EINVAL;
return L_ERROR;
}
/* check against unknown flags */
if (flags & ~KNOWN_FLAGS) {
errno = EINVAL;
return L_ERROR;
}
return lockfile_create_set_tmplock(lockfile, NULL, retries, flags, args);
}
#endif
#endif
/*
* See if a valid lockfile is present .
* Returns 0 if so , - 1 if not .
*/
int lockfile_check(const char *lockfile, int flags)
{
struct stat st, st2;
char buf[16 ];
time_t now;
pid_t pid;
int fd, len, r;
if (stat(lockfile, &st) < 0 )
return -1 ;
/*
* Get the contents and mtime of the lockfile .
*/
time(&now);
pid = 0 ;
if ((fd = open(lockfile, O_RDONLY)) >= 0 ) {
/*
* Try to use ' atime after read ' as now , this is
* the time of the filesystem . Should not get
* confused by ' atime ' or ' noatime ' mount options .
*/
len = 0 ;
if (fstat(fd, &st) == 0 &&
(len = read(fd, buf, sizeof (buf))) >= 0 &&
fstat(fd, &st2) == 0 &&
st.st_atime != st2.st_atime)
now = st.st_atime;
close(fd);
if (len > 0 && (flags & (L_PID|L_PPID))) {
buf[len] = 0 ;
pid = atoi(buf);
}
}
if (pid > 0 ) {
/*
* If we have a pid , see if the process
* owning the lockfile is still alive .
*/
r = kill(pid, 0 );
if (r == 0 || errno == EPERM)
return 0 ;
if (r < 0 && errno == ESRCH)
return -1 ;
/* EINVAL - FALLTHRU */
}
/*
* Without a pid in the lockfile , the lock
* is valid if it is newer than 5 mins .
*/
if (now < st.st_mtime + 300 )
return 0 ;
return -1 ;
}
/*
* Remove a lock .
*/
int lockfile_remove(const char *lockfile)
{
if (unlink(lockfile) < 0 ) {
#if defined (LIB) && defined (MAILGROUP)
if (errno == EACCES && is_maillock(lockfile))
return run_helper("-u" , lockfile, 0 , 0 );
#endif
return errno == ENOENT ? 0 : -1 ;
}
return 0 ;
}
/*
* Touch a lock .
*/
int lockfile_touch(const char *lockfile)
{
#ifdef HAVE_UTIME
return utime(lockfile, NULL);
#else
return utimes(lockfile, NULL);
#endif
}
#ifdef LIB
/*
* Lock a mailfile . This looks a lot like the SVR4 function .
* Arguments : lusername , retries .
*/
int maillock(const char *name, int retries)
{
char *p, *mail;
char *newlock;
int i, e;
int len, newlen;
if (islocked) return 0 ;
#ifdef MAXPATHLEN
if (strlen(name) + sizeof (MAILDIR) + 6 > MAXPATHLEN) {
errno = ENAMETOOLONG;
return L_NAMELEN;
}
#endif
/*
* If $ MAIL is for the same username as " name "
* then use $ MAIL instead .
*/
len = strlen(name)+strlen(MAILDIR)+6 ;
mlockfile = (char *)malloc(len);
if (!mlockfile)
return L_ERROR;
sprintf(mlockfile, "%s%s.lock" , MAILDIR, name);
if ((mail = getenv("MAIL" )) != NULL) {
if ((p = strrchr(mail, '/' )) != NULL)
p++;
else
p = mail;
if (strcmp(p, name) == 0 ) {
newlen = strlen(mail)+6 ;
#ifdef MAXPATHLEN
if (newlen > MAXPATHLEN) {
errno = ENAMETOOLONG;
return L_NAMELEN;
}
#endif
if (newlen > len) {
newlock = (char *)realloc (mlockfile, newlen);
if (newlock == NULL) {
e = errno;
free (mlockfile);
mlockfile = NULL;
errno = e;
return L_ERROR;
}
mlockfile = newlock;
}
sprintf(mlockfile, "%s.lock" , mail);
}
}
i = lockfile_create(mlockfile, retries, 0 );
if (i == 0 ) islocked = 1 ;
return i;
}
void mailunlock(void )
{
if (!islocked) return ;
lockfile_remove(mlockfile);
free (mlockfile);
islocked = 0 ;
}
void touchlock(void )
{
lockfile_touch(mlockfile);
}
#endif
Messung V0.5 in Prozent C=95 H=100 G=97
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-09-27)
¤
*© Formatika GbR, Deutschland