xref: /netbsd-src/usr.sbin/puffs/mount_psshfs/psshfs.c (revision b757af438b42b93f8c6571f026d8b8ef3eaf5fc9)
1 /*	$NetBSD: psshfs.c,v 1.65 2011/08/31 13:32:39 joerg 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.65 2011/08/31 13:32:39 joerg 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 __dead static void	usage(void);
67 static char *	cleanhostname(char *);
68 static char *	colon(char *);
69 static void	add_ssharg(char ***, int *, const char *);
70 static void	psshfs_notify(struct puffs_usermount *, int, int);
71 
72 #define SSH_PATH "/usr/bin/ssh"
73 
74 unsigned int max_reads;
75 static int sighup;
76 
77 static char *
78 cleanhostname(char *host)
79 {
80 	if (*host == '[' && host[strlen(host) - 1] == ']') {
81 		host[strlen(host) - 1] = '\0';
82 		return (host + 1);
83 	} else
84 		return host;
85 }
86 
87 static char *
88 colon(char *cp)
89 {
90 	int flag = 0;
91 
92 	if (*cp == '[')
93 		flag = 1;
94 
95 	for (; *cp; ++cp) {
96 		if (*cp == '@' && *(cp+1) == '[')
97 			flag = 1;
98 		if (*cp == ']' && *(cp+1) == ':' && flag)
99 			return (cp+1);
100 		if (*cp == ':' && !flag)
101 			return (cp);
102 		if (*cp == '/')
103 			return NULL;
104 	}
105 	return NULL;
106 }
107 
108 static void
109 add_ssharg(char ***sshargs, int *nargs, const char *arg)
110 {
111 
112 	*sshargs = realloc(*sshargs, (*nargs + 2) * sizeof(char*));
113 	if (!*sshargs)
114 		err(1, "realloc");
115 	(*sshargs)[(*nargs)++] = estrdup(arg);
116 	(*sshargs)[*nargs] = NULL;
117 }
118 
119 static void
120 usage(void)
121 {
122 
123 	fprintf(stderr, "usage: %s "
124 	    "[-ceprst] [-F configfile] [-O sshopt=value] [-o opts] "
125 	    "user@host:path mountpath\n",
126 	    getprogname());
127 	exit(1);
128 }
129 
130 static void
131 takehup(int sig)
132 {
133 
134 	sighup = 1;
135 }
136 
137 int
138 main(int argc, char *argv[])
139 {
140 	struct psshfs_ctx pctx;
141 	struct puffs_usermount *pu;
142 	struct puffs_ops *pops;
143 	struct psshfs_node *root = &pctx.psn_root;
144 	struct puffs_node *pn_root;
145 	puffs_framev_fdnotify_fn notfn;
146 	struct vattr *rva;
147 	mntoptparse_t mp;
148 	char **sshargs;
149 	char *user;
150 	char *host;
151 	char *path;
152 	int mntflags, pflags, ch;
153 	int detach;
154 	int exportfs, refreshival, numconnections;
155 	int nargs;
156 
157 	setprogname(argv[0]);
158 	puffs_unmountonsignal(SIGINT, true);
159 	puffs_unmountonsignal(SIGTERM, true);
160 
161 	if (argc < 3)
162 		usage();
163 
164 	memset(&pctx, 0, sizeof(pctx));
165 	mntflags = pflags = exportfs = nargs = 0;
166 	numconnections = 1;
167 	detach = 1;
168 	refreshival = DEFAULTREFRESH;
169 	notfn = puffs_framev_unmountonclose;
170 	sshargs = NULL;
171 	add_ssharg(&sshargs, &nargs, SSH_PATH);
172 	add_ssharg(&sshargs, &nargs, "-axs");
173 	add_ssharg(&sshargs, &nargs, "-oClearAllForwardings=yes");
174 
175 	while ((ch = getopt(argc, argv, "c:eF:g:o:O:pr:st:u:")) != -1) {
176 		switch (ch) {
177 		case 'c':
178 			numconnections = atoi(optarg);
179 			if (numconnections < 1 || numconnections > 2) {
180 				fprintf(stderr, "%s: only 1 or 2 connections "
181 				    "permitted currently\n", getprogname());
182 				usage();
183 				/*NOTREACHED*/
184 			}
185 			break;
186 		case 'e':
187 			exportfs = 1;
188 			break;
189 		case 'F':
190 			add_ssharg(&sshargs, &nargs, "-F");
191 			add_ssharg(&sshargs, &nargs, optarg);
192 			break;
193 		case 'g':
194 			pctx.domanglegid = 1;
195 			pctx.manglegid = atoi(optarg);
196 			if (pctx.manglegid == (gid_t)-1)
197 				errx(1, "-1 not allowed for -g");
198 			pctx.mygid = getegid();
199 			break;
200 		case 'O':
201 			add_ssharg(&sshargs, &nargs, "-o");
202 			add_ssharg(&sshargs, &nargs, optarg);
203 			break;
204 		case 'o':
205 			mp = getmntopts(optarg, puffsmopts, &mntflags, &pflags);
206 			if (mp == NULL)
207 				err(1, "getmntopts");
208 			freemntopts(mp);
209 			break;
210 		case 'p':
211 			notfn = psshfs_notify;
212 			break;
213 		case 'r':
214 			max_reads = atoi(optarg);
215 			break;
216 		case 's':
217 			detach = 0;
218 			break;
219 		case 't':
220 			refreshival = atoi(optarg);
221 			if (refreshival < 0 && refreshival != -1)
222 				errx(1, "invalid timeout %d", refreshival);
223 			break;
224 		case 'u':
225 			pctx.domangleuid = 1;
226 			pctx.mangleuid = atoi(optarg);
227 			if (pctx.mangleuid == (uid_t)-1)
228 				errx(1, "-1 not allowed for -u");
229 			pctx.myuid = geteuid();
230 			break;
231 		default:
232 			usage();
233 			/*NOTREACHED*/
234 		}
235 	}
236 	argc -= optind;
237 	argv += optind;
238 
239 	if (pflags & PUFFS_FLAG_OPDUMP)
240 		detach = 0;
241 	pflags |= PUFFS_FLAG_BUILDPATH;
242 	pflags |= PUFFS_KFLAG_WTCACHE | PUFFS_KFLAG_IAONDEMAND;
243 
244 	if (argc != 2)
245 		usage();
246 
247 	PUFFSOP_INIT(pops);
248 
249 	PUFFSOP_SET(pops, psshfs, fs, unmount);
250 	PUFFSOP_SETFSNOP(pops, sync); /* XXX */
251 	PUFFSOP_SET(pops, psshfs, fs, statvfs);
252 	PUFFSOP_SET(pops, psshfs, fs, nodetofh);
253 	PUFFSOP_SET(pops, psshfs, fs, fhtonode);
254 
255 	PUFFSOP_SET(pops, psshfs, node, lookup);
256 	PUFFSOP_SET(pops, psshfs, node, create);
257 	PUFFSOP_SET(pops, psshfs, node, open);
258 	PUFFSOP_SET(pops, psshfs, node, inactive);
259 	PUFFSOP_SET(pops, psshfs, node, readdir);
260 	PUFFSOP_SET(pops, psshfs, node, getattr);
261 	PUFFSOP_SET(pops, psshfs, node, setattr);
262 	PUFFSOP_SET(pops, psshfs, node, mkdir);
263 	PUFFSOP_SET(pops, psshfs, node, remove);
264 	PUFFSOP_SET(pops, psshfs, node, readlink);
265 	PUFFSOP_SET(pops, psshfs, node, rmdir);
266 	PUFFSOP_SET(pops, psshfs, node, symlink);
267 	PUFFSOP_SET(pops, psshfs, node, rename);
268 	PUFFSOP_SET(pops, psshfs, node, read);
269 	PUFFSOP_SET(pops, psshfs, node, write);
270 	PUFFSOP_SET(pops, psshfs, node, reclaim);
271 
272 	pu = puffs_init(pops, argv[0], "psshfs", &pctx, pflags);
273 	if (pu == NULL)
274 		err(1, "puffs_init");
275 
276 	pctx.mounttime = time(NULL);
277 	pctx.refreshival = refreshival;
278 	pctx.numconnections = numconnections;
279 
280 	user = strdup(argv[0]);
281 	if ((host = strrchr(user, '@')) == NULL) {
282 		host = user;
283 	} else {
284 		*host++ = '\0';		/* break at the '@' */
285 		if (user[0] == '\0') {
286 			fprintf(stderr, "Missing username\n");
287 			usage();
288 		}
289 		add_ssharg(&sshargs, &nargs, "-l");
290 		add_ssharg(&sshargs, &nargs, user);
291 	}
292 
293 	if ((path = colon(host)) != NULL) {
294 		*path++ = '\0';		/* break at the ':' */
295 		pctx.mountpath = path;
296 	} else {
297 		pctx.mountpath = ".";
298 	}
299 
300 	host = cleanhostname(host);
301 	if (host[0] == '\0') {
302 		fprintf(stderr, "Missing hostname\n");
303 		usage();
304 	}
305 
306 	add_ssharg(&sshargs, &nargs, host);
307 	add_ssharg(&sshargs, &nargs, "sftp");
308 	pctx.sshargs = sshargs;
309 
310 	pctx.nextino = 2;
311 	memset(root, 0, sizeof(struct psshfs_node));
312 	TAILQ_INIT(&root->pw);
313 	pn_root = puffs_pn_new(pu, root);
314 	if (pn_root == NULL)
315 		return errno;
316 	puffs_setroot(pu, pn_root);
317 
318 	puffs_framev_init(pu, psbuf_read, psbuf_write, psbuf_cmp, NULL, notfn);
319 
320 	signal(SIGHUP, takehup);
321 	puffs_ml_setloopfn(pu, psshfs_loopfn);
322 	if (pssh_connect(pu, PSSHFD_META) == -1)
323 		err(1, "can't connect meta");
324 	if (puffs_framev_addfd(pu, pctx.sshfd,
325 	    PUFFS_FBIO_READ | PUFFS_FBIO_WRITE) == -1)
326 		err(1, "framebuf addfd meta");
327 	if (numconnections == 2) {
328 		if (pssh_connect(pu, PSSHFD_DATA) == -1)
329 			err(1, "can't connect data");
330 		if (puffs_framev_addfd(pu, pctx.sshfd_data,
331 		    PUFFS_FBIO_READ | PUFFS_FBIO_WRITE) == -1)
332 			err(1, "framebuf addfd data");
333 	} else {
334 		pctx.sshfd_data = pctx.sshfd;
335 	}
336 
337 	if (exportfs)
338 		puffs_setfhsize(pu, sizeof(struct psshfs_fid),
339 		    PUFFS_FHFLAG_NFSV2 | PUFFS_FHFLAG_NFSV3);
340 
341 	rva = &pn_root->pn_va;
342 	rva->va_fileid = pctx.nextino++;
343 
344 	/*
345 	 * For root link count, just guess something ridiculously high.
346 	 * Guessing too high has no known adverse effects, but fts(3)
347 	 * doesn't like too low values.  This guess will be replaced
348 	 * with the real value when readdir is first called for
349 	 * the root directory.
350 	 */
351 	rva->va_nlink = 8811;
352 
353 	if (detach)
354 		if (puffs_daemon(pu, 1, 1) == -1)
355 			err(1, "puffs_daemon");
356 
357 	if (puffs_mount(pu, argv[1], mntflags, puffs_getroot(pu)) == -1)
358 		err(1, "puffs_mount");
359 	if (puffs_setblockingmode(pu, PUFFSDEV_NONBLOCK) == -1)
360 		err(1, "setblockingmode");
361 
362 	if (puffs_mainloop(pu) == -1)
363 		err(1, "mainloop");
364 	puffs_exit(pu, 1);
365 
366 	return 0;
367 }
368 
369 #define RETRY_MAX 100
370 
371 void
372 psshfs_notify(struct puffs_usermount *pu, int fd, int what)
373 {
374 	struct psshfs_ctx *pctx = puffs_getspecific(pu);
375 	int nretry, which, newfd, dummy;
376 
377 	if (fd == pctx->sshfd) {
378 		which = PSSHFD_META;
379 	} else {
380 		assert(fd == pctx->sshfd_data);
381 		which = PSSHFD_DATA;
382 	}
383 
384 	if (puffs_getstate(pu) != PUFFS_STATE_RUNNING)
385 		return;
386 
387 	if (what != (PUFFS_FBIO_READ | PUFFS_FBIO_WRITE)) {
388 		puffs_framev_removefd(pu, fd, ECONNRESET);
389 		return;
390 	}
391 	close(fd);
392 
393 	/* deal with zmobies, beware of half-eaten brain */
394 	while (waitpid(-1, &dummy, WNOHANG) > 0)
395 		continue;
396 
397 	for (nretry = 0;;nretry++) {
398 		if ((newfd = pssh_connect(pu, which)) == -1)
399 			goto retry2;
400 
401 		if (puffs_framev_addfd(pu, newfd,
402 		    PUFFS_FBIO_READ | PUFFS_FBIO_WRITE) == -1)
403 			goto retry1;
404 
405 		break;
406  retry1:
407 		fprintf(stderr, "reconnect failed... ");
408 		close(newfd);
409  retry2:
410 		if (nretry < RETRY_MAX) {
411 			fprintf(stderr, "retry (%d left)\n", RETRY_MAX-nretry);
412 			sleep(nretry);
413 		} else {
414 			fprintf(stderr, "retry count exceeded, going south\n");
415 			exit(1); /* XXXXXXX */
416 		}
417 	}
418 }
419 
420 static int
421 pssh_connect(struct puffs_usermount *pu, int which)
422 {
423 	struct psshfs_ctx *pctx = puffs_getspecific(pu);
424 	char * const *sshargs = pctx->sshargs;
425 	int fds[2];
426 	pid_t pid;
427 	int dnfd, x;
428 	int *sshfd;
429 	pid_t *sshpid;
430 
431 	if (which == PSSHFD_META) {
432 		sshfd = &pctx->sshfd;
433 		sshpid = &pctx->sshpid;
434 	} else {
435 		assert(which == PSSHFD_DATA);
436 		sshfd = &pctx->sshfd_data;
437 		sshpid = &pctx->sshpid_data;
438 	}
439 
440 	if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == -1)
441 		return -1;
442 
443 	pid = fork();
444 	switch (pid) {
445 	case -1:
446 		return -1;
447 		/*NOTREACHED*/
448 	case 0: /* child */
449 		if (dup2(fds[0], STDIN_FILENO) == -1)
450 			err(1, "child dup2");
451 		if (dup2(fds[0], STDOUT_FILENO) == -1)
452 			err(1, "child dup2");
453 		close(fds[0]);
454 		close(fds[1]);
455 
456 		dnfd = open(_PATH_DEVNULL, O_RDWR);
457 		if (dnfd != -1)
458 			dup2(dnfd, STDERR_FILENO);
459 
460 		execvp(sshargs[0], sshargs);
461 		/*NOTREACHED*/
462 		break;
463 	default:
464 		*sshpid = pid;
465 		*sshfd = fds[1];
466 		close(fds[0]);
467 		break;
468 	}
469 
470 	if (psshfs_handshake(pu, *sshfd) != 0)
471 		errx(1, "handshake failed, server does not support sftp?");
472 	x = 1;
473 	if (ioctl(*sshfd, FIONBIO, &x) == -1)
474 		err(1, "nonblocking descriptor %d", which);
475 
476 	return *sshfd;
477 }
478 
479 static void *
480 invalone(struct puffs_usermount *pu, struct puffs_node *pn, void *arg)
481 {
482 	struct psshfs_node *psn = pn->pn_data;
483 
484 	psn->attrread = 0;
485 	psn->dentread = 0;
486 	psn->slread = 0;
487 
488 	return NULL;
489 }
490 
491 static void
492 psshfs_loopfn(struct puffs_usermount *pu)
493 {
494 
495 	if (sighup) {
496 		puffs_pn_nodewalk(pu, invalone, NULL);
497 		sighup = 0;
498 	}
499 }
500