1 /*  Copyright 2005,2009,2018 Alain Knaff.
2  *  This file is part of mtools.
3  *
4  *  Mtools is free software: you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation, either version 3 of the License, or
7  *  (at your option) any later version.
8  *
9  *  Mtools is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with Mtools.  If not, see <http://www.gnu.org/licenses/>.
16  *
17  * Create an advisory lock on the device to prevent concurrent writes.
18  * Uses either lockf, flock, or fcntl locking methods.  See the Makefile
19  * and the Configure files for how to specify the proper method.
20  */
21 
22 #include "sysincludes.h"
23 #include "mtools.h"
24 #include "lockdev.h"
25 
26 #if (defined HAVE_SIGACTION && defined HAVE_ALARM)
27 # define ALRM
28 #endif
29 
30 
31 #if (defined(HAVE_FLOCK) && defined (LOCK_EX) && (defined(LOCK_NB) || defined(ALRM)))
32 
33 # ifdef ALRM
34 #  define USE_FLOCK_W
35 # else
36 #  define USE_FLOCK
37 # endif
38 
39 #else /* FLOCK */
40 
41 #if (defined(HAVE_LOCKF) && (defined(F_TLOCK) || defined(ALRM)))
42 
43 # ifdef ALRM
44 #  define USE_LOCKF_W
45 # else
46 #  define USE_LOCKF
47 # endif
48 
49 #else /* LOCKF */
50 
51 #if (defined(F_SETLK) && defined(F_WRLCK))
52 
53 # if (defined ALRM && defined F_SETLKW)
54 #  define USE_SETLK_W
55 # else
56 #  define USE_SETLK_W
57 # endif
58 
59 #else
60 
61 #endif /* FCNTL */
62 #endif /* LOCKF */
63 #endif /* FLOCK */
64 
65 #if  defined(USE_FLOCK_W) || defined(USE_LOCKF_W) || defined (USE_SETLK_W)
alrm(int a UNUSEDP)66 static void alrm(int a UNUSEDP) {
67 }
68 #endif
69 
lock_dev(int fd,int mode,struct device * dev)70 int lock_dev(int fd, int mode, struct device *dev)
71 {
72 	unsigned int retries = 0;
73 	if(IS_NOLOCK(dev))
74 		return 0;
75 
76 	while(1) {
77 		int ret=0;
78 #if defined(USE_FLOCK_W) || defined(USE_LOCKF_W) || defined (USE_SETLK_W)
79 		struct sigaction alrm_action, old_alrm_action;
80 		int old_alrm = alarm(0);
81 		memset(&alrm_action, 0, sizeof(alrm_action));
82 		alrm_action.sa_handler = alrm;
83 		alrm_action.sa_flags = 0;
84 		sigaction(SIGALRM, &alrm_action, &old_alrm_action);
85 		alarm(mtools_lock_timeout);
86 #endif
87 
88 #ifdef USE_FLOCK
89 		ret = flock(fd, (mode ? LOCK_EX : LOCK_SH)|LOCK_NB);
90 #endif
91 
92 #ifdef USE_FLOCK_W
93 		ret = flock(fd, (mode ? LOCK_EX : LOCK_SH));
94 #endif
95 
96 #if (defined(USE_LOCKF) || defined(USE_LOCKF_W))
97 		if(mode)
98 # ifdef USE_LOCKF
99 			ret = lockf(fd, F_TLOCK, 0);
100 # else
101 			ret = lockf(fd, F_LOCK, 0);
102 # endif
103 		else
104 			ret = 0;
105 #endif
106 
107 #if (defined(USE_SETLK) || defined(USE_SETLK_W))
108 		{
109 			struct flock flk;
110 			flk.l_type = mode ? F_WRLCK : F_RDLCK;
111 			flk.l_whence = 0;
112 			flk.l_start = 0L;
113 			flk.l_len = 0L;
114 
115 # ifdef USE_SETLK_W
116 			ret = fcntl(fd, F_SETLKW, &flk);
117 # else
118 			ret = fcntl(fd, F_SETLK, &flk);
119 # endif
120 		}
121 #endif
122 
123 #if defined(USE_FLOCK_W) || defined(USE_LOCKF_W) || defined (USE_SETLK_W)
124 		/* Cancel the alarm */
125 		sigaction(SIGALRM, &old_alrm_action, NULL);
126 		alarm(old_alrm);
127 #endif
128 
129 		if(ret < 0) {
130 #if defined(USE_FLOCK_W) || defined(USE_LOCKF_W) || defined (USE_SETLK_W)
131 			/* ALARM fired ==> this means we are still locked */
132 			if(errno == EINTR) {
133 				return 1;
134 			}
135 #endif
136 
137 			if(
138 #ifdef EWOULDBLOCK
139 				(errno != EWOULDBLOCK)
140 #else
141 				1
142 #endif
143 				&&
144 #ifdef EAGAIN
145 				(errno != EAGAIN)
146 #else
147 				1
148 #endif
149 				&&
150 #ifdef EINTR
151 				(errno != EINTR)
152 #else
153 				1
154 #endif
155 			) {
156 				/* Error other than simply being locked */
157 				return -1;
158 			}
159 			/* Locked ==> continue until timeout */
160 		} else /* no error => we got the lock! */
161 			return 0;
162 
163 #ifdef HAVE_USLEEP
164 		if(retries++ < mtools_lock_timeout * 10)
165 			usleep(100000);
166 #else
167 		if(retries++ < mtools_lock_timeout)
168 			sleep(1);
169 #endif
170 		else
171 			/* waited for too long => give up */
172 			return 1;
173 	}
174 }
175