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#pragma ident	"%Z%%M%	%I%	%E% SMI"
27
28#include <stdio.h>
29#include <stdlib.h>
30#include <unistd.h>
31#include <string.h>
32#include <fcntl.h>
33#include <sys/types.h>
34#include <sys/param.h>
35#include <sys/stat.h>
36#include <errno.h>
37
38
39/*
40 * Format a message telling why the lock could not be created.
41 */
42/* VARARGS5 */
43static void
44file_lock_error(char *msg, char *file, int err, const char *str,
45    char *arg1, char *arg2, size_t mlen)
46{
47	size_t	len;
48	char	*errstr;
49
50	(void) snprintf(msg, mlen, "Could not lock file `%s'; ", file);
51	len = strlen(msg);
52	(void) snprintf(&msg[len], (mlen - len), str, arg1, arg2);
53	(void) strcat(msg, " failed - ");
54	if ((errstr = strerror(err)) != NULL) {
55		(void) strlcat(msg, errstr, mlen);
56	} else {
57		len = strlen(msg);
58		(void) sprintf(&msg[len], "errno %d", err);
59	}
60}
61
62/*
63 * This code stolen from the NSE library and changed to not depend
64 * upon any NSE routines or header files.
65 *
66 * Simple file locking.
67 * Create a symlink to a file.  The "test and set" will be
68 * atomic as creating the symlink provides both functions.
69 *
70 * The timeout value specifies how long to wait for stale locks
71 * to disappear.  If the lock is more than 'timeout' seconds old
72 * then it is ok to blow it away.  This part has a small window
73 * of vunerability as the operations of testing the time,
74 * removing the lock and creating a new one are not atomic.
75 * It would be possible for two processes to both decide to blow
76 * away the lock and then have process A remove the lock and establish
77 * its own, and then then have process B remove the lock which accidentily
78 * removes A's lock rather than the stale one.
79 *
80 * A further complication is with the NFS.  If the file in question is
81 * being served by an NFS server, then its time is set by that server.
82 * We can not use the time on the client machine to check for a stale
83 * lock.  Therefore, a temp file on the server is created to get
84 * the servers current time.
85 *
86 * Returns an error message.  NULL return means the lock was obtained.
87 *
88 */
89char *
90file_lock(char *name, char *lockname, int timeout)
91{
92	int		r;
93	int		fd;
94	struct	stat	statb;
95	struct	stat	fs_statb;
96	char		tmpname[MAXPATHLEN];
97	static	char	msg[MAXPATHLEN];
98
99	if (timeout <= 0) {
100		timeout = 15;
101	}
102	for (;;) {
103		r = symlink(name, lockname);
104		if (r == 0) {
105			return (NULL);
106		}
107		if (errno != EEXIST) {
108			file_lock_error(msg, name, errno,
109			    (const char *)"symlink(%s, %s)", name, lockname,
110			    sizeof (msg));
111			return (msg);
112		}
113		for (;;) {
114			(void) sleep(1);
115			r = lstat(lockname, &statb);
116			if (r == -1) {
117				/*
118				 * The lock must have just gone away - try
119				 * again.
120				 */
121				break;
122			}
123
124			/*
125			 * With the NFS the time given a file is the time on
126			 * the file server.  This time may vary from the
127			 * client's time.  Therefore, we create a tmpfile in
128			 * the same directory to establish the time on the
129			 * server and use this time to see if the lock has
130			 * expired.
131			 */
132			(void) snprintf(tmpname, MAXPATHLEN, "%s.XXXXXX",
133			    lockname);
134			(void) mktemp(tmpname);
135			fd = creat(tmpname, 0666);
136			if (fd != -1) {
137				(void) close(fd);
138			} else {
139				file_lock_error(msg, name, errno,
140				    (const char *)"creat(%s)", tmpname, 0,
141				    sizeof (msg));
142				return (msg);
143			}
144			if (stat(tmpname, &fs_statb) == -1) {
145				file_lock_error(msg, name, errno,
146				    (const char *)"stat(%s)", tmpname, 0,
147				    sizeof (msg));
148				return (msg);
149			}
150			(void) unlink(tmpname);
151			if (statb.st_mtime + timeout < fs_statb.st_mtime) {
152				/*
153				 * The lock has expired - blow it away.
154				 */
155				(void) unlink(lockname);
156				break;
157			}
158		}
159	}
160	/* NOTREACHED */
161}
162