1 /* $NetBSD: shlock.c,v 1.8 2004/07/13 12:16:12 wiz Exp $ */ 2 3 /* 4 ** Program to produce reliable locks for shell scripts. 5 ** Algorithm suggested by Peter Honeyman, January 1984, 6 ** in connection with HoneyDanBer UUCP. 7 ** 8 ** I tried extending this to handle shared locks in November 1987, 9 ** and ran into to some fundamental problems: 10 ** 11 ** Neither 4.3 BSD nor System V have an open(2) with locking, 12 ** so that you can open a file and have it locked as soon as 13 ** it's real; you have to make two system calls, and there's 14 ** a race... 15 ** 16 ** When removing dead process id's from a list in a file, 17 ** you need to truncate the file (you don't want to create a 18 ** new one; see above); unfortunately for the portability of 19 ** this program, only 4.3 BSD has ftruncate(2). 20 ** 21 ** Erik E. Fair <fair@ucbarpa.berkeley.edu>, November 8, 1987 22 ** 23 ** Extensions for UUCP style locks (i.e. pid is an int in the file, 24 ** rather than an ASCII string). Also fix long standing bug with 25 ** full file systems and temporary files. 26 ** 27 ** Erik E. Fair <fair@apple.com>, November 12, 1989 28 ** 29 ** ANSIfy the code somewhat to make gcc -Wall happy with the code. 30 ** Submit to NetBSD 31 ** 32 ** Erik E. Fair <fair@clock.org>, May 20, 1997 33 */ 34 35 #include <sys/cdefs.h> 36 37 #ifndef lint 38 __RCSID("$NetBSD: shlock.c,v 1.8 2004/07/13 12:16:12 wiz Exp $"); 39 #endif 40 41 #include <sys/types.h> 42 #include <sys/file.h> 43 #include <fcntl.h> /* Needed on hpux */ 44 #include <stdio.h> 45 #include <signal.h> 46 #include <errno.h> 47 #include <string.h> 48 #include <unistd.h> 49 #include <stdlib.h> 50 51 #define LOCK_SET 0 52 #define LOCK_FAIL 1 53 54 #define LOCK_GOOD 0 55 #define LOCK_BAD 1 56 57 #define FAIL (-1) 58 59 #define TRUE 1 60 #define FALSE 0 61 62 int Debug = FALSE; 63 char *Pname; 64 const char USAGE[] = "%s: USAGE: %s [-du] [-p PID] -f file\n"; 65 const char E_unlk[] = "%s: unlink(%s): %s\n"; 66 const char E_open[] = "%s: open(%s): %s\n"; 67 68 #define dprintf if (Debug) printf 69 70 /* 71 ** Prototypes to make the ANSI compilers happy 72 ** Didn't lint used to do type and argument checking? 73 ** (and wasn't that sufficient?) 74 */ 75 76 /* the following is in case you need to make the prototypes go away. */ 77 char *xtmpfile(char *, pid_t, int); 78 int p_exists(pid_t); 79 int cklock(char *, int); 80 int mklock(char *, pid_t, int); 81 void bad_usage(void); 82 int main(int, char **); 83 84 /* 85 ** Create a temporary file, all ready to lock with. 86 ** The file arg is so we get the filename right, if he 87 ** gave us a full path, instead of using the current directory 88 ** which might not be in the same filesystem. 89 */ 90 char * 91 xtmpfile(char *file, __pid_t pid, int uucpstyle) 92 { 93 int fd; 94 int len; 95 char *cp, buf[BUFSIZ]; 96 static char tempname[BUFSIZ]; 97 98 sprintf(buf, "shlock%ld", (u_long)getpid()); 99 if ((cp = strrchr(strcpy(tempname, file), '/')) != (char *)NULL) { 100 *++cp = '\0'; 101 (void) strcat(tempname, buf); 102 } else 103 (void) strcpy(tempname, buf); 104 dprintf("%s: temporary filename: %s\n", Pname, tempname); 105 106 sprintf(buf, "%ld\n", (u_long)pid); 107 len = strlen(buf); 108 openloop: 109 if ((fd = open(tempname, O_RDWR|O_CREAT|O_EXCL, 0644)) < 0) { 110 switch(errno) { 111 case EEXIST: 112 dprintf("%s: file %s exists already.\n", 113 Pname, tempname); 114 if (unlink(tempname) < 0) { 115 fprintf(stderr, E_unlk, 116 Pname, tempname, strerror(errno)); 117 return((char *)NULL); 118 } 119 /* 120 ** Further profanity 121 */ 122 goto openloop; 123 default: 124 fprintf(stderr, E_open, 125 Pname, tempname, strerror(errno)); 126 return((char *)NULL); 127 } 128 } 129 130 /* 131 ** Write the PID into the temporary file before attempting to link 132 ** to the actual lock file. That way we have a valid lock the instant 133 ** the link succeeds. 134 */ 135 if (uucpstyle ? 136 (write(fd, &pid, sizeof(pid)) != sizeof(pid)) : 137 (write(fd, buf, len) < 0)) 138 { 139 fprintf(stderr, "%s: write(%s,%ld): %s\n", 140 Pname, tempname, (u_long)pid, strerror(errno)); 141 (void) close(fd); 142 if (unlink(tempname) < 0) { 143 fprintf(stderr, E_unlk, 144 Pname, tempname, strerror(errno)); 145 } 146 return((char *)NULL); 147 } 148 (void) close(fd); 149 return(tempname); 150 } 151 152 /* 153 ** Does the PID exist? 154 ** Send null signal to find out. 155 */ 156 int 157 p_exists(__pid_t pid) 158 { 159 dprintf("%s: process %ld is ", Pname, (u_long)pid); 160 if (pid <= 0) { 161 dprintf("invalid\n"); 162 return(FALSE); 163 } 164 if (kill(pid, 0) < 0) { 165 switch(errno) { 166 case ESRCH: 167 dprintf("dead\n"); 168 return(FALSE); /* pid does not exist */ 169 case EPERM: 170 dprintf("alive\n"); 171 return(TRUE); /* pid exists */ 172 default: 173 dprintf("state unknown: %s\n", strerror(errno)); 174 return(TRUE); /* be conservative */ 175 } 176 } 177 dprintf("alive\n"); 178 return(TRUE); /* pid exists */ 179 } 180 181 /* 182 ** Check the validity of an existing lock file. 183 ** 184 ** Read the PID out of the lock 185 ** Send a null signal to determine whether that PID still exists 186 ** Existence (or not) determines the validity of the lock. 187 ** 188 ** Two bigs wins to this algorithm: 189 ** 190 ** o Locks do not survive crashes of either the system or the 191 ** application by any appreciable period of time. 192 ** 193 ** o No clean up to do if the system or application crashes. 194 ** 195 */ 196 int 197 cklock(char *file, int uucpstyle) 198 { 199 int fd = open(file, O_RDONLY); 200 ssize_t len; 201 pid_t pid; 202 char buf[BUFSIZ]; 203 204 dprintf("%s: checking extant lock <%s>\n", Pname, file); 205 if (fd < 0) { 206 if (errno != ENOENT) 207 fprintf(stderr, E_open, Pname, file, strerror(errno)); 208 return(TRUE); /* might or might not; conservatism */ 209 } 210 211 if (uucpstyle ? 212 ((len = read(fd, &pid, sizeof(pid))) != sizeof(pid)) : 213 ((len = read(fd, buf, sizeof(buf))) <= 0)) 214 { 215 close(fd); 216 dprintf("%s: lock file format error\n", Pname); 217 return(FALSE); 218 } 219 close(fd); 220 buf[len + 1] = '\0'; 221 return(p_exists(uucpstyle ? pid : atoi(buf))); 222 } 223 224 int 225 mklock(char *file, __pid_t pid, int uucpstyle) 226 { 227 char *tmp; 228 int retcode = FALSE; 229 230 dprintf("%s: trying lock <%s> for process %ld\n", Pname, file, 231 (u_long)pid); 232 if ((tmp = xtmpfile(file, pid, uucpstyle)) == (char *)NULL) 233 return(FALSE); 234 235 linkloop: 236 if (link(tmp, file) < 0) { 237 switch(errno) { 238 case EEXIST: 239 dprintf("%s: lock <%s> already exists\n", Pname, file); 240 if (cklock(file, uucpstyle)) { 241 dprintf("%s: extant lock is valid\n", Pname); 242 break; 243 } else { 244 dprintf("%s: lock is invalid, removing\n", 245 Pname); 246 if (unlink(file) < 0) { 247 fprintf(stderr, E_unlk, 248 Pname, file, strerror(errno)); 249 break; 250 } 251 } 252 /* 253 ** I hereby profane the god of structured programming, 254 ** Edsgar Dijkstra 255 */ 256 goto linkloop; 257 default: 258 fprintf(stderr, "%s: link(%s, %s): %s\n", 259 Pname, tmp, file, strerror(errno)); 260 break; 261 } 262 } else { 263 dprintf("%s: got lock <%s>\n", Pname, file); 264 retcode = TRUE; 265 } 266 if (unlink(tmp) < 0) { 267 fprintf(stderr, E_unlk, Pname, tmp, strerror(errno)); 268 } 269 return(retcode); 270 } 271 272 void 273 bad_usage(void) 274 { 275 fprintf(stderr, USAGE, Pname, Pname); 276 exit(LOCK_FAIL); 277 } 278 279 int 280 main(int ac, char **av) 281 { 282 int x; 283 char *file = (char *)NULL; 284 pid_t pid = 0; 285 int uucpstyle = FALSE; /* indicating UUCP style locks */ 286 int only_check = TRUE; /* don't make a lock */ 287 288 Pname = ((Pname = strrchr(av[0], '/')) ? Pname + 1 : av[0]); 289 290 for(x = 1; x < ac; x++) { 291 if (av[x][0] == '-') { 292 switch(av[x][1]) { 293 case 'u': 294 uucpstyle = TRUE; 295 break; 296 case 'd': 297 Debug = TRUE; 298 break; 299 case 'p': 300 if (strlen(av[x]) > 2) { 301 pid = atoi(&av[x][2]); 302 } else { 303 if (++x >= ac) { 304 bad_usage(); 305 } 306 pid = atoi(av[x]); 307 } 308 only_check = FALSE; /* wants one */ 309 break; 310 case 'f': 311 if (strlen(av[x]) > 2) { 312 file = &av[x][2]; 313 } else { 314 if (++x >= ac) { 315 bad_usage(); 316 } 317 file = av[x]; 318 } 319 break; 320 default: 321 bad_usage(); 322 } 323 } 324 } 325 326 if (file == (char *)NULL || (!only_check && pid <= 0)) { 327 bad_usage(); 328 } 329 330 if (only_check) { 331 exit(cklock(file, uucpstyle) ? LOCK_GOOD : LOCK_BAD); 332 } 333 334 exit(mklock(file, pid, uucpstyle) ? LOCK_SET : LOCK_FAIL); 335 } 336