/*
 * Copyright (c) 2000-2002 Silicon Graphics, Inc.
 * All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it would be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write the Free Software Foundation,
 * Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>
#include <dirent.h>
#include <assert.h>
#include <string.h>
#include <xfs/xfs.h>
#include <xfs/jdm.h>

#include "config.h"

#include "types.h"
#include "util.h"
#include "mlog.h"
#include "getdents.h"

extern size_t pgsz;

int
write_buf(char *bufp,
	   size_t bufsz,
	   void *contextp,
	   gwbfp_t get_write_buf_funcp,
	   wfp_t write_funcp)
{
	char *mbufp;	/* buffer obtained from manager */
	size_t mbufsz;/* size of buffer obtained from manager */

	while (bufsz) {
		int rval;

		assert(bufsz > 0);
		mbufp = (*get_write_buf_funcp)(contextp, bufsz, &mbufsz);
		assert(mbufsz <= bufsz);
		if (bufp) {
			(void)memcpy((void *)mbufp, (void *)bufp, mbufsz);
		} else {
			(void)memset((void *)mbufp, 0, mbufsz);
		}
		rval = (*write_funcp)(contextp, mbufp, mbufsz);
		if (rval) {
			return rval;
		}
		if (bufp) {
			bufp += mbufsz;
		}
		bufsz -= mbufsz;
	}

	return 0;
}

int
read_buf(char *bufp,
	  size_t bufsz,
	  void *contextp,
	  rfp_t read_funcp,
	  rrbfp_t return_read_buf_funcp,
	  int *statp)
{
	char *mbufp;		/* manager's buffer pointer */
	size_t mbufsz;	/* size of manager's buffer */
	int nread;

	nread = 0;
	*statp = 0;
	while (bufsz) {
		mbufp = (*read_funcp)(contextp, bufsz, &mbufsz, statp);
		if (*statp) {
			break;
		}
		assert(mbufsz <= bufsz);
		if (bufp) {
			(void)memcpy((void *)bufp, (void *)mbufp, mbufsz);
			bufp += mbufsz;
		}
		bufsz -= mbufsz;
		nread += (int)mbufsz;
		(*return_read_buf_funcp)(contextp, mbufp, mbufsz);
	}

	return nread;
}

char
*strncpyterm(char *s1, char *s2, size_t n)
{
	char *rval;

	if (n < 1) return 0;
	rval = strncpy(s1, s2, n);
	s1[n - 1] = 0;
	return rval;
}

int
bigstat_iter(jdm_fshandle_t *fshandlep,
	      int fsfd,
	      int selector,
	      xfs_ino_t start_ino,
	      bstat_cbfp_t fp,
	      void * cb_arg1,
	      bstat_seekfp_t seekfp,
	      void * seek_arg1,
	      int *statp,
	      bool_t (pfp)(int),
	      struct xfs_bstat *buf,
	      size_t buflenin)
{
	__s32 buflenout;
	xfs_ino_t lastino;
	int saved_errno;
	int bulkstatcnt;
        struct xfs_fsop_bulkreq bulkreq;

	/* stat set with return from callback func
	 */
	*statp = 0;

	/* NOT COOL: open will affect root dir's access time
	 */

	mlog(MLOG_NITTY,
	      "bulkstat iteration initiated: start_ino == %llu\n",
	      start_ino);

	/* quirk of the interface: I want to play in terms of the
	 * ino to begin with, and ino 0 is not used. so, ...
	 */
	if (start_ino > 0) {
		lastino = start_ino - 1;
	} else {
		lastino = 0;
	}
	mlog(MLOG_NITTY + 1,
	      "calling bulkstat\n");

	bulkstatcnt = 0;
	bulkreq.lastip = (__u64 *)&lastino;
	bulkreq.icount = buflenin;
	bulkreq.ubuffer = buf;
	bulkreq.ocount = &buflenout;
	while (!ioctl(fsfd, XFS_IOC_FSBULKSTAT, &bulkreq)) {
		struct xfs_bstat *p;
		struct xfs_bstat *endp;

		if (buflenout == 0) {
			mlog(MLOG_NITTY + 1,
			      "bulkstat returns buflen %d\n",
			      buflenout);
			return 0;
		}
		mlog(MLOG_NITTY + 1,
		      "bulkstat returns buflen %d ino %llu\n",
		      buflenout,
		      buf->bs_ino);
		for (p = buf, endp = buf + buflenout; p < endp; p++) {
			int rval;

			if (p->bs_ino == 0)
				continue;

			if (!p->bs_nlink || !p->bs_mode) {
				/* inode being modified, get synced data */
				mlog(MLOG_NITTY + 1,
				      "ino %llu needed second bulkstat\n",
				      p->bs_ino);

				if(bigstat_one(fsfd, p->bs_ino, p) < 0) {
				    mlog(MLOG_WARNING,
					  _("failed to get bulkstat information for inode %llu\n"),
					  p->bs_ino);
				    continue;
				}
				if (!p->bs_nlink || !p->bs_mode || !p->bs_ino) {
					mlog(MLOG_TRACE,
					      _("failed to get valid bulkstat information for inode %llu\n"),
					      p->bs_ino);
					continue;
				}
			}

			if ((p->bs_mode & S_IFMT) == S_IFDIR) {
				if (!(selector & BIGSTAT_ITER_DIR)){
					continue;
				}
			} else {
				if (!(selector & BIGSTAT_ITER_NONDIR)){
					continue;
				}
			}
			rval = (*fp)(cb_arg1, fshandlep, fsfd, p);
			if (rval) {
				*statp = rval;
				return 0;
			}
			if (pfp) (pfp)(PREEMPT_PROGRESSONLY);
		}

		if (pfp && (++bulkstatcnt % 10) == 0 &&
		     (pfp)(PREEMPT_FULL)) {
			return EINTR;
		}

		if (seekfp) {
			lastino = seekfp(seek_arg1, lastino);
			if (lastino == INO64MAX) {
				mlog(MLOG_DEBUG,
				      "bulkstat seeked to EOS\n");
				return 0;
			}

			mlog(MLOG_DEBUG,
			      "bulkstat seeked to %llu\n", lastino);

			lastino = (lastino > 0) ? lastino - 1 : 0;
		}

		mlog(MLOG_NITTY + 1,
		      "calling bulkstat\n");
	}

	saved_errno = errno;

	mlog(MLOG_NORMAL,
	      _("syssgi( SGI_FS_BULKSTAT ) on fsroot failed: %s\n"),
	      strerror(saved_errno));

	return saved_errno;
}

/* ARGSUSED */
int
bigstat_one(int fsfd,
	     xfs_ino_t ino,
	     struct xfs_bstat *statp)
{
	struct xfs_fsop_bulkreq bulkreq;
	int count = 0;

	assert(ino > 0);
	bulkreq.lastip = (__u64 *)&ino;
	bulkreq.icount = 1;
	bulkreq.ubuffer = statp;
	bulkreq.ocount = &count;
	return xfsctl(NULL, fsfd, XFS_IOC_FSBULKSTAT_SINGLE, &bulkreq);
}

/* call the given callback for each inode group in the filesystem.
 */
#define INOGRPLEN	256
int
inogrp_iter(int fsfd,
	     int (*fp)(void *arg1,
				int fsfd,
				struct xfs_inogrp *inogrp),
	     void * arg1,
	     int *statp)
{
	xfs_ino_t lastino;
	int inogrpcnt;
	struct xfs_inogrp *igrp;
	struct xfs_fsop_bulkreq bulkreq;

	/* stat set with return from callback func */
	*statp = 0;

	igrp = malloc(INOGRPLEN * sizeof(struct xfs_inogrp));
	if (!igrp) {
		mlog(MLOG_NORMAL | MLOG_ERROR,
		     _("malloc of stream context failed (%d bytes): %s\n"),
		     INOGRPLEN * sizeof(struct xfs_inogrp),
		     strerror(errno));
		return -1;
	}

	lastino = 0;
	inogrpcnt = 0;
	bulkreq.lastip = (__u64 *)&lastino;
	bulkreq.icount = INOGRPLEN;
	bulkreq.ubuffer = igrp;
	bulkreq.ocount = &inogrpcnt;
	while (!ioctl(fsfd, XFS_IOC_FSINUMBERS, &bulkreq)) {
		struct xfs_inogrp *p, *endp;

		if (inogrpcnt == 0) {
			free(igrp);
			return 0;
		}
		for (p = igrp, endp = igrp + inogrpcnt; p < endp; p++) {
			int rval;

			rval = (*fp)(arg1, fsfd, p);
			if (rval) {
				*statp = rval;
				free(igrp);
				return 0;
			}
		}
	}

	free(igrp);
	return errno;
}

/* calls the callback for every entry in the directory specified
 * by the stat buffer. supplies the callback with a file system
 * handle and a stat buffer, and the name from the dirent.
 *
 * NOTE: does NOT invoke callback for "." or ".."!
 *
 * if the callback returns non-zero, returns 1 with cbrval set to the
 * callback's return value. if syscall fails, returns -1 with errno set.
 * otherwise returns 0.
 *
 * caller may supply a dirent buffer. if not, will malloc one
 */
int
diriter(jdm_fshandle_t *fshandlep,
	 int fsfd,
	 struct xfs_bstat *statp,
	 int (*cbfp)(void *arg1,
			     jdm_fshandle_t *fshandlep,
			     int fsfd,
			     struct xfs_bstat *statp,
			     char *namep),
	 void *arg1,
	 int *cbrvalp,
	 char *usrgdp,
	 size_t usrgdsz)
{
	size_t gdsz;
	struct dirent *gdp;
	int fd;
	int gdcnt;
	int scrval;
	int cbrval;

	if (usrgdp) {
		assert(usrgdsz >= sizeof(struct dirent));
		gdsz = usrgdsz;
		gdp = (struct dirent *)usrgdp;
	} else {
		gdsz = pgsz;
		gdp = (struct dirent *)malloc(gdsz);
		assert(gdp);
	}

	/* open the directory
	 */
	fd = jdm_open(fshandlep, statp, O_RDONLY);
	if (fd < 0) {
		mlog(MLOG_NORMAL,
		      _("WARNING: unable to open directory ino %llu: %s\n"),
		      statp->bs_ino,
		      strerror(errno));
		*cbrvalp = 0;
		if (!usrgdp) {
			free((void *)gdp);
		}
		return -1;
	}
	assert((statp->bs_mode & S_IFMT) == S_IFDIR);

	/* lots of buffering done here, to achieve OS-independence.
	 * if proves to be to much overhead, can streamline.
	 */
	scrval = 0;
	cbrval = 0;
	for (gdcnt = 1 ; ; gdcnt++) {
		struct dirent *p;
		int nread;
		register size_t reclen;

		assert(scrval == 0);
		assert(cbrval == 0);

		nread = getdents_wrap(fd, (char *)gdp, gdsz);

		/* negative count indicates something very bad happened;
		 * try to gracefully end this dir.
		 */
		if (nread < 0) {
			mlog(MLOG_NORMAL,
			      _("WARNING: unable to read dirents (%d) for "
			      "directory ino %llu: %s\n"),
			      gdcnt,
			      statp->bs_ino,
			      strerror(errno));
			nread = 0; /* pretend we are done */
		}

		/* no more directory entries: break;
		 */
		if (nread == 0) {
			break;
		}

		/* translate and invoke cb each entry: skip "." and "..".
		 */
		for (p = gdp,
		      reclen = (size_t)p->d_reclen
		      ;
		      nread > 0
		      ;
		      nread -= (int)reclen,
		      assert(nread >= 0),
		      p = (struct dirent *)((char *)p + reclen),
		      reclen = (size_t)p->d_reclen) {
			struct xfs_bstat statbuf;
			assert(scrval == 0);
			assert(cbrval == 0);

			/* skip "." and ".."
			 */
			if (*(p->d_name + 0) == '.'
			     &&
			     (*(p->d_name + 1) == 0
			       ||
			       (*(p->d_name + 1) == '.'
				 &&
				 *(p->d_name + 2) == 0))) {
				continue;
			}

			/* use bigstat
			 */
			scrval = bigstat_one(fsfd,
					      p->d_ino,
					      &statbuf);
			if (scrval) {
				mlog(MLOG_NORMAL,
				      _("WARNING: could not stat "
				      "dirent %s ino %llu: %s\n"),
				      p->d_name,
				      (xfs_ino_t)p->d_ino,
				      strerror(errno));
				scrval = 0;
				continue;
			}

			/* if getdents64() not yet available, and ino
			 * occupied more than 32 bits, warn and skip.
			 */
#ifndef __USE_LARGEFILE64
			if (statbuf.bs_ino > (xfs_ino_t)INOMAX) {
				mlog(MLOG_NORMAL,
				      _("WARNING: unable to process dirent %s: "
				      "ino %llu too large\n"),
				      p->d_name,
				      (xfs_ino_t)p->d_ino);
				continue;
			}
#endif

			/* invoke the callback
			 */
			cbrval = (*cbfp)(arg1,
					     fshandlep,
					     fsfd,
					     &statbuf,
					     p->d_name);

			/* abort the iteration if the callback returns non-zero
			 */
			if (cbrval) {
				break;
			}
		}

		if (scrval || cbrval) {
			break;
		}
	}

	(void)close(fd);
	if (!usrgdp) {
		free((void *)gdp);
	}

	if (scrval) {
		*cbrvalp = 0;
		return -1;
	} else if (cbrval) {
		*cbrvalp = cbrval;
		return 1;
	} else {
		*cbrvalp = 0;
		return 0;
	}
}

int
cvtnum(int blocksize, char *s)
{
	int i;
	char *sp;

	i = (int)strtoll(s, &sp, 0);
	if (i == 0 && sp == s)
		return -1;
	if (*sp == '\0')
		return i;
	if (*sp == 'b' && blocksize && sp[1] == '\0')
		return i * blocksize;
	if (*sp == 'k' && sp[1] == '\0')
		return 1024 * i;
	if (*sp == 'm' && sp[1] == '\0')
		return 1024 * 1024 * i;
	return -1;
}
