[PATCH v2] flock: add support for using fcntl() with open file description locks

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Currently, there is no way for shell scripts to safely access
resources protected by POSIX locking (fcntl with the F_SETLK/F_SETLKW
commands). For example, the glibc function lckpwdf(), used to
protect access to the /etc/shadow database, works by taking a
F_SETLKW on /etc/.pwd.lock .

Due to the odd semantics of POSIX locking (e.g. released when any file
descriptor associated to the inode is closed), we cannot usefully
directly expose the POSIX F_SETLK/F_SETLKW commands. However, linux
3.15 introduced F_OFD_SETLK[W], with semantics wrt. ownership and
release better matching those of flock(2), and crucially they do
conflict with locks obtained via F_SETLK[W]. With this, a shell script
can do

  exec 4> /etc/.pwd.lock
  flock --fcntl 4
  <access/modify /etc/shadow ...>
  flock --fcntl --unlock 4 # or just exit

without conflicting with passwd(1) or other utilities that
access/modify /etc/shadow.

No single-letter shorthand is defined for the option, because this is
somewhat low-level and the user really needs to know what he is doing.

Also, this leaves the door open for teaching --fcntl to accept an
optional argument: "ofd", the default, and "posix", should anyone find
a use for flock(1) taking a F_SETLK[W] lock.

Signed-off-by: Rasmus Villemoes <rasmus.villemoes@xxxxxxxxx>
---
v2:

- Shorten option name to --fcntl instead of --fcntl-ofd.

- Use a do_lock() helper function switching on the API to use, making
  the while () condition easier to read and making it simpler to add
  the mentioned --fcntl=posix should the need arise.

- Fix up places that need HAVE_FCNTL_OFD_LOCKS guarding.

 configure.ac      |  6 ++++
 meson.build       |  3 ++
 sys-utils/flock.c | 82 +++++++++++++++++++++++++++++++++++++++++++++--
 3 files changed, 89 insertions(+), 2 deletions(-)

diff --git a/configure.ac b/configure.ac
index c302732e7..441b09440 100644
--- a/configure.ac
+++ b/configure.ac
@@ -434,6 +434,12 @@ AC_CHECK_DECLS([PR_REP_CAPACITY], [], [], [
 	#include <linux/pr.h>
 ])
 
+AC_CHECK_DECL([F_OFD_SETLK],
+	[AC_DEFINE([HAVE_FCNTL_OFD_LOCKS], [1],
+	[Define to 1 if fcntl.h defines F_OFD_ constants])], [], [
+#include <fcntl.h>
+])
+
 AC_CHECK_HEADERS([security/openpam.h], [], [], [
 #ifdef HAVE_SECURITY_PAM_APPL_H
 #include <security/pam_appl.h>
diff --git a/meson.build b/meson.build
index 99126f7aa..004c849f1 100644
--- a/meson.build
+++ b/meson.build
@@ -704,6 +704,9 @@ conf.set('HAVE_DECL_BLK_ZONE_REP_CAPACITY', have ? 1 : false)
 have = cc.has_header_symbol('linux/pr.h', 'PR_REP_CAPACITY')
 conf.set('HAVE_DECL_PR_REP_CAPACITY', have ? 1 : false)
 
+have = cc.has_header_symbol('fcntl.h', 'F_OFD_SETLK', args: '-D_GNU_SOURCE')
+conf.set('HAVE_FCNTL_OFD_LOCKS', have ? 1 : false)
+
 code = '''
 #include <time.h>
 #if !@0@
diff --git a/sys-utils/flock.c b/sys-utils/flock.c
index 7d878ff81..17088ce7e 100644
--- a/sys-utils/flock.c
+++ b/sys-utils/flock.c
@@ -48,6 +48,11 @@
 #include "monotonic.h"
 #include "timer.h"
 
+enum {
+	API_FLOCK,
+	API_FCNTL_OFD,
+};
+
 static void __attribute__((__noreturn__)) usage(void)
 {
 	fputs(USAGE_HEADER, stdout);
@@ -70,6 +75,9 @@ static void __attribute__((__noreturn__)) usage(void)
 	fputs(_(  " -o, --close              close file descriptor before running command\n"), stdout);
 	fputs(_(  " -c, --command <command>  run a single command string through the shell\n"), stdout);
 	fputs(_(  " -F, --no-fork            execute command without forking\n"), stdout);
+#ifdef HAVE_FCNTL_OFD_LOCKS
+	fputs(_(  "     --fcntl              use fcntl(F_OFD_SETLK) rather than flock()\n"), stdout);
+#endif
 	fputs(_(  "     --verbose            increase verbosity\n"), stdout);
 	fputs(USAGE_SEPARATOR, stdout);
 	fprintf(stdout, USAGE_HELP_OPTIONS(26));
@@ -126,6 +134,53 @@ static void __attribute__((__noreturn__)) run_program(char **cmd_argv)
 	_exit((errno == ENOMEM) ? EX_OSERR : EX_UNAVAILABLE);
 }
 
+#ifdef HAVE_FCNTL_OFD_LOCKS
+static int flock_to_fcntl_type(int op)
+{
+	switch (op) {
+	case LOCK_EX:
+		return F_WRLCK;
+	case LOCK_SH:
+		return F_RDLCK;
+	case LOCK_UN:
+		return F_UNLCK;
+	default:
+		errx(EX_SOFTWARE, _("internal error, unknown operation %d"), op);
+	}
+}
+
+static int fcntl_lock(int fd, int op, int block)
+{
+	struct flock arg = {
+		.l_type = flock_to_fcntl_type(op),
+		.l_whence = SEEK_SET,
+		.l_start = 0,
+		.l_len = 0,
+	};
+	int cmd = (block & LOCK_NB) ? F_OFD_SETLK : F_OFD_SETLKW;
+	return fcntl(fd, cmd, &arg);
+}
+#endif
+
+static int do_lock(int api, int fd, int op, int block)
+{
+	switch (api) {
+	case API_FLOCK:
+		return flock(fd, op | block);
+#ifdef HAVE_FCNTL_OFD_LOCKS
+	case API_FCNTL_OFD:
+		return fcntl_lock(fd, op, block);
+#endif
+	/*
+	 * Should never happen, api can never have values other than
+	 * API_*, and must be API_FLOCK when !HAVE_FCNTL_OFD_LOCKS.
+	 */
+	default:
+		errx(EX_SOFTWARE, _("internal error, unknown api %d"), api);
+	}
+}
+
+
 int main(int argc, char *argv[])
 {
 	struct ul_timer timer;
@@ -140,6 +195,7 @@ int main(int argc, char *argv[])
 	int no_fork = 0;
 	int status;
 	int verbose = 0;
+	int api = API_FLOCK;
 	struct timeval time_start = { 0 }, time_done = { 0 };
 	/*
 	 * The default exit code for lock conflict or timeout
@@ -149,7 +205,8 @@ int main(int argc, char *argv[])
 	char **cmd_argv = NULL, *sh_c_argv[4];
 	const char *filename = NULL;
 	enum {
-		OPT_VERBOSE = CHAR_MAX + 1
+		OPT_VERBOSE = CHAR_MAX + 1,
+		OPT_FCNTL,
 	};
 	static const struct option long_options[] = {
 		{"shared", no_argument, NULL, 's'},
@@ -163,6 +220,9 @@ int main(int argc, char *argv[])
 		{"close", no_argument, NULL, 'o'},
 		{"no-fork", no_argument, NULL, 'F'},
 		{"verbose", no_argument, NULL, OPT_VERBOSE},
+#ifdef HAVE_FCNTL_OFD_LOCKS
+		{"fcntl", no_argument, NULL, OPT_FCNTL},
+#endif
 		{"help", no_argument, NULL, 'h'},
 		{"version", no_argument, NULL, 'V'},
 		{NULL, 0, NULL, 0}
@@ -217,6 +277,11 @@ int main(int argc, char *argv[])
 			if (conflict_exit_code < 0 || conflict_exit_code > 255)
 				errx(EX_USAGE, _("exit code out of range (expected 0 to 255)"));
 			break;
+#ifdef HAVE_FCNTL_OFD_LOCKS
+		case OPT_FCNTL:
+			api = API_FCNTL_OFD;
+			break;
+#endif
 		case OPT_VERBOSE:
 			verbose = 1;
 			break;
@@ -234,6 +299,13 @@ int main(int argc, char *argv[])
 		errx(EX_USAGE,
 			_("the --no-fork and --close options are incompatible"));
 
+	/*
+	 * For fcntl(F_OFD_SETLK), an exclusive lock requires that the
+	 * file is open for write.
+	 */
+	if (api != API_FLOCK && type == LOCK_EX)
+		open_flags = O_WRONLY;
+
 	if (argc > optind + 1) {
 		/* Run command */
 		if (!strcmp(argv[optind + 1], "-c") ||
@@ -280,9 +352,15 @@ int main(int argc, char *argv[])
 
 	if (verbose)
 		gettime_monotonic(&time_start);
-	while (flock(fd, type | block)) {
+	while (do_lock(api, fd, type, block)) {
 		switch (errno) {
 		case EWOULDBLOCK:
+			/*
+			 * Per the man page, for fcntl(), EACCES may
+			 * be returned and means the same as
+			 * EAGAIN/EWOULDBLOCK.
+			 */
+		case EACCES:
 			/* -n option set and failed to lock. */
 			if (verbose)
 				warnx(_("failed to get lock"));
-- 
2.40.1.1.g1c60b9335d





[Index of Archives]     [Netdev]     [Ethernet Bridging]     [Linux Wireless]     [Kernel Newbies]     [Security]     [Linux for Hams]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux Admin]     [Samba]

  Powered by Linux