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