/*
 * _sio_mnt_unixpath.c
 *
 * Virtual Unix Filesystem Ops
 *
 * Authors: Khalil Amiri, CMU SCS/ECE, July 18 1997
 *          Sean Levy, CMU SCS, July 1999
 */
/*
 * Copyright (c) of Carnegie Mellon University, 1996,1997,1998,1999.
 *
 * Permission to reproduce, use, and prepare derivative works of
 * this software for internal use is granted provided the copyright
 * and "No Warranty" statements are included with all reproductions
 * and derivative works. This software may also be redistributed
 * without charge provided that the copyright and "No Warranty"
 * statements are included in all redistributions.
 *
 * NO WARRANTY. THIS SOFTWARE IS FURNISHED ON AN "AS IS" BASIS.
 * CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER
 * EXPRESSED OR IMPLIED AS TO THE MATTER INCLUDING, BUT NOT LIMITED
 * TO: WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY
 * OF RESULTS OR RESULTS OBTAINED FROM USE OF THIS SOFTWARE. CARNEGIE
 * MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT
 * TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
 */

#include "_sio_internal.h"
#include <sys/stat.h>
#include <fcntl.h>

#define _UNIX_MNT_DEBUG 0

struct _unixpath_open_file {
	sio_mode_t mode;
	int	datafd;
	int	labelfd;
};

static NASD_DECLARE_ONCE(_unixpath_init_once_block);
static int _unixpath_initted;

static u_int32_t _unixpath_pid;
static u_int32_t _unixpath_uniq;
NASD_DECLARE_STATIC_MUTEX(_unixpath_uniq_mutex);

sio_return_t	_sio_mnt_unixpath_createtest(void *fscookie,
		    sio_control_t *controls, sio_count_t controlcount,
		    int *score);
sio_return_t	_sio_mnt_unixpath_create(void *fscookie,
		    sio_control_t *controls, sio_count_t controlcount,
		    char *buf, int buflen);
sio_return_t	_sio_mnt_unixpath_open(void *fscookie, const char *fsfilename,
		    sio_mode_t mode, sio_control_t *controls,
		    sio_count_t controlcount, void **fsfilecookie);
/* XXX test */
sio_return_t	_sio_mnt_unixpath_unlink(void *fscookie,
		    const char *fsfilename);
/* XXX control */
sio_return_t	_sio_mnt_unixpath_close(void *filecookie);
void		_sio_mnt_unixpath_doio(void *filecookie,
		    struct _sio_iodesc *iod);

void
_unixpath_init()
{
  int rc;
	_unixpath_pid = getpid();

  rc = nasd_mutex_init(&_unixpath_uniq_mutex);
  if (rc) {
    NASD_PANIC();
  }
	_unixpath_uniq = 0;
	_unixpath_initted = 1;
}

struct _sio_mntpoint *
_sio_mnt_unixpath_init(const char *name, const char *path)
{
	struct _sio_mntpoint *nmp = NULL;
	sio_return_t err;
	int fd = -1;
	struct stat sb;

	nasd_once(&_unixpath_init_once_block, &_unixpath_init);
  if (!_unixpath_initted) {
    NASD_PANIC();
  }

	nmp = malloc(sizeof(*nmp));
	if (nmp == NULL) {
		err = SIO_ERR_VEND_OSERR_FIRST + ENOMEM;
		goto bad;
	}
	memset(nmp, '0', sizeof *nmp);

	nmp->mnt_name = strdup(name);
	if (nmp->mnt_name == NULL) {
		err = SIO_ERR_VEND_OSERR_FIRST + ENOMEM;
		goto bad;
	}

	fd = open(path, O_RDONLY, 0);
	if (fd == -1 || fstat(fd, &sb) == -1) {
		err = SIO_ERR_VEND_OSERR_FIRST + errno;
		goto bad;
	}
	if (!S_ISDIR(sb.st_mode)) {
		err = SIO_ERR_VEND_OSERR_FIRST + ENOTDIR;
		goto bad;
	}

	nmp->mnt_cookie = (void *)(long)fd;

	/* fill in the function switch */
	nmp->mnt_createtest = _sio_mnt_unixpath_createtest;
	nmp->mnt_create = _sio_mnt_unixpath_create;
	nmp->mnt_open = _sio_mnt_unixpath_open;
	nmp->mnt_test = NULL;
	nmp->mnt_unlink = _sio_mnt_unixpath_unlink;
	nmp->mnt_control = NULL;
	nmp->mnt_close = _sio_mnt_unixpath_close;
	nmp->mnt_doio = _sio_mnt_unixpath_doio;

	return (nmp);

bad:
	if (nmp != NULL) {
		if (nmp->mnt_name != NULL)
			free(nmp->mnt_name);
		if (nmp->mnt_cookie != NULL)
			free(nmp->mnt_name);
		free(nmp);
		if (fd != -1)
			close(fd);
	}
	_sio_warning("_sio_mnt_unixpath_init: %s (%s): %s\n", name, path,
	    sio_error_string(err));
	return (NULL);
}

sio_return_t
_sio_mnt_unixpath_createtest(void *fscookie, sio_control_t *controls,
    sio_count_t controlcount, int *score)
{

	/* XXX do something with controls */
	if (controlcount > 0)
		return (SIO_ERR_OP_UNSUPPORTED);

	/*
	 * XXX make sure there's some minimal amount of disk space
	 * available, etc.
	 */

	*score = 1;
	return (SIO_SUCCESS);
}

sio_return_t
_sio_mnt_unixpath_create(void *fscookie, sio_control_t *controls,
    sio_count_t controlcount, char *buf, int buflen)
{
	char fn[(3 * 8) + 3 + 5 + 1], labelbuf[SIO_MAX_LABEL_LEN];
	u_int32_t uniq, time32;
	int cwdfd, tmp;
	sio_return_t rv;

	time32 = time(NULL);
  NASD_LOCK_MUTEX(_unixpath_uniq_mutex);
	uniq = _unixpath_uniq++;
  NASD_UNLOCK_MUTEX(_unixpath_uniq_mutex);

	if (buflen < (3 * 8) + 3)
		_sio_panic("_sio_mnt_unixpath_create: buffer too short (%d)",
		    buflen);

	sprintf(buf, "%08x.%08x.%08x", _unixpath_pid, time32, uniq);

	cwdfd = open(".", O_RDONLY, 0);
	if (cwdfd == -1)
		_sio_warning("_sio_mnt_unixpath_create: couldn't get cwd");

	if (fchdir((long)fscookie) == -1) {
		_sio_warning("_sio_mnt_unixpath_create: couldn't set cwd");
		rv = SIO_ERR_VEND_OSERR_FIRST + errno;
		goto out;
	}

	if (mkdir(buf, 0777) == -1) {
		rv = SIO_ERR_VEND_OSERR_FIRST + errno;
		goto out;
	}

	sprintf(fn, "%s/data", buf);
	if ((tmp = open(fn, O_RDWR|O_CREAT, 0666)) == -1) {
		rv = SIO_ERR_VEND_OSERR_FIRST + errno;
		goto baddata;
	}
	(void)close(tmp);

	sprintf(fn, "%s/label", buf);
	if ((tmp = open(fn, O_RDWR|O_CREAT, 0666)) == -1) {
		rv = SIO_ERR_VEND_OSERR_FIRST + errno;
		goto badlabel;
	}
	memset(labelbuf, 0, sizeof labelbuf);
	if (write(tmp, labelbuf, sizeof labelbuf) != sizeof labelbuf) {
		rv = SIO_ERR_VEND_OSERR_FIRST + errno;
		goto badfilllabel;
	}
	(void)close(tmp);

	rv = SIO_SUCCESS;

out:
	if (cwdfd != -1) {
		fchdir(cwdfd);
		close(cwdfd);
	}
	return (rv);

badfilllabel:
	sprintf(fn, "%s/label", buf);
	(void)unlink(fn);
badlabel:
	sprintf(fn, "%s/data", buf);
	(void)unlink(fn);
baddata:
	(void)rmdir(buf);
	goto out;
}

sio_return_t
_sio_mnt_unixpath_open(void *fscookie, const char *fsfilename, sio_mode_t mode,
    sio_control_t *controls, sio_count_t controlcount, void **fsfilecookie)
{
	char fn[(3 * 8) + 3 + 5 + 1];
	struct _unixpath_open_file *of;
	sio_return_t rv;
	int cwdfd = -1, unixmode;

	/* XXX do something with controls */
	if (controlcount > 0)
		return (SIO_ERR_OP_UNSUPPORTED);

	of = malloc(sizeof *of);
	if (of == NULL)
		return (SIO_ERR_VEND_OSERR_FIRST + ENOMEM);

	of->mode = mode;
	of->datafd = of->labelfd = -1;

	if ((mode & SIO_MODE_READ|SIO_MODE_WRITE) ==
	    (SIO_MODE_READ|SIO_MODE_WRITE))
		unixmode = O_RDWR;
	else if ((mode & SIO_MODE_READ) == SIO_MODE_READ)
		unixmode = O_RDONLY;
	else if ((mode & SIO_MODE_WRITE) == SIO_MODE_WRITE)
		unixmode = O_WRONLY;

	cwdfd = open(".", O_RDONLY, 0);
	if (cwdfd == -1)
		_sio_warning("_sio_mnt_unixpath_open: couldn't get cwd");

	if (fchdir((long)fscookie) == -1) {
		_sio_warning("_sio_mnt_unixpath_open: couldn't set cwd");
		rv = SIO_ERR_VEND_OSERR_FIRST + errno;
		goto bad;
	}

	sprintf(fn, "%s/data", fsfilename);
	if ((of->datafd = open(fn, unixmode, 0)) == -1) {
		rv = SIO_ERR_VEND_OSERR_FIRST + errno;
		goto bad;
	}

	sprintf(fn, "%s/label", fsfilename);
	if ((of->labelfd = open(fn, unixmode, 0)) == -1) {
		rv = SIO_ERR_VEND_OSERR_FIRST + errno;
		goto bad;
	}

	*fsfilecookie = of;
	rv = SIO_SUCCESS;

out:
	if (cwdfd != -1) {
		fchdir(cwdfd);
		close(cwdfd);
	}
	return (rv);

bad:
	if (of != NULL) {
		if (of->datafd != -1)
			while (close(of->datafd) == -1 && errno == EINTR)
				;
		if (of->labelfd != -1)
			while (close(of->labelfd) == -1 && errno == EINTR)
				;
		free(of);
	}
	goto out;
}

/* XXX test */

sio_return_t
_sio_mnt_unixpath_unlink(void *fscookie,
    const char *fsfilename)
{
	char fn[(3 * 8) + 3 + 5 + 1];
	sio_return_t rv;
	int cwdfd;

	cwdfd = open(".", O_RDONLY, 0);
	if (cwdfd == -1)
		_sio_warning("_sio_mnt_unixpath_unlink: couldn't get cwd");

	if (fchdir((long)fscookie) == -1) {
		_sio_warning("_sio_mnt_unixpath_unlink: couldn't set cwd");
		rv = SIO_ERR_VEND_OSERR_FIRST + errno;
		goto out;
	}

	sprintf(fn, "%s/label", fsfilename);
	if (unlink(fn) == -1) {
		rv = SIO_ERR_VEND_OSERR_FIRST + errno;
		goto out;
	}

	sprintf(fn, "%s/data", fsfilename);
	if (unlink(fn) == -1)
		_sio_warning("_sio_mnt_unixpath_unlink: couldn't unlink %s",
		    fn);

	if (rmdir(fsfilename) == -1)
		_sio_warning("_sio_mnt_unixpath_unlink: couldn't rmdir %s",
		    fsfilename);

	rv = SIO_SUCCESS;

out:
	if (cwdfd != -1) {
		fchdir(cwdfd);
		close(cwdfd);
	}
	return (rv);
}

/* XXX control */

sio_return_t
_sio_mnt_unixpath_close(void *filecookie)
{
	struct _unixpath_open_file *of = filecookie;

	while (close(of->datafd) == -1 && errno == EINTR)
		;
	while (close(of->labelfd) == -1 && errno == EINTR)
		;
	free(of);

	return (SIO_SUCCESS);
}

void
_sio_mnt_unixpath_doio(void *filecookie, struct _sio_iodesc *iod)
{
	struct _unixpath_open_file *of = filecookie;
	sio_transfer_len_t fbytes, mbytes, f_elem_left, m_elem_left, f_chunk_left, m_chunk_left;
	sio_count_t fi, mi, f_chunk_index, m_chunk_index;
	off_t f_elem_off, f_chunk_off, io_offset;
	ssize_t m_elem_off, m_chunk_off, iofn_rv;
	ssize_t (*iofn)(int, void *, size_t);
	size_t todo, done;
	char *io_addr;

	if (((iod->iod_flags & _SIO_IOD_READ) != 0 &&
	     (of->mode & SIO_MODE_READ) == 0) ||
	    ((iod->iod_flags & _SIO_IOD_WRITE) != 0 &&
	     (of->mode & SIO_MODE_WRITE) == 0)) {
		iod->iod_transferlen = 0;
		iod->iod_result = SIO_ERR_INCORRECT_MODE;
		goto out;
	}
	if ((iod->iod_flags & _SIO_IOD_READ) != 0)
		iofn = (ssize_t (*)(int, void*, size_t))read;
	else
		iofn = (ssize_t (*)(int, void*, size_t))write;

	for (fbytes = 0, fi = 0; fi < iod->iod_fllen; fi++) {
		if (iod->iod_fl[fi].size < 0) {
			iod->iod_transferlen = 0;
			iod->iod_result = SIO_ERR_INVALID_FILE_LIST;
			goto out;
		}
		fbytes += iod->iod_fl[fi].size * iod->iod_fl[fi].element_cnt;
	}
	for (mbytes = 0, mi = 0; mi < iod->iod_mllen; mi++) {
		if (iod->iod_ml[mi].size < 0) {
			iod->iod_transferlen = 0;
			iod->iod_result = SIO_ERR_INVALID_MEMORY_LIST;
			goto out;
		}
		mbytes += iod->iod_ml[mi].size * iod->iod_ml[mi].element_cnt;
	}
	if (mbytes != fbytes) {
		iod->iod_transferlen = 0;
		iod->iod_result = SIO_ERR_UNEQUAL_LISTS;
		goto out;
	}

	done = 0;
	fi = mi = -1;
	f_elem_left = m_elem_left = 0;
	while (done < fbytes) {
		if (f_elem_left == 0) {
			fi++;
			if (fi == iod->iod_fllen)
				break;
			f_elem_left = iod->iod_fl[fi].size *
			    iod->iod_fl[fi].element_cnt;
			f_elem_off = 0 - iod->iod_fl[fi].stride;
			f_chunk_index = -1;
			f_chunk_left = 0;
		}
		if (f_chunk_left == 0) {
			f_chunk_index++;
			f_elem_off += iod->iod_fl[fi].stride;
			f_chunk_left = iod->iod_fl[fi].size;
			f_chunk_off = 0;
		}
		if (m_elem_left == 0) {
			mi++;
			if (mi == iod->iod_mllen)
				break;
			m_elem_left = iod->iod_ml[mi].size *
			    iod->iod_ml[mi].element_cnt;
			m_elem_off = 0 - iod->iod_ml[mi].stride;
			m_chunk_index = -1;
			m_chunk_left = 0;
		}
		if (m_chunk_left == 0) {
			m_chunk_index++;
			m_elem_off += iod->iod_ml[mi].stride;
			m_chunk_left = iod->iod_ml[mi].size;
			m_chunk_off = 0;
		}

		todo = f_chunk_left > m_chunk_left ?
		    m_chunk_left : f_chunk_left;

		io_offset = iod->iod_fl[fi].offset + f_elem_off + f_chunk_off;
		io_addr = (char *)iod->iod_ml[mi].addr + m_elem_off +
		    m_chunk_off;

#if _UNIX_MNT_DEBUG > 0
	_sio_warning("unixpath_doio: issuing io obj=%d, off=%d, size=%d\n", of->datafd, io_offset, todo);
#endif

	       if (lseek(of->datafd, io_offset, SEEK_SET) != io_offset) {
			iod->iod_result = SIO_ERR_VEND_OSERR_FIRST + errno;
			goto done;
		}
		if ((iofn_rv = (*iofn)(of->datafd, io_addr, todo)) != todo) {
			if (iofn_rv == -1)
				iod->iod_result = SIO_ERR_VEND_OSERR_FIRST +
				    errno;
			else { 
			  done += iofn_rv;
			  iod->iod_result = SIO_ERR_VEND_EOF;
#if _UNIX_MNT_DEBUG > 0
			  _sio_warning("unixpath_doio: io incomplete to obj=%d, off=%d, size=%d/out_len=%d\n", of->datafd, io_offset, todo, iofn_rv);
#endif		  
			goto done;
			}
		}

		done += iofn_rv;
		f_chunk_left -= todo;
		f_elem_left -= todo;
		f_chunk_off += todo;
		m_chunk_left -= todo;
		m_elem_left -= todo;
		m_chunk_off += todo;
	}
	iod->iod_result = SIO_SUCCESS;

done:
#if _UNIX_MNT_DEBUG > 0
	_sio_warning("unixpath_doio: io complete obj=%d, off=%d, done=%d\n", of->datafd, io_offset, done);
#endif
	iod->iod_transferlen = done;
out:
	NASD_LOCK_MUTEX(iod->iod_rcobj.rco_mutex);
	iod->iod_flags |= _SIO_IOD_DONE;
	NASD_UNLOCK_MUTEX(iod->iod_rcobj.rco_mutex);
	return;
}

/* Local Variables:  */
/* indent-tabs-mode: nil */
/* tab-width: 2 */
/* End: */
