1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License"). You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22 /*
23 * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
25 */
26
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <unistd.h>
30 #include <string.h>
31 #include <fcntl.h>
32 #include <sys/types.h>
33 #include <sys/param.h>
34 #include <sys/stat.h>
35 #include <errno.h>
36
37
38 /*
39 * Format a message telling why the lock could not be created.
40 */
41 /* VARARGS5 */
42 static void
file_lock_error(char * msg,char * file,int err,const char * str,char * arg1,char * arg2,size_t mlen)43 file_lock_error(char *msg, char *file, int err, const char *str,
44 char *arg1, char *arg2, size_t mlen)
45 {
46 size_t len;
47 char *errstr;
48
49 (void) snprintf(msg, mlen, "Could not lock file `%s'; ", file);
50 len = strlen(msg);
51 (void) snprintf(&msg[len], (mlen - len), str, arg1, arg2);
52 (void) strcat(msg, " failed - ");
53 if ((errstr = strerror(err)) != NULL) {
54 (void) strlcat(msg, errstr, mlen);
55 } else {
56 len = strlen(msg);
57 (void) sprintf(&msg[len], "errno %d", err);
58 }
59 }
60
61 /*
62 * This code stolen from the NSE library and changed to not depend
63 * upon any NSE routines or header files.
64 *
65 * Simple file locking.
66 * Create a symlink to a file. The "test and set" will be
67 * atomic as creating the symlink provides both functions.
68 *
69 * The timeout value specifies how long to wait for stale locks
70 * to disappear. If the lock is more than 'timeout' seconds old
71 * then it is ok to blow it away. This part has a small window
72 * of vunerability as the operations of testing the time,
73 * removing the lock and creating a new one are not atomic.
74 * It would be possible for two processes to both decide to blow
75 * away the lock and then have process A remove the lock and establish
76 * its own, and then then have process B remove the lock which accidentily
77 * removes A's lock rather than the stale one.
78 *
79 * A further complication is with the NFS. If the file in question is
80 * being served by an NFS server, then its time is set by that server.
81 * We can not use the time on the client machine to check for a stale
82 * lock. Therefore, a temp file on the server is created to get
83 * the servers current time.
84 *
85 * Returns an error message. NULL return means the lock was obtained.
86 *
87 */
88 char *
file_lock(char * name,char * lockname,int timeout)89 file_lock(char *name, char *lockname, int timeout)
90 {
91 int r;
92 int fd;
93 struct stat statb;
94 struct stat fs_statb;
95 char tmpname[MAXPATHLEN];
96 static char msg[MAXPATHLEN];
97
98 if (timeout <= 0) {
99 timeout = 15;
100 }
101 for (;;) {
102 r = symlink(name, lockname);
103 if (r == 0) {
104 return (NULL);
105 }
106 if (errno != EEXIST) {
107 file_lock_error(msg, name, errno,
108 (const char *)"symlink(%s, %s)", name, lockname,
109 sizeof (msg));
110 return (msg);
111 }
112 for (;;) {
113 (void) sleep(1);
114 r = lstat(lockname, &statb);
115 if (r == -1) {
116 /*
117 * The lock must have just gone away - try
118 * again.
119 */
120 break;
121 }
122
123 /*
124 * With the NFS the time given a file is the time on
125 * the file server. This time may vary from the
126 * client's time. Therefore, we create a tmpfile in
127 * the same directory to establish the time on the
128 * server and use this time to see if the lock has
129 * expired.
130 */
131 (void) snprintf(tmpname, MAXPATHLEN, "%s.XXXXXX",
132 lockname);
133 (void) mktemp(tmpname);
134 fd = creat(tmpname, 0666);
135 if (fd != -1) {
136 (void) close(fd);
137 } else {
138 file_lock_error(msg, name, errno,
139 (const char *)"creat(%s)", tmpname, 0,
140 sizeof (msg));
141 return (msg);
142 }
143 if (stat(tmpname, &fs_statb) == -1) {
144 file_lock_error(msg, name, errno,
145 (const char *)"stat(%s)", tmpname, 0,
146 sizeof (msg));
147 return (msg);
148 }
149 (void) unlink(tmpname);
150 if (statb.st_mtime + timeout < fs_statb.st_mtime) {
151 /*
152 * The lock has expired - blow it away.
153 */
154 (void) unlink(lockname);
155 break;
156 }
157 }
158 }
159 /* NOTREACHED */
160 }
161