1354195Sbrooks/*	NetBSD: dotlock.c,v 1.11 2009/10/21 01:07:46 snj Exp	*/
2315512Sdchagin
3315512Sdchagin/*
4315512Sdchagin * Copyright (c) 1996 Christos Zoulas.  All rights reserved.
5315512Sdchagin *
6315512Sdchagin * Redistribution and use in source and binary forms, with or without
7315512Sdchagin * modification, are permitted provided that the following conditions
8315512Sdchagin * are met:
9315512Sdchagin * 1. Redistributions of source code must retain the above copyright
10315512Sdchagin *    notice, this list of conditions and the following disclaimer.
11315512Sdchagin * 2. Redistributions in binary form must reproduce the above copyright
12315512Sdchagin *    notice, this list of conditions and the following disclaimer in the
13315512Sdchagin *    documentation and/or other materials provided with the distribution.
14315512Sdchagin *
15315512Sdchagin * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16315512Sdchagin * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17315512Sdchagin * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18315512Sdchagin * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19315512Sdchagin * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20315512Sdchagin * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21315512Sdchagin * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22315512Sdchagin * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23315512Sdchagin * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24315512Sdchagin * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25315512Sdchagin */
26315512Sdchagin#include "sh.h"
27315512Sdchagin
28315512Sdchagin#include <stdio.h>
29315512Sdchagin#ifndef O_SYNC
30315512Sdchagin#define O_SYNC	0
31315512Sdchagin#endif
32315512Sdchagin
33315512Sdchagin#include "dotlock.h"
34315512Sdchagin
35315512Sdchaginstatic int create_exclusive(const char *);
36315512Sdchagin/*
37315512Sdchagin * Create a unique file. O_EXCL does not really work over NFS so we follow
38315512Sdchagin * the following trick: [Inspired by  S.R. van den Berg]
39315512Sdchagin *
40315512Sdchagin * - make a mostly unique filename and try to create it.
41315512Sdchagin * - link the unique filename to our target
42315512Sdchagin * - get the link count of the target
43315512Sdchagin * - unlink the mostly unique filename
44315512Sdchagin * - if the link count was 2, then we are ok; else we've failed.
45315512Sdchagin */
46315512Sdchaginstatic int
47315512Sdchagincreate_exclusive(const char *fname)
48315512Sdchagin{
49315512Sdchagin	char path[MAXPATHLEN], hostname[MAXHOSTNAMELEN + 1];
50315512Sdchagin	const char *ptr;
51315512Sdchagin	struct timeval tv;
52315512Sdchagin	pid_t pid;
53315512Sdchagin	size_t ntries, cookie;
54315512Sdchagin	int fd, serrno;
55315512Sdchagin	struct stat st;
56315512Sdchagin
57315512Sdchagin	(void)gettimeofday(&tv, NULL);
58315512Sdchagin	(void)gethostname(hostname, sizeof(hostname));
59315512Sdchagin	hostname[sizeof(hostname) - 1] = '\0';
60315512Sdchagin	pid = getpid();
61315512Sdchagin
62315512Sdchagin	cookie = pid ^ tv.tv_usec;
63315512Sdchagin
64315512Sdchagin	/*
65315512Sdchagin	 * We generate a semi-unique filename, from hostname.(pid ^ usec)
66315512Sdchagin	 */
67315512Sdchagin	if ((ptr = strrchr(fname, '/')) == NULL)
68315512Sdchagin		ptr = fname;
69315512Sdchagin	else
70315512Sdchagin		ptr++;
71315512Sdchagin
72315512Sdchagin	(void)snprintf(path, sizeof(path), "%.*s.%s.%lx",
73315512Sdchagin	    (int)(ptr - fname), fname, hostname, (u_long)cookie);
74315512Sdchagin
75315512Sdchagin	/*
76315512Sdchagin	 * We try to create the unique filename.
77315512Sdchagin	 */
78315512Sdchagin	for (ntries = 0; ntries < 5; ntries++) {
79315512Sdchagin		fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL|O_SYNC, 0);
80315512Sdchagin		if (fd != -1) {
81315512Sdchagin			(void)close(fd);
82315512Sdchagin			break;
83315512Sdchagin		}
84315512Sdchagin		else if (errno == EEXIST)
85315512Sdchagin			continue;
86315512Sdchagin		else
87315512Sdchagin			return -1;
88315512Sdchagin	}
89315512Sdchagin
90315512Sdchagin	/*
91315512Sdchagin	 * We link the path to the name
92315512Sdchagin	 */
93315512Sdchagin	if (link(path, fname) == -1)
94315512Sdchagin		goto bad;
95315512Sdchagin
96315512Sdchagin	/*
97315512Sdchagin	 * Note that we stat our own exclusively created name, not the
98315512Sdchagin	 * destination, since the destination can be affected by others.
99315512Sdchagin	 */
100315512Sdchagin	if (stat(path, &st) == -1)
101315512Sdchagin		goto bad;
102315512Sdchagin
103315512Sdchagin	(void)unlink(path);
104315512Sdchagin
105315512Sdchagin	/*
106315512Sdchagin	 * If the number of links was two (one for the unique file and one
107315512Sdchagin	 * for the lock), we've won the race
108315512Sdchagin	 */
109315512Sdchagin	if (st.st_nlink != 2) {
110315512Sdchagin		errno = EEXIST;
111315512Sdchagin		return -1;
112315512Sdchagin	}
113315512Sdchagin	return 0;
114315512Sdchagin
115315512Sdchaginbad:
116315512Sdchagin	serrno = errno;
117315512Sdchagin	(void)unlink(path);
118315512Sdchagin	errno = serrno;
119315512Sdchagin	return -1;
120315512Sdchagin}
121315512Sdchagin
122315512Sdchagin/*
123315512Sdchagin * fname -- Pathname to lock
124315512Sdchagin * pollinterval -- Interval (miliseconds) to check for lock, -1 return
125315512Sdchagin */
126315512Sdchaginint
127315512Sdchagindot_lock(const char *fname, int pollinterval)
128315512Sdchagin{
129315512Sdchagin	char path[MAXPATHLEN];
130315512Sdchagin	sigset_t nset, oset;
131315512Sdchagin	int retval;
132315512Sdchagin
133315512Sdchagin	(void)sigemptyset(&nset);
134315512Sdchagin	(void)sigaddset(&nset, SIGHUP);
135315512Sdchagin	(void)sigaddset(&nset, SIGINT);
136315512Sdchagin	(void)sigaddset(&nset, SIGQUIT);
137315512Sdchagin	(void)sigaddset(&nset, SIGTERM);
138315512Sdchagin	(void)sigaddset(&nset, SIGTTIN);
139315512Sdchagin	(void)sigaddset(&nset, SIGTTOU);
140315512Sdchagin	(void)sigaddset(&nset, SIGTSTP);
141315512Sdchagin	(void)sigaddset(&nset, SIGCHLD);
142315512Sdchagin
143315512Sdchagin	(void)snprintf(path, sizeof(path), "%s.lock", fname);
144315512Sdchagin
145315512Sdchagin	retval = -1;
146315512Sdchagin	for (;;) {
147315512Sdchagin		handle_pending_signals();
148315512Sdchagin		(void)sigprocmask(SIG_BLOCK, &nset, &oset);
149315512Sdchagin		if (create_exclusive(path) != -1) {
150315512Sdchagin			(void)sigprocmask(SIG_SETMASK, &oset, NULL);
151315512Sdchagin			retval = 0;
152315512Sdchagin			break;
153315512Sdchagin		}
154315512Sdchagin		else
155315512Sdchagin			(void)sigprocmask(SIG_SETMASK, &oset, NULL);
156315512Sdchagin
157315512Sdchagin		if (errno != EEXIST)
158315512Sdchagin			break;
159315512Sdchagin
160315512Sdchagin		if (pollinterval) {
161315512Sdchagin			if (pollinterval == -1) {
162315512Sdchagin				errno = EEXIST;
163315512Sdchagin				break;
164315512Sdchagin			}
165315512Sdchagin			(void)usleep((unsigned int)pollinterval * 1000);
166315512Sdchagin		}
167315512Sdchagin	}
168315512Sdchagin	handle_pending_signals();
169315512Sdchagin	return retval;
170315512Sdchagin}
171315512Sdchagin
172315512Sdchaginvoid
173315512Sdchagindot_unlock(const char *fname)
174315512Sdchagin{
175315512Sdchagin	char path[MAXPATHLEN];
176315512Sdchagin
177315512Sdchagin	(void)snprintf(path, sizeof(path), "%s.lock", fname);
178315512Sdchagin	(void)unlink(path);
179315512Sdchagin}
180