/* * adv-flock.c * * Copyright (c) 2009, 2014-2018 The MacPorts Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of The MacPorts Project nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #if HAVE_CONFIG_H #include #endif #ifndef __APPLE__ /* needed to get struct sigaction on some platforms, but hides flock on macOS */ #define _XOPEN_SOURCE 500L #endif #if HAVE_SYS_FILE_H #include #endif #include #include #include #include #include #include #include "adv-flock.h" static volatile int alarmReceived = 0; static void alarmHandler(int sig UNUSED) { alarmReceived = 1; } int AdvFlockCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { static const char errorstr[] = "use one of \"-shared\", \"-exclusive\", or \"-unlock\", and optionally \"-noblock\""; int operation = 0, fd, i, ret, sigret = TCL_OK; int errnoval = 0; int oshared = 0, oexclusive = 0, ounlock = 0, onoblock = 0, retry = 0; #if !defined(HAVE_FLOCK) off_t curpos; #endif char *res; Tcl_Channel channel; ClientData handle; struct sigaction sa_oldalarm, sa_alarm; if (objc < 3 || objc > 4) { Tcl_WrongNumArgs(interp, 1, objv, "channelId switches"); return TCL_ERROR; } if ((channel = Tcl_GetChannel(interp, Tcl_GetString(objv[1]), NULL)) == NULL) { Tcl_SetResult(interp, "error getting channel, result was NULL", TCL_STATIC); return TCL_ERROR; } if (Tcl_GetChannelHandle(channel, TCL_READABLE | TCL_WRITABLE, &handle) != TCL_OK) { Tcl_SetResult(interp, "error getting channel handle", TCL_STATIC); return TCL_ERROR; } fd = (int)(intptr_t)handle; for (i = 2; i < objc; i++) { char *arg = Tcl_GetString(objv[i]); if (!strcmp(arg, "-shared")) { oshared = 1; } else if (!strcmp(arg, "-exclusive")) { oexclusive = 1; } else if (!strcmp(arg, "-unlock")) { ounlock = 1; } else if (!strcmp(arg, "-noblock")) { onoblock = 1; } } /* verify the arguments */ if ((oshared + oexclusive + ounlock) != 1) { /* only one of the options should have been specified */ Tcl_SetResult(interp, (void *) &errorstr, TCL_STATIC); return TCL_ERROR; } if (onoblock && ounlock) { /* should not be specified together */ Tcl_SetResult(interp, "-noblock cannot be used with -unlock", TCL_STATIC); return TCL_ERROR; } /* (re-)enable SIGALRM so we can use alarm(3) to specify a timeout for the * locking, do some Tcl signal processing and restart the locking to solve * #43388. */ memset(&sa_alarm, 0, sizeof(struct sigaction)); sigemptyset(&sa_alarm.sa_mask); sa_alarm.sa_flags = 0; /* explicitly don't specify SA_RESTART, we want the following alarm(3) to interrupt the locking. */ sa_alarm.sa_handler = alarmHandler; sigaction(SIGALRM, &sa_alarm, &sa_oldalarm); do { /* use a delay of one second */ retry = 0; alarmReceived = 0; alarm(1); #if HAVE_FLOCK /* prefer flock if present */ if (oshared) { operation |= LOCK_SH; } if (oexclusive) { operation |= LOCK_EX; } if (ounlock) { operation |= LOCK_UN; } if (onoblock) { operation |= LOCK_NB; } ret = flock(fd, operation); if (ret == -1) { errnoval = errno; } #else if (ounlock) { operation = F_ULOCK; } /* lockf semantics don't map to shared locks. */ if (oshared || oexclusive) { if (onoblock) { operation = F_TLOCK; } else { operation = F_LOCK; } } curpos = lseek(fd, 0, SEEK_CUR); if (curpos == -1) { Tcl_SetResult(interp, (void *) "Seek error", TCL_STATIC); return TCL_ERROR; } ret = lockf(fd, operation, 0); /* lock entire file */ if (ret == -1) { errnoval = errno; } curpos = lseek(fd, curpos, SEEK_SET); if (curpos == -1) { Tcl_SetResult(interp, (void *) "Seek error", TCL_STATIC); return TCL_ERROR; } #endif /* HAVE_FLOCK */ /* disable the alarm timer */ alarm(0); if (ret == -1) { if (oshared || oexclusive) { if (!onoblock && alarmReceived && errnoval == EINTR) { /* We were trying to lock, the lock was supposed to block, * it failed with EINTR and we processed a SIGALRM. This * probably means the call was interrupted by the timer. * Call Tcl signal processing functions and try again. */ if (Tcl_AsyncReady()) { sigret = Tcl_AsyncInvoke(interp, TCL_OK); if (sigret != TCL_OK) { break; } } retry = 1; continue; } if (onoblock && errnoval == EAGAIN) { /* The lock wasn't supposed to block, and the lock wasn't * successful because the lock is taken. On some systems * EAGAIN == EWOULDBLOCK, but let's play it safe. */ errnoval = EWOULDBLOCK; } } } } while (retry); /* Restore the previous handler for SIGALRM */ sigaction(SIGALRM, &sa_oldalarm, NULL); if (sigret != TCL_OK) { /* We received a signal that raised an error. The file hasn't been * locked. */ return sigret; } if (ret != 0) { switch (errnoval) { case EAGAIN: res = "EAGAIN"; break; case EBADF: res = "EBADF"; break; case EINVAL: res = "EINVAL"; break; case EOPNOTSUPP: res = "EOPNOTSUPP"; break; default: res = strerror(errno); break; } Tcl_SetResult(interp, (void *) res, TCL_STATIC); return TCL_ERROR; } return TCL_OK; }