xref: /netbsd-src/usr.sbin/puffs/mount_psshfs/psshfs.c (revision 3816d47b2c42fcd6e549e3407f842a5b1a1d23ad)
1 /*	$NetBSD: psshfs.c,v 1.58 2010/01/07 21:26:49 pooka Exp $	*/
2 
3 /*
4  * Copyright (c) 2006-2009  Antti Kantee.  All Rights Reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
16  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18  * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 /*
29  * psshfs: puffs sshfs
30  *
31  * psshfs implements sshfs functionality on top of puffs making it
32  * possible to mount a filesystme through the sftp service.
33  *
34  * psshfs can execute multiple operations in "parallel" by using the
35  * puffs_cc framework for continuations.
36  *
37  * Concurrency control is handled currently by vnode locking (this
38  * will change in the future).  Context switch locations are easy to
39  * find by grepping for puffs_framebuf_enqueue_cc().
40  */
41 
42 #include <sys/cdefs.h>
43 #ifndef lint
44 __RCSID("$NetBSD: psshfs.c,v 1.58 2010/01/07 21:26:49 pooka Exp $");
45 #endif /* !lint */
46 
47 #include <sys/types.h>
48 #include <sys/wait.h>
49 
50 #include <assert.h>
51 #include <err.h>
52 #include <errno.h>
53 #include <mntopts.h>
54 #include <paths.h>
55 #include <poll.h>
56 #include <puffs.h>
57 #include <signal.h>
58 #include <stdlib.h>
59 #include <util.h>
60 #include <unistd.h>
61 
62 #include "psshfs.h"
63 
64 static int	pssh_connect(struct puffs_usermount *, int);
65 static void	psshfs_loopfn(struct puffs_usermount *);
66 static void	usage(void);
67 static void	add_ssharg(char ***, int *, const char *);
68 static void	psshfs_notify(struct puffs_usermount *, int, int);
69 
70 #define SSH_PATH "/usr/bin/ssh"
71 
72 unsigned int max_reads;
73 static int sighup;
74 
75 static void
76 add_ssharg(char ***sshargs, int *nargs, const char *arg)
77 {
78 
79 	*sshargs = realloc(*sshargs, (*nargs + 2) * sizeof(char*));
80 	if (!*sshargs)
81 		err(1, "realloc");
82 	(*sshargs)[(*nargs)++] = estrdup(arg);
83 	(*sshargs)[*nargs] = NULL;
84 }
85 
86 static void
87 usage()
88 {
89 
90 	fprintf(stderr, "usage: %s "
91 	    "[-ceprst] [-F configfile] [-O sshopt=value] [-o opts] "
92 	    "user@host:path mountpath\n",
93 	    getprogname());
94 	exit(1);
95 }
96 
97 static void
98 takehup(int sig)
99 {
100 
101 	sighup = 1;
102 }
103 
104 int
105 main(int argc, char *argv[])
106 {
107 	struct psshfs_ctx pctx;
108 	struct puffs_usermount *pu;
109 	struct puffs_ops *pops;
110 	struct psshfs_node *root = &pctx.psn_root;
111 	struct puffs_node *pn_root;
112 	puffs_framev_fdnotify_fn notfn;
113 	struct vattr *rva;
114 	mntoptparse_t mp;
115 	char **sshargs;
116 	char *userhost;
117 	char *hostpath;
118 	int mntflags, pflags, ch;
119 	int detach;
120 	int exportfs, refreshival, numconnections;
121 	int nargs;
122 
123 	setprogname(argv[0]);
124 
125 	if (argc < 3)
126 		usage();
127 
128 	memset(&pctx, 0, sizeof(pctx));
129 	mntflags = pflags = exportfs = nargs = 0;
130 	numconnections = 1;
131 	detach = 1;
132 	refreshival = DEFAULTREFRESH;
133 	notfn = puffs_framev_unmountonclose;
134 	sshargs = NULL;
135 	add_ssharg(&sshargs, &nargs, SSH_PATH);
136 	add_ssharg(&sshargs, &nargs, "-axs");
137 	add_ssharg(&sshargs, &nargs, "-oClearAllForwardings=yes");
138 
139 	while ((ch = getopt(argc, argv, "c:eF:g:o:O:pr:st:u:")) != -1) {
140 		switch (ch) {
141 		case 'c':
142 			numconnections = atoi(optarg);
143 			if (numconnections < 1 || numconnections > 2) {
144 				fprintf(stderr, "%s: only 1 or 2 connections "
145 				    "permitted currently\n", getprogname());
146 				usage();
147 				/*NOTREACHED*/
148 			}
149 			break;
150 		case 'e':
151 			exportfs = 1;
152 			break;
153 		case 'F':
154 			add_ssharg(&sshargs, &nargs, "-F");
155 			add_ssharg(&sshargs, &nargs, optarg);
156 			break;
157 		case 'g':
158 			pctx.domanglegid = 1;
159 			pctx.manglegid = atoi(optarg);
160 			if (pctx.manglegid == (gid_t)-1)
161 				errx(1, "-1 not allowed for -g");
162 			pctx.mygid = getegid();
163 			break;
164 		case 'O':
165 			add_ssharg(&sshargs, &nargs, "-o");
166 			add_ssharg(&sshargs, &nargs, optarg);
167 			break;
168 		case 'o':
169 			mp = getmntopts(optarg, puffsmopts, &mntflags, &pflags);
170 			if (mp == NULL)
171 				err(1, "getmntopts");
172 			freemntopts(mp);
173 			break;
174 		case 'p':
175 			notfn = psshfs_notify;
176 			break;
177 		case 'r':
178 			max_reads = atoi(optarg);
179 			break;
180 		case 's':
181 			detach = 0;
182 			break;
183 		case 't':
184 			refreshival = atoi(optarg);
185 			if (refreshival < 0 && refreshival != -1)
186 				errx(1, "invalid timeout %d", refreshival);
187 			break;
188 		case 'u':
189 			pctx.domangleuid = 1;
190 			pctx.mangleuid = atoi(optarg);
191 			if (pctx.mangleuid == (uid_t)-1)
192 				errx(1, "-1 not allowed for -u");
193 			pctx.myuid = geteuid();
194 			break;
195 		default:
196 			usage();
197 			/*NOTREACHED*/
198 		}
199 	}
200 	argc -= optind;
201 	argv += optind;
202 
203 	if (pflags & PUFFS_FLAG_OPDUMP)
204 		detach = 0;
205 	pflags |= PUFFS_FLAG_BUILDPATH;
206 	pflags |= PUFFS_KFLAG_WTCACHE | PUFFS_KFLAG_IAONDEMAND;
207 
208 	if (argc != 2)
209 		usage();
210 
211 	PUFFSOP_INIT(pops);
212 
213 	PUFFSOP_SET(pops, psshfs, fs, unmount);
214 	PUFFSOP_SETFSNOP(pops, sync); /* XXX */
215 	PUFFSOP_SET(pops, psshfs, fs, statvfs);
216 	PUFFSOP_SET(pops, psshfs, fs, nodetofh);
217 	PUFFSOP_SET(pops, psshfs, fs, fhtonode);
218 
219 	PUFFSOP_SET(pops, psshfs, node, lookup);
220 	PUFFSOP_SET(pops, psshfs, node, create);
221 	PUFFSOP_SET(pops, psshfs, node, open);
222 	PUFFSOP_SET(pops, psshfs, node, inactive);
223 	PUFFSOP_SET(pops, psshfs, node, readdir);
224 	PUFFSOP_SET(pops, psshfs, node, getattr);
225 	PUFFSOP_SET(pops, psshfs, node, setattr);
226 	PUFFSOP_SET(pops, psshfs, node, mkdir);
227 	PUFFSOP_SET(pops, psshfs, node, remove);
228 	PUFFSOP_SET(pops, psshfs, node, readlink);
229 	PUFFSOP_SET(pops, psshfs, node, rmdir);
230 	PUFFSOP_SET(pops, psshfs, node, symlink);
231 	PUFFSOP_SET(pops, psshfs, node, rename);
232 	PUFFSOP_SET(pops, psshfs, node, read);
233 	PUFFSOP_SET(pops, psshfs, node, write);
234 	PUFFSOP_SET(pops, psshfs, node, reclaim);
235 
236 	pu = puffs_init(pops, argv[0], "psshfs", &pctx, pflags);
237 	if (pu == NULL)
238 		err(1, "puffs_init");
239 
240 	pctx.mounttime = time(NULL);
241 	pctx.refreshival = refreshival;
242 	pctx.numconnections = numconnections;
243 
244 	userhost = argv[0];
245 	hostpath = strchr(userhost, ':');
246 	if (hostpath) {
247 		*hostpath++ = '\0';
248 		pctx.mountpath = hostpath;
249 	} else
250 		pctx.mountpath = ".";
251 
252 	add_ssharg(&sshargs, &nargs, argv[0]);
253 	add_ssharg(&sshargs, &nargs, "sftp");
254 	pctx.sshargs = sshargs;
255 
256 	pctx.nextino = 2;
257 	memset(root, 0, sizeof(struct psshfs_node));
258 	pn_root = puffs_pn_new(pu, root);
259 	if (pn_root == NULL)
260 		return errno;
261 	puffs_setroot(pu, pn_root);
262 
263 	puffs_framev_init(pu, psbuf_read, psbuf_write, psbuf_cmp, NULL, notfn);
264 
265 	signal(SIGHUP, takehup);
266 	puffs_ml_setloopfn(pu, psshfs_loopfn);
267 	if (pssh_connect(pu, PSSHFD_META) == -1)
268 		err(1, "can't connect meta");
269 	if (puffs_framev_addfd(pu, pctx.sshfd,
270 	    PUFFS_FBIO_READ | PUFFS_FBIO_WRITE) == -1)
271 		err(1, "framebuf addfd meta");
272 	if (numconnections == 2) {
273 		if (pssh_connect(pu, PSSHFD_DATA) == -1)
274 			err(1, "can't connect data");
275 		if (puffs_framev_addfd(pu, pctx.sshfd_data,
276 		    PUFFS_FBIO_READ | PUFFS_FBIO_WRITE) == -1)
277 			err(1, "framebuf addfd data");
278 	} else {
279 		pctx.sshfd_data = pctx.sshfd;
280 	}
281 
282 	if (exportfs)
283 		puffs_setfhsize(pu, sizeof(struct psshfs_fid),
284 		    PUFFS_FHFLAG_NFSV2 | PUFFS_FHFLAG_NFSV3);
285 
286 	rva = &pn_root->pn_va;
287 	rva->va_fileid = pctx.nextino++;
288 	rva->va_nlink = 101; /* XXX */
289 
290 	if (detach)
291 		if (puffs_daemon(pu, 1, 1) == -1)
292 			err(1, "puffs_daemon");
293 
294 	if (puffs_mount(pu, argv[1], mntflags, puffs_getroot(pu)) == -1)
295 		err(1, "puffs_mount");
296 	if (puffs_setblockingmode(pu, PUFFSDEV_NONBLOCK) == -1)
297 		err(1, "setblockingmode");
298 
299 	if (puffs_mainloop(pu) == -1)
300 		err(1, "mainloop");
301 	puffs_exit(pu, 1);
302 
303 	return 0;
304 }
305 
306 #define RETRY_MAX 100
307 
308 void
309 psshfs_notify(struct puffs_usermount *pu, int fd, int what)
310 {
311 	struct psshfs_ctx *pctx = puffs_getspecific(pu);
312 	int nretry, which, newfd, dummy;
313 
314 	if (fd == pctx->sshfd) {
315 		which = PSSHFD_META;
316 	} else {
317 		assert(fd == pctx->sshfd_data);
318 		which = PSSHFD_DATA;
319 	}
320 
321 	if (puffs_getstate(pu) != PUFFS_STATE_RUNNING)
322 		return;
323 
324 	if (what != (PUFFS_FBIO_READ | PUFFS_FBIO_WRITE)) {
325 		puffs_framev_removefd(pu, fd, ECONNRESET);
326 		return;
327 	}
328 	close(fd);
329 
330 	/* deal with zmobies, beware of half-eaten brain */
331 	while (waitpid(-1, &dummy, WNOHANG) > 0)
332 		continue;
333 
334 	for (nretry = 0;;nretry++) {
335 		if ((newfd = pssh_connect(pu, which)) == -1)
336 			goto retry2;
337 
338 		if (puffs_framev_addfd(pu, newfd,
339 		    PUFFS_FBIO_READ | PUFFS_FBIO_WRITE) == -1)
340 			goto retry1;
341 
342 		break;
343  retry1:
344 		fprintf(stderr, "reconnect failed... ");
345 		close(newfd);
346  retry2:
347 		if (nretry < RETRY_MAX) {
348 			fprintf(stderr, "retry (%d left)\n", RETRY_MAX-nretry);
349 			sleep(nretry);
350 		} else {
351 			fprintf(stderr, "retry count exceeded, going south\n");
352 			exit(1); /* XXXXXXX */
353 		}
354 	}
355 }
356 
357 static int
358 pssh_connect(struct puffs_usermount *pu, int which)
359 {
360 	struct psshfs_ctx *pctx = puffs_getspecific(pu);
361 	char * const *sshargs = pctx->sshargs;
362 	int fds[2];
363 	pid_t pid;
364 	int dnfd, x;
365 	int *sshfd;
366 	pid_t *sshpid;
367 
368 	if (which == PSSHFD_META) {
369 		sshfd = &pctx->sshfd;
370 		sshpid = &pctx->sshpid;
371 	} else {
372 		assert(which == PSSHFD_DATA);
373 		sshfd = &pctx->sshfd_data;
374 		sshpid = &pctx->sshpid_data;
375 	}
376 
377 	if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == -1)
378 		return -1;
379 
380 	pid = fork();
381 	switch (pid) {
382 	case -1:
383 		return -1;
384 		/*NOTREACHED*/
385 	case 0: /* child */
386 		if (dup2(fds[0], STDIN_FILENO) == -1)
387 			err(1, "child dup2");
388 		if (dup2(fds[0], STDOUT_FILENO) == -1)
389 			err(1, "child dup2");
390 		close(fds[0]);
391 		close(fds[1]);
392 
393 		dnfd = open(_PATH_DEVNULL, O_RDWR);
394 		if (dnfd != -1)
395 			dup2(dnfd, STDERR_FILENO);
396 
397 		execvp(sshargs[0], sshargs);
398 		/*NOTREACHED*/
399 		break;
400 	default:
401 		*sshpid = pid;
402 		*sshfd = fds[1];
403 		close(fds[0]);
404 		break;
405 	}
406 
407 	if (psshfs_handshake(pu, *sshfd) != 0)
408 		errx(1, "psshfs_handshake %d", which);
409 	x = 1;
410 	if (ioctl(*sshfd, FIONBIO, &x) == -1)
411 		err(1, "nonblocking descriptor %d", which);
412 
413 	return *sshfd;
414 }
415 
416 static void *
417 invalone(struct puffs_usermount *pu, struct puffs_node *pn, void *arg)
418 {
419 	struct psshfs_node *psn = pn->pn_data;
420 
421 	psn->attrread = 0;
422 	psn->dentread = 0;
423 	psn->slread = 0;
424 
425 	return NULL;
426 }
427 
428 static void
429 psshfs_loopfn(struct puffs_usermount *pu)
430 {
431 
432 	if (sighup) {
433 		puffs_pn_nodewalk(pu, invalone, NULL);
434 		sighup = 0;
435 	}
436 }
437