psshfs.c revision 1.51 1 /* $NetBSD: psshfs.c,v 1.51 2009/05/20 13:56:36 pooka Exp $ */
2
3 /*
4 * Copyright (c) 2006 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.51 2009/05/20 13:56:36 pooka Exp $");
45 #endif /* !lint */
46
47 #include <sys/types.h>
48
49 #include <assert.h>
50 #include <err.h>
51 #include <errno.h>
52 #include <mntopts.h>
53 #include <paths.h>
54 #include <poll.h>
55 #include <puffs.h>
56 #include <signal.h>
57 #include <stdlib.h>
58 #include <util.h>
59 #include <unistd.h>
60
61 #include "psshfs.h"
62
63 static int pssh_connect(struct puffs_usermount *, int);
64 static void psshfs_loopfn(struct puffs_usermount *);
65 static void usage(void);
66 static void add_ssharg(char ***, int *, char *);
67 static void psshfs_notify(struct puffs_usermount *, int, int);
68
69 #define SSH_PATH "/usr/bin/ssh"
70
71 unsigned int max_reads;
72 static int sighup;
73
74 static void
75 add_ssharg(char ***sshargs, int *nargs, char *arg)
76 {
77
78 *sshargs = realloc(*sshargs, (*nargs + 2) * sizeof(char*));
79 if (!*sshargs)
80 err(1, "realloc");
81 (*sshargs)[(*nargs)++] = arg;
82 (*sshargs)[*nargs] = NULL;
83 }
84
85 static void
86 usage()
87 {
88
89 fprintf(stderr, "usage: %s "
90 "[-ceprst] [-F configfile] [-O sshopt=value] [-o opts] "
91 "user@host:path mountpath\n",
92 getprogname());
93 exit(1);
94 }
95
96 static void
97 takehup(int sig)
98 {
99
100 sighup = 1;
101 }
102
103 int
104 main(int argc, char *argv[])
105 {
106 struct psshfs_ctx pctx;
107 struct puffs_usermount *pu;
108 struct puffs_ops *pops;
109 struct psshfs_node *root = &pctx.psn_root;
110 struct puffs_node *pn_root;
111 puffs_framev_fdnotify_fn notfn;
112 struct vattr *rva;
113 mntoptparse_t mp;
114 char **sshargs;
115 char *userhost;
116 char *hostpath;
117 int mntflags, pflags, ch;
118 int detach;
119 int exportfs, refreshival, numconnections;
120 int nargs;
121
122 setprogname(argv[0]);
123
124 if (argc < 3)
125 usage();
126
127 mntflags = pflags = exportfs = nargs = 0;
128 numconnections = 1;
129 detach = 1;
130 refreshival = DEFAULTREFRESH;
131 notfn = puffs_framev_unmountonclose;
132 sshargs = NULL;
133 add_ssharg(&sshargs, &nargs, SSH_PATH);
134 add_ssharg(&sshargs, &nargs, "-axs");
135 add_ssharg(&sshargs, &nargs, "-oClearAllForwardings=yes");
136
137 while ((ch = getopt(argc, argv, "c:eF:o:O:pr:st:")) != -1) {
138 switch (ch) {
139 case 'c':
140 numconnections = atoi(optarg);
141 if (numconnections < 1 || numconnections > 2) {
142 fprintf(stderr, "%s: only 1 or 2 connections "
143 "permitted currently\n", getprogname());
144 usage();
145 /*NOTREACHED*/
146 }
147 break;
148 case 'e':
149 exportfs = 1;
150 break;
151 case 'F':
152 add_ssharg(&sshargs, &nargs, "-F");
153 add_ssharg(&sshargs, &nargs, optarg);
154 break;
155 case 'O':
156 add_ssharg(&sshargs, &nargs, "-o");
157 add_ssharg(&sshargs, &nargs, optarg);
158 break;
159 case 'o':
160 mp = getmntopts(optarg, puffsmopts, &mntflags, &pflags);
161 if (mp == NULL)
162 err(1, "getmntopts");
163 freemntopts(mp);
164 break;
165 case 'p':
166 notfn = psshfs_notify;
167 break;
168 case 'r':
169 max_reads = atoi(optarg);
170 break;
171 case 's':
172 detach = 0;
173 break;
174 case 't':
175 refreshival = atoi(optarg);
176 if (refreshival < 0 && refreshival != -1)
177 errx(1, "invalid timeout %d", refreshival);
178 break;
179 default:
180 usage();
181 /*NOTREACHED*/
182 }
183 }
184 argc -= optind;
185 argv += optind;
186
187 if (pflags & PUFFS_FLAG_OPDUMP)
188 detach = 0;
189 pflags |= PUFFS_FLAG_BUILDPATH;
190 pflags |= PUFFS_KFLAG_WTCACHE | PUFFS_KFLAG_IAONDEMAND;
191
192 if (argc != 2)
193 usage();
194
195 PUFFSOP_INIT(pops);
196
197 PUFFSOP_SET(pops, psshfs, fs, unmount);
198 PUFFSOP_SETFSNOP(pops, sync); /* XXX */
199 PUFFSOP_SET(pops, psshfs, fs, statvfs);
200 PUFFSOP_SET(pops, psshfs, fs, nodetofh);
201 PUFFSOP_SET(pops, psshfs, fs, fhtonode);
202
203 PUFFSOP_SET(pops, psshfs, node, lookup);
204 PUFFSOP_SET(pops, psshfs, node, create);
205 PUFFSOP_SET(pops, psshfs, node, open);
206 PUFFSOP_SET(pops, psshfs, node, inactive);
207 PUFFSOP_SET(pops, psshfs, node, readdir);
208 PUFFSOP_SET(pops, psshfs, node, getattr);
209 PUFFSOP_SET(pops, psshfs, node, setattr);
210 PUFFSOP_SET(pops, psshfs, node, mkdir);
211 PUFFSOP_SET(pops, psshfs, node, remove);
212 PUFFSOP_SET(pops, psshfs, node, readlink);
213 PUFFSOP_SET(pops, psshfs, node, rmdir);
214 PUFFSOP_SET(pops, psshfs, node, symlink);
215 PUFFSOP_SET(pops, psshfs, node, rename);
216 PUFFSOP_SET(pops, psshfs, node, read);
217 PUFFSOP_SET(pops, psshfs, node, write);
218 PUFFSOP_SET(pops, psshfs, node, reclaim);
219
220 pu = puffs_init(pops, argv[0], "psshfs", &pctx, pflags);
221 if (pu == NULL)
222 err(1, "puffs_init");
223
224 memset(&pctx, 0, sizeof(pctx));
225 pctx.mounttime = time(NULL);
226 pctx.refreshival = refreshival;
227 pctx.numconnections = numconnections;
228
229 userhost = argv[0];
230 hostpath = strchr(userhost, ':');
231 if (hostpath) {
232 *hostpath++ = '\0';
233 pctx.mountpath = hostpath;
234 } else
235 pctx.mountpath = ".";
236
237 add_ssharg(&sshargs, &nargs, argv[0]);
238 add_ssharg(&sshargs, &nargs, "sftp");
239 pctx.sshargs = sshargs;
240
241 pctx.nextino = 2;
242 memset(root, 0, sizeof(struct psshfs_node));
243 pn_root = puffs_pn_new(pu, root);
244 if (pn_root == NULL)
245 return errno;
246 puffs_setroot(pu, pn_root);
247
248 puffs_framev_init(pu, psbuf_read, psbuf_write, psbuf_cmp, NULL, notfn);
249
250 signal(SIGHUP, takehup);
251 puffs_ml_setloopfn(pu, psshfs_loopfn);
252 if (pssh_connect(pu, PSSHFD_META) == -1)
253 err(1, "can't connect meta");
254 if (puffs_framev_addfd(pu, pctx.sshfd,
255 PUFFS_FBIO_READ | PUFFS_FBIO_WRITE) == -1)
256 err(1, "framebuf addfd meta");
257 if (numconnections == 2) {
258 if (pssh_connect(pu, PSSHFD_DATA) == -1)
259 err(1, "can't connect data");
260 if (puffs_framev_addfd(pu, pctx.sshfd_data,
261 PUFFS_FBIO_READ | PUFFS_FBIO_WRITE) == -1)
262 err(1, "framebuf addfd data");
263 } else {
264 pctx.sshfd_data = pctx.sshfd;
265 }
266
267 if (exportfs)
268 puffs_setfhsize(pu, sizeof(struct psshfs_fid),
269 PUFFS_FHFLAG_NFSV2 | PUFFS_FHFLAG_NFSV3);
270
271 rva = &pn_root->pn_va;
272 rva->va_fileid = pctx.nextino++;
273 rva->va_nlink = 101; /* XXX */
274
275 if (detach)
276 if (puffs_daemon(pu, 1, 1) == -1)
277 err(1, "puffs_daemon");
278
279 if (puffs_mount(pu, argv[1], mntflags, puffs_getroot(pu)) == -1)
280 err(1, "puffs_mount");
281 if (puffs_setblockingmode(pu, PUFFSDEV_NONBLOCK) == -1)
282 err(1, "setblockingmode");
283
284 if (puffs_mainloop(pu) == -1)
285 err(1, "mainloop");
286 puffs_exit(pu, 1);
287
288 return 0;
289 }
290
291 #define RETRY_MAX 100
292
293 void
294 psshfs_notify(struct puffs_usermount *pu, int fd, int what)
295 {
296 struct psshfs_ctx *pctx = puffs_getspecific(pu);
297 int x, nretry, which, newfd;
298
299 if (fd == pctx->sshfd) {
300 which = PSSHFD_META;
301 } else {
302 assert(fd == pctx->sshfd_data);
303 which = PSSHFD_DATA;
304 }
305
306 if (puffs_getstate(pu) != PUFFS_STATE_RUNNING)
307 return;
308
309 if (what != (PUFFS_FBIO_READ | PUFFS_FBIO_WRITE)) {
310 puffs_framev_removefd(pu, fd, ECONNRESET);
311 return;
312 }
313 close(fd);
314
315 for (nretry = 0;;nretry++) {
316 if ((newfd = pssh_connect(pu, which)) == -1)
317 goto retry2;
318
319 if (psshfs_handshake(pu, newfd) != 0)
320 goto retry1;
321
322 x = 1;
323 if (ioctl(newfd, FIONBIO, &x) == -1)
324 goto retry1;
325
326 if (puffs_framev_addfd(pu, newfd,
327 PUFFS_FBIO_READ | PUFFS_FBIO_WRITE) == -1)
328 goto retry1;
329
330 break;
331 retry1:
332 fprintf(stderr, "reconnect failed... ");
333 close(newfd);
334 retry2:
335 if (nretry < RETRY_MAX) {
336 fprintf(stderr, "retrying\n");
337 sleep(nretry);
338 } else {
339 fprintf(stderr, "retry count exceeded, going south\n");
340 exit(1); /* XXXXXXX */
341 }
342 }
343 }
344
345 static int
346 pssh_connect(struct puffs_usermount *pu, int which)
347 {
348 struct psshfs_ctx *pctx = puffs_getspecific(pu);
349 char **sshargs = pctx->sshargs;
350 int fds[2];
351 pid_t pid;
352 int dnfd, x;
353 int *sshfd;
354 pid_t *sshpid;
355
356 if (which == PSSHFD_META) {
357 sshfd = &pctx->sshfd;
358 sshpid = &pctx->sshpid;
359 } else {
360 assert(which == PSSHFD_DATA);
361 sshfd = &pctx->sshfd_data;
362 sshpid = &pctx->sshpid_data;
363 }
364
365 if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == -1)
366 return -1;
367
368 pid = fork();
369 switch (pid) {
370 case -1:
371 return -1;
372 /*NOTREACHED*/
373 case 0: /* child */
374 if (dup2(fds[0], STDIN_FILENO) == -1)
375 err(1, "child dup2");
376 if (dup2(fds[0], STDOUT_FILENO) == -1)
377 err(1, "child dup2");
378 close(fds[0]);
379 close(fds[1]);
380
381 dnfd = open(_PATH_DEVNULL, O_RDWR);
382 if (dnfd != -1)
383 dup2(dnfd, STDERR_FILENO);
384
385 execvp(sshargs[0], sshargs);
386 /*NOTREACHED*/
387 break;
388 default:
389 *sshpid = pid;
390 *sshfd = fds[1];
391 close(fds[0]);
392 break;
393 }
394
395 if (psshfs_handshake(pu, *sshfd) != 0)
396 errx(1, "psshfs_handshake %d", which);
397 x = 1;
398 if (ioctl(*sshfd, FIONBIO, &x) == -1)
399 err(1, "nonblocking descriptor %d", which);
400
401 return *sshfd;
402 }
403
404 static void *
405 invalone(struct puffs_usermount *pu, struct puffs_node *pn, void *arg)
406 {
407 struct psshfs_node *psn = pn->pn_data;
408
409 psn->attrread = 0;
410 psn->dentread = 0;
411 psn->slread = 0;
412
413 return NULL;
414 }
415
416 static void
417 psshfs_loopfn(struct puffs_usermount *pu)
418 {
419
420 if (sighup) {
421 puffs_pn_nodewalk(pu, invalone, NULL);
422 sighup = 0;
423 }
424 }
425