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