1 /*
   2  * Copyright (C) 2008 Paul Armstrong. All rights reserved.
   3  *
   4  *  Redistribution and use in source and binary forms, with or without
   5  *  modification, are permitted provided that the following conditions
   6  *  are met:
   7  *  1. Redistributions of source code must retain the above copyright
   8  *     notice, this list of conditions and the following disclaimer.
   9  *  2. Redistributions in binary form must reproduce the above copyright
  10  *     notice, this list of conditions and the following disclaimer in the
  11  *     documentation and/or other materials provided with the distribution.
  12  *
  13  *  THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  14  *  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  15  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  16  *  ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
  17  *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  18  *  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  19  *  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  20  *  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  21  *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  22  *  OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  23  *  SUCH DAMAGE.
  24  */
  25 
  26 #include <ctype.h>
  27 #include <err.h>
  28 #include <errno.h>
  29 #include <limits.h>
  30 #include <locale.h>
  31 #include <fcntl.h>
  32 #include <signal.h>
  33 #include <stdio.h>
  34 #include <stdlib.h>
  35 #include <sys/types.h>
  36 #include <sys/stat.h>
  37 #include <unistd.h>
  38 #include <wait.h>
  39 
  40 #define SLEEP_TIME 1 /* Sleep time between parent checks */
  41 
  42 #if !defined(TEXT_DOMAIN)
  43 #define TEXT_DOMAIN "SYS_TEST"
  44 #endif
  45 
  46 /* globals for use in signal handlers */
  47 int pid = 0;
  48 boolean_t lock_existed = B_FALSE;
  49 char *file_name = NULL;
  50 
  51 static void
  52 usage(void)
  53 {
  54         (void) fprintf(stderr,
  55             gettext("Usage: lockf -l|-t|-w -f /path/to/lock | -u pid\n"));
  56         exit(EXIT_FAILURE);
  57 }
  58 
  59 /*ARGSUSED*/
  60 static void
  61 print_pid(int signal_number)
  62 {
  63         (void) printf("%d\n", pid);
  64         exit(EXIT_SUCCESS);
  65 }
  66 
  67 /*ARGSUSED*/
  68 static void
  69 cleanup(int signal_handler)
  70 {
  71         if (!lock_existed) {
  72                 if (-1 == unlink(file_name))
  73                         perror(file_name);
  74         }
  75         exit(EXIT_SUCCESS);
  76 }
  77 
  78 int
  79 main(int argc, char **argv)
  80 {
  81         boolean_t arg_err = B_FALSE;
  82         boolean_t lock = B_FALSE, test = B_FALSE;
  83         boolean_t unlock = B_FALSE, wait_lock = B_FALSE;
  84         extern char *optarg;
  85         extern int optind;
  86         int calling_pid;
  87         int child_status;
  88         int filedes;
  89         int flag;
  90         int return_code;
  91         mode_t mode = S_IRUSR | S_IWUSR;
  92         struct stat *statbuf;
  93 
  94         (void) setlocale(LC_ALL, "");
  95         (void) textdomain(TEXT_DOMAIN);
  96 
  97         while ((flag = getopt(argc, argv, "f:ltu:w")) != -1) {
  98                 switch (flag) {
  99                 case 'f':
 100                         file_name = optarg;
 101                         break;
 102                 case 'l':
 103                         if (unlock || wait_lock || test)
 104                                 arg_err = B_TRUE;
 105                         lock = B_TRUE;
 106                         break;
 107                 case 't':
 108                         if (unlock || lock)
 109                                 arg_err = B_TRUE;
 110                         test = B_TRUE;
 111                         break;
 112                 case 'u':
 113                         if (lock || test)
 114                                 arg_err = B_TRUE;
 115                         unlock = B_TRUE;
 116                         pid = atoi(optarg);
 117                         break;
 118                 case 'w':
 119                         if (unlock || lock || test)
 120                                 arg_err = B_TRUE;
 121                         wait_lock = B_TRUE;
 122                         lock = B_TRUE;
 123                         break;
 124                 default:
 125                         usage();
 126                 }
 127         }
 128 
 129         if (1 == argc)
 130                 usage();
 131 
 132         argc -= optind;
 133         argv -= optind;
 134 
 135         if (1 <= argc)
 136                 usage();
 137 
 138         if (arg_err) {
 139                 (void) fprintf(stderr,
 140                     gettext("Use only one of -l, -t, -u or -w\n"));
 141                 usage();
 142         }
 143 
 144         if ((lock || test) && (NULL == file_name)) {
 145                 (void) fprintf(stderr,
 146                     gettext("-l, -t and -w require -f\n"));
 147                 usage();
 148         }
 149         if (unlock && pid <= 0) {
 150                 (void) fprintf(stderr,
 151                     gettext("-u requires a valid PID\n"));
 152                 usage();
 153         }
 154 
 155         if (unlock) {
 156                 if (-1 == kill(pid, SIGINT)) {
 157                         perror("kill");
 158                         return (EXIT_FAILURE);
 159                 } else {
 160                         return (EXIT_SUCCESS);
 161                 }
 162         }
 163 
 164         /* Make test cheap, do it before the fork and return immediately */
 165         if (test) {
 166                 statbuf = malloc(sizeof (struct stat));
 167                 if (NULL == statbuf) {
 168                         perror("malloc");
 169                         exit(EXIT_FAILURE);
 170                 }
 171                 if (stat(file_name, statbuf) == -1) {
 172                         switch (errno) {
 173                         case ENOENT:
 174                                 exit(EXIT_SUCCESS);
 175                                 /*NOTREACHED*/
 176                         case EACCES:
 177                                 err(EXIT_FAILURE,
 178                                     gettext("Bad permissions on lockfile"));
 179                                 /*NOTREACHED*/
 180                         case ENAMETOOLONG:
 181                                 err(EXIT_FAILURE, "lockfile name too long");
 182                         }
 183                 }
 184                 if ((filedes = open(file_name, O_WRONLY, mode)) == -1) {
 185                         perror(file_name);
 186                         return (EXIT_FAILURE);
 187                 }
 188                 return_code = lockf(filedes, F_TEST, 0);
 189                 if (return_code < 0)
 190                         return (EXIT_FAILURE);
 191                 return (EXIT_SUCCESS);
 192         }
 193 
 194         calling_pid = getppid();
 195         if (1 == calling_pid)
 196                 err(EXIT_FAILURE, "Calling process seems to have died early");
 197 
 198         if ((pid = fork()) < 0) {
 199                 perror("fork");
 200                 return (EXIT_FAILURE);
 201         }
 202 
 203         if (pid != 0) {
 204                 (void) signal(SIGUSR1, print_pid);
 205 
 206                 if (waitpid(pid, &child_status, 0) != pid) {
 207                         perror("waitpid");
 208                         exit(EXIT_FAILURE);
 209                 }
 210 
 211                 return (EXIT_FAILURE);
 212         } else {
 213 
 214                 (void) close(STDIN_FILENO);
 215                 (void) close(STDOUT_FILENO);
 216 
 217                 return_code = chdir("/");
 218                 if (return_code < 0) {
 219                         perror("chdir");
 220                         return (EXIT_FAILURE);
 221                 }
 222 
 223                 /* Other users shouldn't be trying to write to our lock file */
 224                 (void) umask(077);
 225 
 226                 /*
 227                  * Note if the file existed beforehand so we can avoid deleting
 228                  * it when we clean up
 229                  */
 230                 if ((filedes =
 231                     open(file_name, O_WRONLY | O_CREAT | O_EXCL, mode)) == -1) {
 232 
 233                         lock_existed = B_TRUE;
 234 
 235                         if ((filedes =
 236                             open(file_name, O_WRONLY | O_CREAT, mode)) == -1) {
 237                                 perror(file_name);
 238                                 exit(EXIT_FAILURE);
 239                         }
 240                 }
 241 
 242                 (void) signal(SIGINT, cleanup);
 243                 (void) signal(SIGQUIT, cleanup);
 244                 (void) signal(SIGABRT, cleanup);
 245                 (void) signal(SIGTERM, cleanup);
 246 
 247                 if (wait_lock) {
 248                         return_code = lockf(filedes, F_LOCK, 0);
 249                 } else {
 250                         return_code = lockf(filedes, F_TLOCK, 0);
 251                 }
 252 
 253                 if (return_code < 0)
 254                         return (EXIT_FAILURE);
 255 
 256                 /* Once we have a lock (or not), then kill the parent */
 257                 if (-1 == kill(getppid(), SIGUSR1)) {
 258                         perror("kill");
 259                         return (EXIT_FAILURE);
 260                 }
 261 
 262                 while (calling_pid) {
 263                         (void) sleep(SLEEP_TIME);
 264                         if (-1 == kill(calling_pid, 0))
 265                                 calling_pid = 0;
 266                 }
 267 
 268                 cleanup(NULL);
 269 
 270                 return (EXIT_SUCCESS);
 271         }
 272 }