/*	$NetBSD: fs.c,v 1.25 2019/09/23 12:00:58 christos Exp $	*/

/*
 * Copyright (c) 2006-2009  Antti Kantee.  All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: fs.c,v 1.25 2019/09/23 12:00:58 christos Exp $");
#endif /* !lint */

#include <err.h>
#include <errno.h>
#include <puffs.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include "psshfs.h"
#include "sftp_proto.h"

#define DO_IO(fname, a1, a2, a3, a4, rv)				\
do {									\
	puffs_framebuf_seekset(a2, 0);					\
	*(a4) = 0;							\
	rv = fname(a1, a2, a3, a4);					\
	if (rv) {							\
		return rv ? rv : EPROTO;				\
	}								\
} while (/*CONSTCOND*/0)

#define reterr(str, rv)							\
do {									\
	fprintf str;							\
	return rv;							\
} while (/*CONSTCOND*/0)

/* openssh extensions */
static const struct extunit {
	const char *ext;
	const char *val;
	int extflag;
} exttable[] = {
{
	"posix-rename@openssh.com",
	"1",
	SFTP_EXT_POSIX_RENAME,
},{
	"statvfs@openssh.com",
	"2",
	SFTP_EXT_STATVFS,
},{
	"fstatvfs@openssh.com",
	"2",
	SFTP_EXT_FSTATVFS,
},{
	NULL,
	NULL,
	0
}};

int
psshfs_handshake(struct puffs_usermount *pu, int fd)
{
	struct psshfs_ctx *pctx = puffs_getspecific(pu);
	struct puffs_framebuf *pb;
	struct puffs_pathobj *po_root;
	struct puffs_node *pn_root;
	struct vattr va, *rva;
	const struct extunit *extu;
	char *rootpath;
	char *ext, *val;
	uint32_t count;
	int rv, done;

	pb = psbuf_makeout();
	psbuf_put_1(pb, SSH_FXP_INIT);
	psbuf_put_4(pb, SFTP_PROTOVERSION);
	DO_IO(psbuf_write, pu, pb, fd, &done, rv);

	puffs_framebuf_recycle(pb);
	DO_IO(psbuf_read, pu, pb, fd, &done, rv);
	if (psbuf_get_type(pb) != SSH_FXP_VERSION)
		reterr((stderr, "invalid server response: %d",
		    psbuf_get_type(pb)), EPROTO);
	pctx->protover = psbuf_get_reqid(pb);

	/*
	 * Check out which extensions are available.  Currently
	 * we are only interested in the openssh statvfs extension.
	 */
	for (;;) {
		if (psbuf_get_str(pb, &ext, NULL) != 0)
			break;
		if (psbuf_get_str(pb, &val, NULL) != 0)
			break;

		for (extu = exttable; extu->ext; extu++)
			if (strcmp(ext, extu->ext) == 0
			    && strcmp(val, extu->val) == 0)
				pctx->extensions |= extu->extflag;
	}

	/* scope out our rootpath */
	psbuf_recycleout(pb);
	psbuf_put_1(pb, SSH_FXP_REALPATH);
	psbuf_put_4(pb, NEXTREQ(pctx));
	psbuf_put_str(pb, pctx->mountpath);
	DO_IO(psbuf_write, pu, pb, fd, &done, rv);

	puffs_framebuf_recycle(pb);
	DO_IO(psbuf_read, pu, pb, fd, &done, rv);
	if (psbuf_get_type(pb) != SSH_FXP_NAME)
		reterr((stderr, "invalid server realpath response for \"%s\"",
		    pctx->mountpath), EPROTO);
	if (psbuf_get_4(pb, &count) == -1)
		reterr((stderr, "invalid realpath response: count"), EPROTO);
	if (psbuf_get_str(pb, &rootpath, NULL) == -1)
		reterr((stderr, "invalid realpath response: rootpath"), EPROTO);

	/* stat the rootdir so that we know it's a dir */
	psbuf_recycleout(pb);
	psbuf_req_str(pb, SSH_FXP_LSTAT, NEXTREQ(pctx), rootpath);
	DO_IO(psbuf_write, pu, pb, fd, &done, rv);

	puffs_framebuf_recycle(pb);
	DO_IO(psbuf_read, pu, pb, fd, &done, rv);

	rv = psbuf_expect_attrs(pb, &va);
	if (rv)
		reterr((stderr, "couldn't stat rootpath"), rv);
	puffs_framebuf_destroy(pb);

	if (puffs_mode2vt(va.va_mode) != VDIR)
		reterr((stderr, "remote path (%s) not a directory", rootpath),
		    ENOTDIR);

	pn_root = puffs_getroot(pu);
	rva = &pn_root->pn_va;
	puffs_setvattr(rva, &va);

	po_root = puffs_getrootpathobj(pu);
	if (po_root == NULL)
		err(1, "getrootpathobj");
	po_root->po_path = rootpath;
	po_root->po_len = strlen(rootpath);

	return 0;
}

int
psshfs_fs_statvfs(struct puffs_usermount *pu, struct puffs_statvfs *sbp)
{
	PSSHFSAUTOVAR(pu);
	uint64_t tmpval;
	uint8_t type;

	memset(sbp, 0, sizeof(*sbp));
	sbp->f_bsize = sbp->f_frsize = sbp->f_iosize = 512;

	if ((pctx->extensions & SFTP_EXT_STATVFS) == 0)
		goto out;

	psbuf_req_str(pb, SSH_FXP_EXTENDED, reqid, "statvfs@openssh.com");
	psbuf_put_str(pb, pctx->mountpath);
	GETRESPONSE(pb, pctx->sshfd);

	type = psbuf_get_type(pb);
	if (type != SSH_FXP_EXTENDED_REPLY) {
		/* use the default */
		goto out;
	}

	psbuf_get_8(pb, &tmpval);
	sbp->f_bsize = tmpval;
	psbuf_get_8(pb, &tmpval);
	sbp->f_frsize = tmpval;
	psbuf_get_8(pb, &sbp->f_blocks);
	psbuf_get_8(pb, &sbp->f_bfree);
	psbuf_get_8(pb, &sbp->f_bavail);
	psbuf_get_8(pb, &sbp->f_files);
	psbuf_get_8(pb, &sbp->f_ffree);
	psbuf_get_8(pb, &sbp->f_favail);

	psbuf_get_8(pb, &tmpval); /* fsid */
	psbuf_get_8(pb, &tmpval); /* flag */
	psbuf_get_8(pb, &tmpval);
	sbp->f_namemax = tmpval;

	sbp->f_bresvd = sbp->f_bfree - sbp->f_bavail;
	sbp->f_fresvd = sbp->f_ffree - sbp->f_favail;

 out:
	PSSHFSRETURN(rv);
}

int
psshfs_fs_unmount(struct puffs_usermount *pu, int flags)
{
	struct psshfs_ctx *pctx = puffs_getspecific(pu);

	kill(pctx->sshpid, SIGTERM);
	close(pctx->sshfd);
	if (pctx->numconnections == 2) {
		kill(pctx->sshpid_data, SIGTERM);
		close(pctx->sshfd_data);
	}

	return 0;
}

int
psshfs_fs_nodetofh(struct puffs_usermount *pu, puffs_cookie_t cookie,
	void *fid, size_t *fidsize)
{
	struct psshfs_ctx *pctx = puffs_getspecific(pu);
	struct puffs_node *pn = cookie;
	struct psshfs_node *psn = pn->pn_data;
	struct psshfs_fid *pf = fid;

	pf->mounttime = pctx->mounttime;
	pf->node = pn;

	psn->stat |= PSN_HASFH;

	return 0;
}

int
psshfs_fs_fhtonode(struct puffs_usermount *pu, void *fid, size_t fidsize,
	struct puffs_newinfo *pni)
{
	struct psshfs_ctx *pctx = puffs_getspecific(pu);
	struct psshfs_fid *pf = fid;
	struct puffs_node *pn = pf->node;
	struct psshfs_node *psn;
	int rv;

	if (pf->mounttime != pctx->mounttime)
		return EINVAL;
	if (pn == 0)
		return EINVAL;
	psn = pn->pn_data;
	if ((psn->stat & PSN_HASFH) == 0)
		return EINVAL;

	/* update node attributes */
	rv = getnodeattr(pu, pn, NULL);
	if (rv)
		return EINVAL;

	puffs_newinfo_setcookie(pni, pn);
	puffs_newinfo_setvtype(pni, pn->pn_va.va_type);
	puffs_newinfo_setsize(pni, pn->pn_va.va_size);

	return 0;
}
