1/*	$NetBSD: puffs_portal.c,v 1.4 2011/08/29 14:35:02 joerg Exp $	*/
2
3/*
4 * Copyright (c) 2007  Antti Kantee.  All Rights Reserved.
5 * Development was supported by the Finnish Cultural Foundation.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
17 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29#include <sys/cdefs.h>
30#ifndef lint
31__RCSID("$NetBSD: puffs_portal.c,v 1.4 2011/08/29 14:35:02 joerg Exp $");
32#endif /* !lint */
33
34#include <sys/types.h>
35#include <sys/wait.h>
36
37#include <assert.h>
38#include <err.h>
39#include <errno.h>
40#include <mntopts.h>
41#include <paths.h>
42#include <poll.h>
43#include <puffs.h>
44#include <stdio.h>
45#include <stdlib.h>
46#include <string.h>
47#include <unistd.h>
48#include <util.h>
49
50#include "portald.h"
51
52struct portal_node {
53	char *path;
54	int fd;
55};
56
57__dead static void usage(void);
58
59PUFFSOP_PROTOS(portal);
60
61#define PORTAL_ROOT NULL
62#define METADATASIZE (sizeof(int) + sizeof(size_t))
63
64qelem q;
65int readcfg, sigchild;
66const char *cfg;
67
68static void
69usage()
70{
71
72	errx(1, "usage: %s [-o options] /path/portal.conf mount_point",
73	    getprogname());
74}
75
76static void
77sighup(int sig)
78{
79
80	readcfg = 1;
81}
82
83static void
84sigcry(int sig)
85{
86
87	sigchild = 1;
88}
89
90static void
91portal_loopfn(struct puffs_usermount *pu)
92{
93
94	if (readcfg)
95		conf_read(&q, cfg);
96	readcfg = 0;
97
98	if (sigchild) {
99		sigchild = 0;
100		while (waitpid(-1, NULL, WNOHANG) != -1)
101			continue;
102	}
103}
104
105#define PUFBUF_FD	1
106#define PUFBUF_DATA	2
107
108#define CMSIZE (sizeof(struct cmsghdr) + sizeof(int))
109
110/* receive file descriptor produced by our child process */
111static int
112readfd(struct puffs_framebuf *pufbuf, int fd, int *done)
113{
114	struct cmsghdr *cmp;
115	struct msghdr msg;
116	struct iovec iov;
117	ssize_t n;
118	int error, rv;
119
120	rv = 0;
121	cmp = emalloc(CMSG_SPACE(sizeof(int)));
122
123	iov.iov_base = &error;
124	iov.iov_len = sizeof(int);
125	msg.msg_iov = &iov;
126	msg.msg_iovlen = 1;
127	msg.msg_name = NULL;
128	msg.msg_namelen = 0;
129	msg.msg_control = cmp;
130	msg.msg_controllen = CMSG_SPACE(sizeof(int));
131
132	n = recvmsg(fd, &msg, 0);
133	if (n == -1) {
134		rv = errno;
135		goto out;
136	}
137	if (n == 0) {
138		rv = ECONNRESET;
139		goto out;
140	}
141
142	/* the data for the server */
143	puffs_framebuf_putdata_atoff(pufbuf, 0, &error, sizeof(int));
144	if (error) {
145		rv = error;
146		goto out;
147	}
148	puffs_framebuf_putdata_atoff(pufbuf, sizeof(int),
149	    CMSG_DATA(cmp), sizeof(int));
150	*done = 1;
151
152 out:
153	free(cmp);
154	return rv;
155}
156
157/*
158 * receive data from provider
159 *
160 * XXX: should read directly into the buffer and adjust offsets
161 * instead of doing memcpy
162 */
163static int
164readdata(struct puffs_framebuf *pufbuf, int fd, int *done)
165{
166	char buf[1024];
167	size_t max;
168	ssize_t n;
169	size_t moved;
170
171	/* don't override metadata */
172	if (puffs_framebuf_telloff(pufbuf) == 0)
173		puffs_framebuf_seekset(pufbuf, METADATASIZE);
174	puffs_framebuf_getdata_atoff(pufbuf, sizeof(int), &max, sizeof(size_t));
175	moved = puffs_framebuf_tellsize(pufbuf) - METADATASIZE;
176	assert(max >= moved);
177	max -= moved;
178
179	do {
180		n = read(fd, buf, MIN(sizeof(buf), max));
181		if (n == 0) {
182			if (moved)
183				break;
184			else
185				return -1; /* caught by read */
186		}
187		if (n < 0) {
188			if (moved)
189				return 0;
190
191			if (errno != EAGAIN)
192				return errno;
193			else
194				return 0;
195		}
196
197		puffs_framebuf_putdata(pufbuf, buf, n);
198		moved += n;
199		max -= n;
200	} while (max > 0);
201
202	*done = 1;
203
204	return 0;
205}
206
207static int
208portal_frame_rf(struct puffs_usermount *pu, struct puffs_framebuf *pufbuf,
209	int fd, int *done)
210{
211	int type;
212
213	if (puffs_framebuf_getdata_atoff(pufbuf, 0, &type, sizeof(int)) == -1)
214		return EINVAL;
215
216	if (type == PUFBUF_FD)
217		return readfd(pufbuf, fd, done);
218	else if (type == PUFBUF_DATA)
219		return readdata(pufbuf, fd, done);
220	else
221		abort();
222}
223
224static int
225portal_frame_wf(struct puffs_usermount *pu, struct puffs_framebuf *pufbuf,
226	int fd, int *done)
227{
228	void *win;
229	size_t pbsize, pboff, winlen;
230	ssize_t n;
231	int error;
232
233	pboff = puffs_framebuf_telloff(pufbuf);
234	pbsize = puffs_framebuf_tellsize(pufbuf);
235	error = 0;
236
237	do {
238		assert(pbsize > pboff);
239		winlen = pbsize - pboff;
240		if (puffs_framebuf_getwindow(pufbuf, pboff, &win, &winlen)==-1)
241			return errno;
242		n = write(fd, win, winlen);
243		if (n == 0) {
244			if (pboff != 0)
245				break;
246			else
247				return -1; /* caught by node_write */
248		}
249		if (n < 0) {
250			if (pboff != 0)
251				break;
252
253			if (errno != EAGAIN)
254				return errno;
255			return 0;
256		}
257
258		pboff += n;
259		puffs_framebuf_seekset(pufbuf, pboff);
260	} while (pboff != pbsize);
261
262	*done = 1;
263	puffs_framebuf_putdata_atoff(pufbuf, 0, &pboff, sizeof(size_t));
264	return error;
265}
266
267/* transfer file descriptor to master file server */
268static int
269sendfd(int s, int fd, int error)
270{
271	struct cmsghdr *cmp;
272	struct msghdr msg;
273	struct iovec iov;
274	ssize_t n;
275	int rv;
276
277	rv = 0;
278	cmp = emalloc(CMSG_LEN(sizeof(int)));
279
280	iov.iov_base = &error;
281	iov.iov_len = sizeof(int);
282
283	msg.msg_iov = &iov;
284	msg.msg_iovlen = 1;
285	msg.msg_name = NULL;
286	msg.msg_namelen = 0;
287	if (error == 0) {
288		cmp->cmsg_level = SOL_SOCKET;
289		cmp->cmsg_type = SCM_RIGHTS;
290		cmp->cmsg_len = CMSG_LEN(sizeof(int));
291
292		msg.msg_control = cmp;
293		msg.msg_controllen = CMSG_LEN(sizeof(int));
294		*(int *)CMSG_DATA(cmp) = fd;
295	} else {
296		msg.msg_control = NULL;
297		msg.msg_controllen = 0;
298	}
299
300	n = sendmsg(s, &msg, 0);
301	if (n == -1)
302		rv = errno;
303	else if (n < (ssize_t)sizeof(int))
304		rv = EPROTO;
305
306	free(cmp);
307	return rv;
308}
309
310/*
311 * Produce I/O file descriptor by forking (like original portald).
312 *
313 * child: run provider and transfer produced fd to parent
314 * parent: yield until child produces fd.  receive it and store it.
315 */
316static int
317provide(struct puffs_usermount *pu, struct portal_node *portn,
318	struct portal_cred *portc, char **v)
319{
320	struct puffs_cc *pcc = puffs_cc_getcc(pu);
321	struct puffs_framebuf *pufbuf;
322	int s[2];
323	int fd, error;
324	int data;
325
326	pufbuf = puffs_framebuf_make();
327	if (pufbuf == NULL)
328		return ENOMEM;
329
330	data = PUFBUF_FD;
331	if (puffs_framebuf_putdata(pufbuf, &data, sizeof(int)) == -1)
332		goto bad;
333
334	if (socketpair(AF_LOCAL, SOCK_STREAM, 0, s) == -1)
335		goto bad;
336
337	switch (fork()) {
338	case -1:
339		goto bad;
340	case 0:
341		error = activate_argv(portc, portn->path, v, &fd);
342		sendfd(s[1], fd, error);
343		exit(0);
344	default:
345		puffs_framev_addfd(pu, s[0], PUFFS_FBIO_READ);
346		puffs_framev_enqueue_directreceive(pcc, s[0], pufbuf, 0);
347		puffs_framev_removefd(pu, s[0], 0);
348		close(s[0]);
349		close(s[1]);
350
351		if (puffs_framebuf_tellsize(pufbuf) < sizeof(int)) {
352			errno = EIO;
353			goto bad;
354		}
355		puffs_framebuf_getdata_atoff(pufbuf, 0, &error, sizeof(int));
356		if (error) {
357			errno = error;
358			goto bad;
359		}
360
361		if (puffs_framebuf_tellsize(pufbuf) != 2*sizeof(int)) {
362			errno = EIO;
363			goto bad;
364		}
365
366		puffs_framebuf_getdata_atoff(pufbuf, sizeof(int),
367		    &fd, sizeof(int));
368		puffs_framebuf_destroy(pufbuf);
369
370		data = 1;
371		if (ioctl(fd, FIONBIO, &data) == -1)
372			return errno;
373
374		if (puffs_framev_addfd(pu, fd, PUFFS_FBIO_WRITE) == -1)
375			return errno;
376
377		portn->fd = fd;
378		return 0;
379	}
380
381 bad:
382	puffs_framebuf_destroy(pufbuf);
383	return errno;
384}
385
386int
387main(int argc, char *argv[])
388{
389	extern char *optarg;
390	extern int optind;
391	struct puffs_usermount *pu;
392	struct puffs_ops *pops;
393	mntoptparse_t mp;
394	int pflags, mntflags;
395	int detach;
396	int ch;
397
398	setprogname(argv[0]);
399
400	mntflags = pflags = 0;
401	detach = 1;
402	while ((ch = getopt(argc, argv, "o:s")) != -1) {
403		switch (ch) {
404		case 'o':
405			mp = getmntopts(optarg, puffsmopts, &mntflags, &pflags);
406			if (mp == NULL)
407				err(1, "getmntopts");
408			freemntopts(mp);
409			break;
410		case 's': /* stay on top */
411			detach = 0;
412			break;
413		default:
414			usage();
415			/*NOTREACHED*/
416		}
417	}
418	pflags |= PUFFS_KFLAG_NOCACHE | PUFFS_KFLAG_LOOKUP_FULLPNBUF;
419	if (pflags & PUFFS_FLAG_OPDUMP)
420		detach = 0;
421	argc -= optind;
422	argv += optind;
423
424	if (argc != 2)
425		usage();
426
427	PUFFSOP_INIT(pops);
428
429	PUFFSOP_SETFSNOP(pops, unmount);
430	PUFFSOP_SETFSNOP(pops, sync);
431	PUFFSOP_SETFSNOP(pops, statvfs);
432
433	PUFFSOP_SET(pops, portal, node, lookup);
434	PUFFSOP_SET(pops, portal, node, getattr);
435	PUFFSOP_SET(pops, portal, node, setattr);
436	PUFFSOP_SET(pops, portal, node, open);
437	PUFFSOP_SET(pops, portal, node, read);
438	PUFFSOP_SET(pops, portal, node, write);
439	PUFFSOP_SET(pops, portal, node, seek);
440	PUFFSOP_SET(pops, portal, node, poll);
441	PUFFSOP_SET(pops, portal, node, inactive);
442	PUFFSOP_SET(pops, portal, node, reclaim);
443
444	pu = puffs_init(pops, _PATH_PUFFS, "portal", NULL, pflags);
445	if (pu == NULL)
446		err(1, "init");
447
448	if (signal(SIGHUP, sighup) == SIG_ERR)
449		warn("cannot set sighup handler");
450	if (signal(SIGCHLD, sigcry) == SIG_ERR)
451		err(1, "cannot set sigchild handler");
452	if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
453		err(1, "cannot ignore sigpipe");
454
455	readcfg = 0;
456	cfg = argv[0];
457	if (*cfg != '/')
458		errx(1, "need absolute path for config");
459	q.q_forw = q.q_back = &q;
460	if (conf_read(&q, cfg) == -1)
461		err(1, "cannot read cfg \"%s\"", cfg);
462
463	puffs_ml_setloopfn(pu, portal_loopfn);
464	puffs_framev_init(pu, portal_frame_rf, portal_frame_wf, NULL,NULL,NULL);
465
466	if (detach)
467		if (puffs_daemon(pu, 1, 1) == -1)
468			err(1, "puffs_daemon");
469
470	if (puffs_mount(pu,  argv[1], mntflags, PORTAL_ROOT) == -1)
471		err(1, "mount");
472	if (puffs_mainloop(pu) == -1)
473		err(1, "mainloop");
474
475	return 0;
476}
477
478static struct portal_node *
479makenode(const char *path)
480{
481	struct portal_node *portn;
482
483	portn = emalloc(sizeof(struct portal_node));
484	portn->path = estrdup(path);
485	portn->fd = -1;
486
487	return portn;
488}
489
490static void
491credtr(struct portal_cred *portc, const struct puffs_cred *puffc, int mode)
492{
493	memset(portc, 0, sizeof(struct portal_cred));
494
495	portc->pcr_flag = mode;
496	puffs_cred_getuid(puffc, &portc->pcr_uid);
497	puffs_cred_getgid(puffc, &portc->pcr_gid);
498	puffs_cred_getgroups(puffc, portc->pcr_groups,
499	    (short *)&portc->pcr_ngroups);
500}
501
502/*
503 * XXX: we could also simply already resolve the name at this stage
504 * instead of deferring it to open.  But doing it in open is how the
505 * original portald does it, and I don't want to introduce any funny
506 * incompatibilities.
507 */
508int
509portal_node_lookup(struct puffs_usermount *pu, puffs_cookie_t opc,
510	struct puffs_newinfo *pni, const struct puffs_cn *pcn)
511{
512	struct portal_node *portn;
513
514	assert(opc == PORTAL_ROOT);
515
516	if (pcn->pcn_nameiop != NAMEI_LOOKUP
517	    && pcn->pcn_nameiop != NAMEI_CREATE)
518		return EOPNOTSUPP;
519
520	portn = makenode(pcn->pcn_name);
521	puffs_newinfo_setcookie(pni, portn);
522	puffs_newinfo_setvtype(pni, VREG);
523
524	pcn->pcn_flags &= ~NAMEI_REQUIREDIR;
525	pcn->pcn_consume = strlen(pcn->pcn_name) - pcn->pcn_namelen;
526
527	return 0;
528}
529
530int fakeid = 3;
531
532/* XXX: libpuffs'ize */
533int
534portal_node_getattr(struct puffs_usermount *pu, puffs_cookie_t opc,
535	struct vattr *va, const struct puffs_cred *pcr)
536{
537	struct timeval tv;
538	struct timespec ts;
539
540	puffs_vattr_null(va);
541	if (opc == PORTAL_ROOT) {
542		va->va_type = VDIR;
543		va->va_mode = 0777;
544		va->va_nlink = 2;
545	} else {
546		va->va_type = VREG;
547		va->va_mode = 0666;
548		va->va_nlink = 1;
549	}
550	va->va_uid = va->va_gid = 0;
551	va->va_fileid = fakeid++;
552	va->va_size = va->va_bytes = 0;
553	va->va_gen = 0;
554	va->va_rdev = PUFFS_VNOVAL;
555	va->va_blocksize = DEV_BSIZE;
556
557	gettimeofday(&tv, NULL);
558	TIMEVAL_TO_TIMESPEC(&tv, &ts);
559	va->va_atime = va->va_ctime = va->va_mtime = va->va_birthtime = ts;
560
561	return 0;
562}
563
564/* for writing, just pretend we care */
565int
566portal_node_setattr(struct puffs_usermount *pu, puffs_cookie_t opc,
567	const struct vattr *va, const struct puffs_cred *pcr)
568{
569
570	return 0;
571}
572
573int
574portal_node_open(struct puffs_usermount *pu, puffs_cookie_t opc, int mode,
575	const struct puffs_cred *pcr)
576{
577	struct portal_node *portn = opc;
578	struct portal_cred portc;
579	char **v;
580
581	if (opc == PORTAL_ROOT)
582		return 0;
583
584	if (mode & O_NONBLOCK)
585		return EOPNOTSUPP;
586
587	v = conf_match(&q, portn->path);
588	if (v == NULL)
589		return ENOENT;
590
591	credtr(&portc, pcr, mode);
592	return provide(pu, portn, &portc, v);
593}
594
595int
596portal_node_read(struct puffs_usermount *pu, puffs_cookie_t opc,
597	uint8_t *buf, off_t offset, size_t *resid,
598	const struct puffs_cred *pcr, int ioflag)
599{
600	struct puffs_cc *pcc = puffs_cc_getcc(pu);
601	struct portal_node *portn = opc;
602	struct puffs_framebuf *pufbuf;
603	size_t xfersize, winsize, boff;
604	void *win;
605	int rv, error;
606	int data, dummy;
607
608	assert(opc != PORTAL_ROOT);
609	error = 0;
610
611	/* if we can't (re-)enable it, treat it as EOF */
612	rv = puffs_framev_enablefd(pu, portn->fd, PUFFS_FBIO_READ);
613	if (rv == -1)
614		return 0;
615
616	pufbuf = puffs_framebuf_make();
617	data = PUFBUF_DATA;
618	puffs_framebuf_putdata(pufbuf, &data, sizeof(int));
619	puffs_framebuf_putdata(pufbuf, resid, sizeof(size_t));
620
621	/* if we are doing nodelay, do read directly */
622	if (ioflag & PUFFS_IO_NDELAY) {
623		rv = readdata(pufbuf, portn->fd, &dummy);
624		if (rv != 0) {
625			error = rv;
626			goto out;
627		}
628	} else {
629		rv = puffs_framev_enqueue_directreceive(pcc,
630		    portn->fd, pufbuf, 0);
631
632		if (rv == -1) {
633			error = errno;
634			goto out;
635		}
636	}
637
638	xfersize = puffs_framebuf_tellsize(pufbuf) - METADATASIZE;
639	if (xfersize == 0) {
640		assert(ioflag & PUFFS_IO_NDELAY);
641		error = EAGAIN;
642		goto out;
643	}
644
645	*resid -= xfersize;
646	boff = 0;
647	while (xfersize > 0) {
648		winsize = xfersize;
649		rv = puffs_framebuf_getwindow(pufbuf, METADATASIZE,
650		    &win, &winsize);
651		assert(rv == 0);
652		assert(winsize > 0);
653
654		memcpy(buf + boff, win, winsize);
655		xfersize -= winsize;
656		boff += winsize;
657	}
658
659 out:
660	puffs_framev_disablefd(pu, portn->fd, PUFFS_FBIO_READ);
661	puffs_framebuf_destroy(pufbuf);
662
663	/* a trickery, from readdata() */
664	if (error == -1)
665		return 0;
666	return error;
667}
668
669int
670portal_node_write(struct puffs_usermount *pu, puffs_cookie_t opc,
671	uint8_t *buf, off_t offset, size_t *resid,
672	const struct puffs_cred *pcr, int ioflag)
673{
674	struct puffs_cc *pcc = puffs_cc_getcc(pu);
675	struct portal_node *portn = opc;
676	struct puffs_framebuf *pufbuf;
677	size_t written;
678	int error, rv, dummy;
679
680	assert(opc != PORTAL_ROOT);
681
682	pufbuf = puffs_framebuf_make();
683	puffs_framebuf_putdata(pufbuf, buf, *resid);
684
685	error = 0;
686	if (ioflag & PUFFS_IO_NDELAY) {
687		rv = portal_frame_wf(pu, pufbuf, portn->fd, &dummy);
688		if (rv) {
689			error = rv;
690			goto out;
691		}
692	} else  {
693		rv = puffs_framev_enqueue_directsend(pcc, portn->fd, pufbuf, 0);
694		if (rv == -1) {
695			error = errno;
696			goto out;
697		}
698	}
699
700	rv = puffs_framebuf_getdata_atoff(pufbuf, 0, &written, sizeof(size_t));
701	assert(rv == 0);
702	assert(written <= *resid);
703	*resid -= written;
704
705 out:
706	puffs_framebuf_destroy(pufbuf);
707	if (error == -1)
708		error = 0;
709	return 0;
710}
711
712int
713portal_node_seek(struct puffs_usermount *pu, puffs_cookie_t opc,
714	off_t oldoff, off_t newoff, const struct puffs_cred *pcr)
715{
716	struct portal_node *portn = opc;
717
718	if (opc == PORTAL_ROOT || portn->fd == -1)
719		return EOPNOTSUPP;
720
721	if (lseek(portn->fd, newoff, SEEK_SET) == -1)
722		return errno;
723	return 0;
724}
725
726int
727portal_node_poll(struct puffs_usermount *pu, puffs_cookie_t opc, int *events)
728{
729	struct puffs_cc *pcc = puffs_cc_getcc(pu);
730	struct portal_node *portn = opc;
731	int what;
732
733	what = 0;
734	if (*events & POLLIN)
735		what |= PUFFS_FBIO_READ;
736	if (*events & POLLOUT)
737		what |= PUFFS_FBIO_WRITE;
738	if (*events & POLLERR)
739		what |= PUFFS_FBIO_ERROR;
740
741	if (puffs_framev_enqueue_waitevent(pcc, portn->fd, &what) == -1) {
742		*events = POLLERR;
743		return errno;
744	}
745
746	*events = 0;
747	if (what & PUFFS_FBIO_READ)
748		*events |= POLLIN;
749	if (what & PUFFS_FBIO_WRITE)
750		*events |= POLLOUT;
751	if (what & PUFFS_FBIO_ERROR)
752		*events |= POLLERR;
753
754	return 0;
755}
756
757int
758portal_node_inactive(struct puffs_usermount *pu, puffs_cookie_t opc)
759{
760
761	if (opc == PORTAL_ROOT)
762		return 0;
763
764	puffs_setback(puffs_cc_getcc(pu), PUFFS_SETBACK_NOREF_N1);
765	return 0;
766}
767
768int
769portal_node_reclaim(struct puffs_usermount *pu, puffs_cookie_t opc)
770{
771	struct portal_node *portn = opc;
772
773	if (portn->fd != -1) {
774		puffs_framev_removefd(pu, portn->fd, 0);
775		close(portn->fd);
776	}
777	free(portn->path);
778	free(portn);
779
780	return 0;
781}
782