Re: [PATCH] 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-ofd 4
>   <access/modify /etc/shadow ...>
>   flock --fcntl-ofd --unlock 4 # or just exit
> 
> without conflicting with passwd(1) or other utilities that
> access/modify /etc/shadow.
> 
> The option name is a bit verbose, and no single-letter shorthand is
> defined, because this is somewhat low-level and the user really needs
> to know what he is doing.
> 
> Signed-off-by: Rasmus Villemoes <rasmus.villemoes@xxxxxxxxx>
> 
> ---
> 
> Both my autotools and meson fu are weak to non-existing, so I don't
> know if I've written the "test if the header exposes this macro"
> correctly.
> 
> I'm not at all married to the option name. I also considered just
> making it --fcntl, with the possiblity of making that grow an optional
> argument (for example --fcntl=posix with plain --fcntl being an alias
> for --fcntl=ofd) should anyone ever figure out a use for the plain
> F_SETLK, perhaps just for testing.
> 
> 
>  configure.ac      |  6 +++++
>  meson.build       |  3 +++
>  sys-utils/flock.c | 60 +++++++++++++++++++++++++++++++++++++++++++++--
>  3 files changed, 67 insertions(+), 2 deletions(-)

You may have to update sys-utils/flock.1.adoc and the completion rule bash-completion/flock
when adding a new optoin.

> 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..40751517d 100644
> --- a/sys-utils/flock.c
> +++ b/sys-utils/flock.c
> @@ -70,6 +70,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-ofd          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 +129,38 @@ static void __attribute__((__noreturn__)) run_program(char **cmd_argv)
>  	_exit((errno == ENOMEM) ? EX_OSERR : EX_UNAVAILABLE);
>  }
>  
> +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);
> +        }
> +}

Don't you need wrap flock_to_fcntl_type with #ifdef HAVE_FCNTL_OFD_LOCKS/#endif?

> +static int do_fcntl_lock(int fd, int op, int block)
> +{
> +#ifdef HAVE_FCNTL_OFD_LOCKS
> +	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);
> +#else
> +	/* Should never happen, nothing can ever set use_fcntl_ofd when !HAVE_FCNTL_OFD_LOCKS. */
> +	errno = ENOSYS;
> +	return -1;
> +#endif
> +}
> +
>  int main(int argc, char *argv[])
>  {
>  	struct ul_timer timer;
> @@ -140,6 +175,7 @@ int main(int argc, char *argv[])
>  	int no_fork = 0;
>  	int status;
>  	int verbose = 0;
> +	int use_fcntl_ofd = 0;
>  	struct timeval time_start = { 0 }, time_done = { 0 };
>  	/*
>  	 * The default exit code for lock conflict or timeout
> @@ -149,7 +185,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_OFD,
>  	};
>  	static const struct option long_options[] = {
>  		{"shared", no_argument, NULL, 's'},
> @@ -163,6 +200,7 @@ int main(int argc, char *argv[])
>  		{"close", no_argument, NULL, 'o'},
>  		{"no-fork", no_argument, NULL, 'F'},
>  		{"verbose", no_argument, NULL, OPT_VERBOSE},
> +		{"fcntl-ofd", no_argument, NULL, OPT_FCNTL_OFD},
>  		{"help", no_argument, NULL, 'h'},
>  		{"version", no_argument, NULL, 'V'},
>  		{NULL, 0, NULL, 0}
> @@ -217,6 +255,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_OFD:
> +			use_fcntl_ofd = 1;
> +			break;
> +#endif
>  		case OPT_VERBOSE:
>  			verbose = 1;
>  			break;
> @@ -234,6 +277,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 (use_fcntl_ofd && type == LOCK_EX)
> +		open_flags = O_WRONLY;
> +
>  	if (argc > optind + 1) {
>  		/* Run command */
>  		if (!strcmp(argv[optind + 1], "-c") ||
> @@ -280,9 +330,15 @@ int main(int argc, char *argv[])
>  
>  	if (verbose)
>  		gettime_monotonic(&time_start);
> -	while (flock(fd, type | block)) {
> +	while (use_fcntl_ofd ? do_fcntl_lock(fd, type, block) : flock(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
> 
> 

Masatake YAMATO





[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