3 * Lock a file, run a program
5 * (c) 2003 Mark Wooding
8 /*----- Licensing notice --------------------------------------------------*
10 * This file is part of the Toys utilties collection.
12 * Toys is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * Toys is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with Toys; if not, write to the Free Software Foundation,
24 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27 /*----- Header files ------------------------------------------------------*/
37 #include <sys/types.h>
44 #include <mLib/mdwopt.h>
45 #include <mLib/quis.h>
46 #include <mLib/report.h>
48 /*----- Static variables --------------------------------------------------*/
53 /*----- Main code ---------------------------------------------------------*/
55 static void alrm(int s
) { longjmp(jmp
, 1); }
57 static void usage(FILE *fp
)
60 "Usage: $ [-cfwx] [-p REALPROG] [-t TIMEOUT] FILE PROG ARGS...\n");
63 static void version(FILE *fp
)
64 { pquis(fp
, "$ (version " VERSION
")\n"); }
66 static void help(FILE *fp
)
72 Lock FILE and run PROG, passing ARGS. Options are:\n\
74 -h, --help Show this help message.\n\
75 -v, --version Show version string.\n\
76 -u, --usage Show terse usage summary.\n\
78 -c, --[no-]create Create FILE if it doesn't exist [default: on].\n\
79 -f, --[no-]fail Fail if the file is already locked [default: off].\n\
80 -w, --[no-]wait Wait for the lock to be available [default: off].\n\
81 -x, --[no-]exclusive Get an exclusive (writer) lock [default: on].\n\
82 -p, --program=REALPROG Run REALPROG instead of PROG.\n\
83 -t, --timeout=TIME Wait for TIME for lock to become available.\n\
87 int main(int argc
, char *argv
[])
92 void (*oalrm
)(int) = 0;
110 unsigned f
= f_create
| f_excl
;
115 static const struct option opts
[] = {
116 { "help", 0, 0, 'h' },
117 { "version", 0, 0, 'v' },
118 { "usage", 0, 0, 'u' },
119 { "wait", OPTF_NEGATE
, 0, 'w' },
120 { "fail", OPTF_NEGATE
, 0, 'f' },
121 { "create", OPTF_NEGATE
, 0, 'c' },
122 { "program", OPTF_ARGREQ
, 0, 'p' },
123 { "timeout", OPTF_ARGREQ
, 0, 't' },
124 { "exclusive", OPTF_NEGATE
, 0, 'x' },
128 int i
= mdwopt(argc
, argv
, "-hvuw+f+c+x+p:t:", opts
,
129 0, 0, OPTF_NEGATION
);
132 case 'h': help(stdout
); exit(0);
133 case 'v': version(stdout
); exit(0);
134 case 'u': usage(stdout
); exit(0);
135 case 'w': f
|= f_wait
; break;
136 case 'w' | OPTF_NEGATED
: f
&= ~f_wait
; break;
137 case 'f': f
|= f_fail
; break;
138 case 'f' | OPTF_NEGATED
: f
&= ~f_fail
; break;
139 case 'c': f
|= f_create
; break;
140 case 'c' | OPTF_NEGATED
: f
&= ~f_create
; break;
141 case 'x': f
|= f_excl
; break;
142 case 'x' | OPTF_NEGATED
: f
&= ~f_excl
; break;
145 t
= strtol(optarg
, &p
, 0);
152 default: die(111, "unknown time unit `%c'", *p
);
154 if (*p
|| t
< 0 || errno
) die(111, "bad time value `%s'", optarg
);
157 case 'p': prog
= optarg
; break;
159 if (file
) { optind
--; goto doneopts
; }
162 default: f
|= f_bogus
; break;
167 if (f
& f_bogus
|| argc
- optind
< 1) {
173 if (!prog
) prog
= av
[0];
174 oflag
= f
& f_excl ? O_RDWR
: O_RDONLY
;
175 if (f
& f_create
) oflag
|= O_CREAT
;
176 l
.l_type
= f
& f_excl ? F_WRLCK
: F_RDLCK
;
177 l
.l_whence
= SEEK_SET
;
187 oalrm
= signal(SIGALRM
, alrm
);
188 if (t
>= 0) alarm(t
);
190 if ((fd
= open(file
, oflag
, 0666)) < 0)
191 die(111, "error opening `%s': %s", file
, strerror(errno
));
193 die(111, "error from fstat on `%s': %s", file
, strerror(errno
));
194 err
= fcntl(fd
, f
& f_wait ? F_SETLKW
: F_SETLK
, &l
) >= 0 ?
0 : errno
;
195 /* It's tempting to `optimize' this code by opening a new file descriptor
196 * here so as to elide the additional call to fstat(2) above. But this
197 * doesn't work: if we successfully acquire the lock, we then have two file
198 * descriptors open on the lock file, so we have to close one -- but, under
199 * the daft fcntl(2) rules, even closing `nfd' will release the lock
202 if (stat(file
, &nst
)) {
203 if (errno
== ENOENT
) { close(fd
); goto again
; }
204 else die(111, "error from stat on `%s': %s", file
, strerror(errno
));
206 if (st
.st_dev
!= nst
.st_dev
|| st
.st_ino
!= nst
.st_ino
)
207 { close(fd
); goto again
; }
209 signal(SIGALRM
, oalrm
);
214 if (nt
> ot
) raise(SIGALRM
);
220 #if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN
224 if (!(f
& f_fail
)) exit(0);
226 die(111, "error locking `%s': %s", file
, strerror(errno
));
228 if ((kid
= fork()) < 0) die(111, "error from fork: %s", strerror(errno
));
232 die(111, "couldn't exec `%s': %s", prog
, strerror(errno
));
234 if (waitpid(kid
, &rc
, 0) < 0)
235 die(EXIT_FAILURE
, "error from wait: %s", strerror(errno
));
237 l
.l_whence
= SEEK_SET
;
240 fcntl(fd
, F_SETLK
, &l
);
241 if (fd
>= 0) close(fd
);
242 if (WIFEXITED(rc
)) exit(WEXITSTATUS(rc
));
243 else exit(128 + WTERMSIG(rc
));
246 /*----- That's all, folks -------------------------------------------------*/