Merge branch 'master' of ssh://git.samba.org/data/git/samba
[kai/samba.git] / source3 / client / mtab.c
1 /*
2  * mtab locking routines for use with mount.cifs and umount.cifs
3  * Copyright (C) 2008 Jeff Layton (jlayton@samba.org)
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 /*
20  * This code was copied from the util-linux-ng sources and modified:
21  *
22  * git://git.kernel.org/pub/scm/utils/util-linux-ng/util-linux-ng.git
23  *
24  * ...specifically from mount/fstab.c. That file has no explicit license. The
25  * "default" license for anything in that tree is apparently GPLv2+, so I
26  * believe we're OK to copy it here.
27  *
28  * Jeff Layton <jlayton@samba.org> 
29  */
30
31 #include <unistd.h>
32 #include <errno.h>
33 #include <stdio.h>
34 #include <sys/time.h>
35 #include <time.h>
36 #include <fcntl.h>
37 #include <mntent.h>
38 #include <stdlib.h>
39 #include <signal.h>
40 #include "mount.h"
41
42
43 /* Updating mtab ----------------------------------------------*/
44
45 /* Flag for already existing lock file. */
46 static int we_created_lockfile = 0;
47 static int lockfile_fd = -1;
48
49 /* Flag to indicate that signals have been set up. */
50 static int signals_have_been_setup = 0;
51
52 static void
53 handler (int sig) {
54      exit(EX_USER);
55 }
56
57 static void
58 setlkw_timeout (int sig) {
59      /* nothing, fcntl will fail anyway */
60 }
61
62 /* Remove lock file.  */
63 void
64 unlock_mtab (void) {
65         if (we_created_lockfile) {
66                 close(lockfile_fd);
67                 lockfile_fd = -1;
68                 unlink (_PATH_MOUNTED_LOCK);
69                 we_created_lockfile = 0;
70         }
71 }
72
73 /* Create the lock file.
74    The lock file will be removed if we catch a signal or when we exit. */
75 /* The old code here used flock on a lock file /etc/mtab~ and deleted
76    this lock file afterwards. However, as rgooch remarks, that has a
77    race: a second mount may be waiting on the lock and proceed as
78    soon as the lock file is deleted by the first mount, and immediately
79    afterwards a third mount comes, creates a new /etc/mtab~, applies
80    flock to that, and also proceeds, so that the second and third mount
81    now both are scribbling in /etc/mtab.
82    The new code uses a link() instead of a creat(), where we proceed
83    only if it was us that created the lock, and hence we always have
84    to delete the lock afterwards. Now the use of flock() is in principle
85    superfluous, but avoids an arbitrary sleep(). */
86
87 /* Where does the link point to? Obvious choices are mtab and mtab~~.
88    HJLu points out that the latter leads to races. Right now we use
89    mtab~.<pid> instead. Use 20 as upper bound for the length of %d. */
90 #define MOUNTLOCK_LINKTARGET            _PATH_MOUNTED_LOCK "%d"
91 #define MOUNTLOCK_LINKTARGET_LTH        (sizeof(_PATH_MOUNTED_LOCK)+20)
92
93 /*
94  * The original mount locking code has used sleep(1) between attempts and
95  * maximal number of attemps has been 5.
96  *
97  * There was very small number of attempts and extremely long waiting (1s)
98  * that is useless on machines with large number of concurret mount processes.
99  *
100  * Now we wait few thousand microseconds between attempts and we have global
101  * time limit (30s) rather than limit for number of attempts. The advantage
102  * is that this method also counts time which we spend in fcntl(F_SETLKW) and
103  * number of attempts is not so much restricted.
104  *
105  * -- kzak@redhat.com [2007-Mar-2007]
106  */
107
108 /* maximum seconds between first and last attempt */
109 #define MOUNTLOCK_MAXTIME               30
110
111 /* sleep time (in microseconds, max=999999) between attempts */
112 #define MOUNTLOCK_WAITTIME              5000
113
114 int
115 lock_mtab (void) {
116         int i;
117         struct timespec waittime;
118         struct timeval maxtime;
119         char linktargetfile[MOUNTLOCK_LINKTARGET_LTH];
120
121         if (!signals_have_been_setup) {
122                 int sig = 0;
123                 struct sigaction sa;
124
125                 sa.sa_handler = handler;
126                 sa.sa_flags = 0;
127                 sigfillset (&sa.sa_mask);
128
129                 while (sigismember (&sa.sa_mask, ++sig) != -1
130                        && sig != SIGCHLD) {
131                         if (sig == SIGALRM)
132                                 sa.sa_handler = setlkw_timeout;
133                         else
134                                 sa.sa_handler = handler;
135                         sigaction (sig, &sa, (struct sigaction *) 0);
136                 }
137                 signals_have_been_setup = 1;
138         }
139
140         sprintf(linktargetfile, MOUNTLOCK_LINKTARGET, getpid ());
141
142         i = open (linktargetfile, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR);
143         if (i < 0) {
144                 /* linktargetfile does not exist (as a file)
145                    and we cannot create it. Read-only filesystem?
146                    Too many files open in the system?
147                    Filesystem full? */
148                 return EX_FILEIO;
149         }
150         close(i);
151
152         gettimeofday(&maxtime, NULL);
153         maxtime.tv_sec += MOUNTLOCK_MAXTIME;
154
155         waittime.tv_sec = 0;
156         waittime.tv_nsec = (1000 * MOUNTLOCK_WAITTIME);
157
158         /* Repeat until it was us who made the link */
159         while (!we_created_lockfile) {
160                 struct timeval now;
161                 struct flock flock;
162                 int errsv, j;
163
164                 j = link(linktargetfile, _PATH_MOUNTED_LOCK);
165                 errsv = errno;
166
167                 if (j == 0)
168                         we_created_lockfile = 1;
169
170                 if (j < 0 && errsv != EEXIST) {
171                         (void) unlink(linktargetfile);
172                         return EX_FILEIO;
173                 }
174
175                 lockfile_fd = open (_PATH_MOUNTED_LOCK, O_WRONLY);
176
177                 if (lockfile_fd < 0) {
178                         /* Strange... Maybe the file was just deleted? */
179                         gettimeofday(&now, NULL);
180                         if (errno == ENOENT && now.tv_sec < maxtime.tv_sec) {
181                                 we_created_lockfile = 0;
182                                 continue;
183                         }
184                         (void) unlink(linktargetfile);
185                         return EX_FILEIO;
186                 }
187
188                 flock.l_type = F_WRLCK;
189                 flock.l_whence = SEEK_SET;
190                 flock.l_start = 0;
191                 flock.l_len = 0;
192
193                 if (j == 0) {
194                         /* We made the link. Now claim the lock. If we can't
195                          * get it, continue anyway
196                          */
197                         fcntl (lockfile_fd, F_SETLK, &flock);
198                         (void) unlink(linktargetfile);
199                 } else {
200                         /* Someone else made the link. Wait. */
201                         gettimeofday(&now, NULL);
202                         if (now.tv_sec < maxtime.tv_sec) {
203                                 alarm(maxtime.tv_sec - now.tv_sec);
204                                 if (fcntl (lockfile_fd, F_SETLKW, &flock) == -1) {
205                                         (void) unlink(linktargetfile);
206                                         return EX_FILEIO;
207                                 }
208                                 alarm(0);
209                                 nanosleep(&waittime, NULL);
210                         } else {
211                                 (void) unlink(linktargetfile);
212                                 return EX_FILEIO;
213                         }
214                         close(lockfile_fd);
215                 }
216         }
217         return 0;
218 }
219