#116 — S38zfs-fuse (1142): /proc/1163/oom_adj is deprecated, please use /proc/1163...
[sehe] / src / zfs-fuse / util.c
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 (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2006 Ricardo Correia.
23  * Use is subject to license terms.
24  */
25
26 #include <sys/debug.h>
27 #include <sys/mount.h>
28 #include <sys/types.h>
29 #include <sys/cred.h>
30 #include <sys/cmn_err.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <unistd.h>
34 #include <pthread.h>
35 #include <syslog.h>
36 #include <signal.h>
37
38 #include "libsolkerncompat.h"
39 #include "zfs_ioctl.h"
40 #include "zfsfuse_socket.h"
41
42 #include "cmd_listener.h"
43 #include "fuse_listener.h"
44
45 #include "fuse.h"
46 #include "zfs_operations.h"
47 #include "util.h"
48
49 static int ioctl_fd = -1;
50 static int lock_fd = -1;
51
52 #define LOCKDIR "/var/lock/zfs"
53 #define LOCKFILE LOCKDIR "/zfs_lock"
54
55 boolean_t listener_thread_started = B_FALSE;
56 pthread_t listener_thread;
57
58 int num_filesystems;
59
60 char * fuse_mount_options = NULL;
61
62 extern vfsops_t *zfs_vfsops;
63 extern int zfs_vfsinit(int fstype, char *name);
64
65 static int zfsfuse_do_locking(int in_child)
66 {
67         /* Ignores errors since the directory might already exist */
68         mkdir(LOCKDIR, 0700);
69
70     if (!in_child)
71     {
72         ASSERT(lock_fd == -1);
73         /*
74          * before the fork, we create the file, truncating it, and locking the
75          * first byte
76          */
77         lock_fd = creat(LOCKFILE, S_IRUSR | S_IWUSR);
78         if(lock_fd == -1)
79             return -1;
80
81         /*
82          * only if we /could/ lock all of the file,
83          * we shall lock just the first byte; this way
84          * we can let the daemon child process lock the
85          * remainder of the file after forking
86          */
87         if (0==lockf(lock_fd, F_TEST, 0))
88             return lockf(lock_fd, F_TLOCK, 1);
89         else
90             return -1;
91     } else
92     {
93         ASSERT(lock_fd != -1);
94         /*
95          * after the fork, we instead try to lock only the region /after/ the
96          * first byte; the file /must/ already exist. Only in this way can we
97          * prevent races with locking before or after the daemonization
98          */
99         lock_fd = open(LOCKFILE, O_WRONLY);
100         if(lock_fd == -1)
101             return -1;
102
103         ASSERT(-1 == lockf(lock_fd, F_TEST, 0)); /* assert that parent still has the lock on the first byte */
104         if (-1 == lseek(lock_fd, 1, SEEK_SET))
105         {
106             perror("lseek");
107             return -1;
108         }
109
110         return lockf(lock_fd, F_TLOCK, 0);
111     }
112 }
113
114 void do_daemon(const char *pidfile)
115 {
116         chdir("/");
117         if (pidfile) {
118                 struct stat dummy;
119                 if (0 == stat(pidfile, &dummy)) {
120                         cmn_err(CE_WARN, "%s already exists; aborting.", pidfile);
121                         exit(1);
122                 }
123         }
124
125     /*
126      * info gleaned from the web, notably
127      * http://www.enderunix.org/docs/eng/daemon.php
128      *
129      * and
130      *
131      * http://sourceware.org/git/?p=glibc.git;a=blob;f=misc/daemon.c;h=7597ce9996d5fde1c4ba622e7881cf6e821a12b4;hb=HEAD
132      */
133     {
134         int forkres, devnull;
135
136         if(getppid()==1)
137             return; /* already a daemon */
138
139         forkres=fork();
140         if (forkres<0)
141         { /* fork error */
142             cmn_err(CE_WARN, "Cannot fork (%s)", strerror(errno));
143             exit(1);
144         }
145         if (forkres>0)
146         {
147             int i;
148             /* parent */
149             for (i=getdtablesize();i>=0;--i)
150                 if ((lock_fd!=i) && (ioctl_fd!=i))       /* except for the lockfile and the comm socket */
151                     close(i);                            /* close all descriptors */
152
153             /* allow for airtight lockfile semantics... */
154             struct timeval tv;
155             tv.tv_sec = 0;
156             tv.tv_usec = 200000;  /* 0.2 seconds */
157             select(0, NULL, NULL, NULL, &tv);
158
159             VERIFY(0 == close(lock_fd));
160             lock_fd == -1;
161             exit(0);
162         }
163
164         /* child (daemon) continues */
165         setsid();                         /* obtain a new process group */
166         VERIFY(0 == chdir("/"));          /* change working directory */
167         umask(027);                       /* set newly created file permissions */
168         devnull=open("/dev/null",O_RDWR); /* handle standard I/O */
169         ASSERT(-1 != devnull);
170         dup2(devnull, 0); /* stdin  */
171         dup2(devnull, 1); /* stdout */
172         dup2(devnull, 2); /* stderr */
173         if (devnull>2)
174             close(devnull);
175
176         /*
177          * contrary to recommendation, do _not_ ignore SIGCHLD:
178          * it will break exec-ing subprocesses, e.g. for kstat mount and
179          * (presumably) nfs sharing!
180          *
181          * this will lead to really bad performance too
182          */
183         signal(SIGTSTP,SIG_IGN);     /* ignore tty signals */
184         signal(SIGTTOU,SIG_IGN);
185         signal(SIGTTIN,SIG_IGN);
186     }
187
188     if (0 != zfsfuse_do_locking(1))
189     {
190         cmn_err(CE_WARN, "Unexpected locking conflict (%s: %s)", strerror(errno), LOCKFILE);
191         exit(1);
192     }
193
194         if (pidfile) {
195                 FILE *f = fopen(pidfile, "w");
196                 if (!f) {
197                         cmn_err(CE_WARN, "Error opening %s.", pidfile);
198                         exit(1);
199                 }
200                 if (fprintf(f, "%d\n", getpid()) < 0) {
201                         unlink(pidfile);
202                         exit(1);
203                 }
204                 if (fclose(f) != 0) {
205                         unlink(pidfile);
206                         exit(1);
207                 }
208         }
209 }
210
211 extern size_t stack_size;
212
213 int do_init_fusesocket()
214 {
215         if(zfsfuse_do_locking(0) != 0) {
216                 cmn_err(CE_WARN, "Error locking " LOCKFILE ". Make sure there isn't another zfs-fuse process running and that you have appropriate permissions.");
217                 return -1;
218         }
219
220         ioctl_fd = zfsfuse_socket_create();
221         if(ioctl_fd == -1)
222                 return -1;
223     return 0;
224 }
225
226 int do_init()
227 {
228         libsolkerncompat_init();
229
230         zfs_vfsinit(zfstype, NULL);
231
232         VERIFY(zfs_ioctl_init() == 0);
233
234     VERIFY(ioctl_fd != -1); // initialization moved to do_init_fusesocket
235
236     VERIFY(cmd_listener_init() == 0);
237
238         pthread_attr_t attr;
239         VERIFY(0 == pthread_attr_init(&attr));
240         if (stack_size)
241             pthread_attr_setstacksize(&attr,stack_size);
242         if(pthread_create(&listener_thread, &attr, listener_loop, (void *) &ioctl_fd) != 0) {
243                 VERIFY(0 == pthread_attr_destroy(&attr));
244                 cmn_err(CE_WARN, "Error creating listener thread.");
245                 return -1;
246         }
247         VERIFY(0 == pthread_attr_destroy(&attr));
248
249         listener_thread_started = B_TRUE;
250
251         return zfsfuse_listener_init();
252 }
253
254 void do_exit()
255 {
256         if(listener_thread_started) {
257                 exit_listener = B_TRUE;
258                 if(pthread_join(listener_thread, NULL) != 0)
259                         cmn_err(CE_WARN, "Error in pthread_join().");
260         }
261
262         zfsfuse_listener_exit();
263     cmd_listener_fini();
264
265         if(ioctl_fd != -1)
266                 zfsfuse_socket_close(ioctl_fd);
267
268         int ret = zfs_ioctl_fini();
269         if(ret != 0)
270                 cmn_err(CE_WARN, "Error %i in zfs_ioctl_fini().\n", ret);
271
272         libsolkerncompat_exit();
273 }
274
275 /* big_writes added if fuse 2.8 is detected at runtime */
276 /* other mount options are added if specified in the command line */
277 #define FUSE_OPTIONS "subtype=zfs,fsname=%s,allow_other,suid,dev%s" // ,big_writes"
278
279 #ifdef DEBUG
280 uint32_t mounted = 0;
281 #endif
282
283 static int detect_fuseoption(const char* options, const char* option)
284 {
285         if ((!options) || (!option))
286                 return 0;
287         ASSERT(NULL == strchr(option, '%'));
288
289         char* spec = 0;
290         VERIFY(asprintf(&spec, "%s%%n", option));
291         ASSERT(spec);
292
293         int pos = -1;
294         int detected = 0;
295         char* tmp = strdup(options);
296         for (char* tok=strtok(tmp, ","); tok && !detected; tok=strtok(NULL, ","))
297                 if (sscanf(tok, spec, &pos) >= 0 && (-1!=pos))
298                         detected = 1;
299
300         free(tmp);
301         free(spec);
302
303         if (detected)
304                 fprintf(stderr, "detected: %s\n", option);
305         return detected;
306 }
307
308 int do_mount(char *spec, char *dir, int mflag, char *opt)
309 {
310         VERIFY(mflag == 0);
311
312         vfs_t *vfs = kmem_zalloc(sizeof(vfs_t), KM_SLEEP);
313         if(vfs == NULL)
314                 return ENOMEM;
315
316         VFS_INIT(vfs, zfs_vfsops, 0);
317         VFS_HOLD(vfs);
318
319         struct mounta uap = {
320         .spec = spec,
321         .dir = dir,
322         .flags = mflag | MS_SYSSPACE,
323         .fstype = "zfs-fuse",
324         .dataptr = "",
325         .datalen = 0,
326         .optptr = opt,
327         .optlen = strlen(opt)
328         };
329
330         int ret;
331         if ((ret = VFS_MOUNT(vfs, rootdir, &uap, kcred)) != 0) {
332                 kmem_free(vfs, sizeof(vfs_t));
333                 return ret;
334         }
335         /* Actually, optptr is totally ignored by VFS_MOUNT.
336          * So we are going to pass this with fuse_mount_options if possible */
337     if (fuse_mount_options == NULL)
338         fuse_mount_options = "";
339         char real_opts[1024];
340         *real_opts = 0;
341         if (*fuse_mount_options)
342                 strcat(real_opts,fuse_mount_options); // comes with a starting ,
343         if (*opt)
344                 sprintf(&real_opts[strlen(real_opts)],",%s",opt);
345
346 #ifdef DEBUG
347         atomic_inc_32(&mounted);;
348
349         fprintf(stderr, "mounting %s\n", dir);
350 #endif
351
352         char *fuse_opts = NULL;
353         int has_default_perm = 0;
354         if (fuse_version() <= 27) {
355         if(asprintf(&fuse_opts, FUSE_OPTIONS, spec, real_opts) == -1) {
356                 VERIFY(do_umount(vfs, B_FALSE) == 0);
357                 return ENOMEM;
358         }
359         } else {
360           if(asprintf(&fuse_opts, FUSE_OPTIONS ",big_writes", spec, real_opts) == -1) {
361             VERIFY(do_umount(vfs, B_FALSE) == 0);
362             return ENOMEM;
363           }
364         }
365         
366         struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
367
368         if(fuse_opt_add_arg(&args, "") == -1 ||
369            fuse_opt_add_arg(&args, "-o") == -1 ||
370            fuse_opt_add_arg(&args, fuse_opts) == -1) {
371                 fuse_opt_free_args(&args);
372                 free(fuse_opts);
373                 VERIFY(do_umount(vfs, B_FALSE) == 0);
374                 return ENOMEM;
375         }
376         has_default_perm = detect_fuseoption(fuse_opts,"default_permissions");
377         free(fuse_opts);
378
379         struct fuse_chan *ch = fuse_mount(dir, &args);
380
381         if(ch == NULL) {
382                 VERIFY(do_umount(vfs, B_FALSE) == 0);
383                 return EIO;
384         }
385
386         if (has_default_perm)
387             vfs->fuse_attribute = FUSE_VFS_HAS_DEFAULT_PERM;
388
389         struct fuse_session *se = fuse_lowlevel_new(&args, &zfs_operations, sizeof(zfs_operations), vfs);
390         fuse_opt_free_args(&args);
391
392         if(se == NULL) {
393                 VERIFY(do_umount(vfs, B_FALSE) == 0); /* ZFSFUSE: FIXME?? */
394                 fuse_unmount(dir,ch);
395                 return EIO;
396         }
397
398         fuse_session_add_chan(se, ch);
399
400         if(zfsfuse_newfs(dir, ch) != 0) {
401                 fuse_session_destroy(se);
402                 fuse_unmount(dir,ch);
403                 return EIO;
404         }
405
406         return 0;
407 }
408
409 int do_umount(vfs_t *vfs, boolean_t force)
410 {
411         VFS_SYNC(vfs, 0, kcred);
412
413         int ret = VFS_UNMOUNT(vfs, force ? MS_FORCE : 0, kcred);
414         if(ret != 0)
415                 return ret;
416
417         ASSERT(force || vfs->vfs_count == 1);
418         VFS_RELE(vfs);
419
420 #ifdef DEBUG
421         fprintf(stderr, "mounted filesystems: %i\n", atomic_dec_32_nv(&mounted));
422 #endif
423
424         return 0;
425 }