/*
 * error.c -- Common error handling functions
 *
 * Copyright (C) 2007 Oracle.  All rights reserved.
 * Copyright (C) 2007 Chuck Lever <chuck.lever@oracle.com>
 *
 * 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; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will 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 to the
 * Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 0211-1301 USA
 *
 * To Do:
 *  + Proper support for internationalization
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <syslog.h>
#include <rpc/rpc.h>

#include "xcommon.h"
#include "nls.h"
#include "mount.h"
#include "error.h"

#ifdef HAVE_RPCSVC_NFS_PROT_H
#include <rpcsvc/nfs_prot.h>
#else
#include <linux/nfs.h>
#define nfsstat nfs_stat
#endif

extern char *progname;

static char errbuf[PATH_MAX];
static char *erreob = &errbuf[PATH_MAX];

/* Convert RPC errors into strings */
static int rpc_strerror(int spos)
{
	int cf_stat = rpc_createerr.cf_stat;
	int pos = 0, cf_errno = rpc_createerr.cf_error.re_errno;
	char *ptr, *estr = clnt_sperrno(cf_stat);
	char *tmp;

	if (estr) {
		if ((ptr = strchr(estr, ':')))
			estr = ++ptr;

		tmp = &errbuf[spos];
		if (cf_stat == RPC_SYSTEMERROR)
			pos = snprintf(tmp, (erreob - tmp),
					_("System Error: %s"),
						strerror(cf_errno));
		else {
			if (cf_errno) 
				pos = snprintf(tmp, (erreob - tmp),
					_("RPC Error:%s; errno = %s"), 
					estr, strerror(cf_errno));
			else
				pos = snprintf(tmp, (erreob - tmp),
						_("RPC Error:%s"), estr);
		}
	}
	return pos;
}

/**
 * rpc_mount_errors - log an RPC error that occurred during a user-space mount
 * @server: C string containing name of server we are attempting to mount
 * @will_retry: one indicates mount will retry at some later point
 * @bg: one indicates this is a background mount
 *
 * Extracts the error code from the user-space RPC library, and reports it
 * on stderr (fg mount) or in the system log (bg mount).
 */
void rpc_mount_errors(char *server, int will_retry, int bg)
{
	int pos = 0;
	char *tmp;
	static int onlyonce = 0;

	tmp = &errbuf[pos];
	if (bg)
		pos = snprintf(tmp, (erreob - tmp),
				_("mount to NFS server '%s' failed: "),
					server);
	else
		pos = snprintf(tmp, (erreob - tmp),
				_("%s: mount to NFS server '%s' failed: "),
					progname, server);

	tmp = &errbuf[pos];
	if (rpc_createerr.cf_stat == RPC_TIMEDOUT) {
		if (will_retry)
			pos = snprintf(tmp, (erreob - tmp),
					_("timed out, retrying"));
		else
			pos = snprintf(tmp, (erreob - tmp),
					_("timed out, giving up"));
	} else {
		pos += rpc_strerror(pos);
		tmp = &errbuf[pos];
		if (bg) {
			if (will_retry)
				pos = snprintf(tmp, (erreob - tmp),
						_(", retrying"));
			else
				pos = snprintf(tmp, (erreob - tmp),
						_(", giving up"));
		}
	}

	if (bg) {
		if (onlyonce++ < 1)
			openlog("mount", LOG_CONS|LOG_PID, LOG_AUTH);
		syslog(LOG_ERR, "%s", errbuf);
	} else
		fprintf(stderr, "%s\n", errbuf);
}

/**
 * sys_mount_errors - log an error that occurred during a mount system call
 * @server: C string containing name of server we are attempting to mount
 * @error: errno value to report
 * @will_retry: one indicates mount will retry at some later point
 * @bg: one indicates this is a background mount
 *
 * Passed an errno value generated by a mount system call, and reports it
 * on stderr (fg mount) or in the system log (bg mount).
 */
void sys_mount_errors(char *server, int error, int will_retry, int bg)
{
	int pos = 0;
	char *tmp;
	static int onlyonce = 0;

	tmp = &errbuf[pos];
	if (bg)
		pos = snprintf(tmp, (erreob - tmp),
				_("mount to NFS server '%s' failed: "),
					server);
	else
		pos = snprintf(tmp, (erreob - tmp),
				_("%s: mount to NFS server '%s' failed: "),
					progname, server);

	tmp = &errbuf[pos];
	if (error == ETIMEDOUT) {
		if (will_retry)
			pos = snprintf(tmp, (erreob - tmp),
					_("timed out, retrying"));
		else
			pos = snprintf(tmp, (erreob - tmp),
					_("timed out, giving up"));
	} else {
		if (bg) {
			if (will_retry)
				pos = snprintf(tmp, (erreob - tmp),
						_("%s, retrying"),
						strerror(error));
			else
				pos = snprintf(tmp, (erreob - tmp),
						_("%s, giving up"),
						strerror(error));
		}
	}

	if (bg) {
		if (onlyonce++ < 1)
			openlog("mount", LOG_CONS|LOG_PID, LOG_AUTH);
		syslog(LOG_ERR, "%s", errbuf);
	} else
		fprintf(stderr, "%s\n", errbuf);
}

/**
 * mount_error - report a foreground mount error
 * @spec: C string containing the device name being mounted
 * @mount_point: C string containing the pathname of the local mounted on dir
 * @error: errno value to report
 *
 */
void mount_error(const char *spec, const char *mount_point, int error)
{
	switch(error) {
	case EACCES:
		nfs_error(_("%s: access denied by server while mounting %s"),
				progname, spec);
		break;
	case EINVAL:
		nfs_error(_("%s: an incorrect mount option was specified for %s"),
				progname, mount_point);
		break;
	case EOPNOTSUPP:
		nfs_error(_("%s: requested NFS version or transport protocol is not supported for %s"),
				progname, mount_point);
		break;
	case ENOTDIR:
		if (spec)
			nfs_error(_("%s: mount spec %s or point %s is not a directory"),
				  progname, spec, mount_point);
		else
			nfs_error(_("%s: mount point %s is not a directory"),
				  progname, mount_point);
		break;
	case EBUSY:
		nfs_error(_("%s: %s is busy or already mounted or sharecache fail"),
			progname, mount_point);
		break;
	case ENOENT:
		if (spec)
			nfs_error(_("%s: mounting %s failed, reason given by server: %s"),
				  progname, spec, strerror(error));
		else
			nfs_error(_("%s: mount point %s does not exist"),
				  progname, mount_point);
		break;
	case ESPIPE:
		rpc_mount_errors((char *)spec, 0, 0);
		break;
	case EIO:
		nfs_error(_("%s: mount system call failed for %s"),
			  progname, mount_point);
		break;
	case EFAULT:
		nfs_error(_("%s: encountered unexpected error condition for %s."),
			  progname, mount_point);
		nfs_error(_("%s: please report the error to" PACKAGE_BUGREPORT),
			  progname);
		break;
	case EALREADY:
		/* Error message has already been provided */
		break;
	default:
		nfs_error(_("%s: %s for %s on %s"),
			  progname, strerror(error), spec, mount_point);
	}
}

/*
 * umount_error - report a failed umount request
 * @err: errno value to report
 * @dev: C string containing the pathname of the local mounted on dir
 *
 */
void umount_error(int err, const char *dev)
{
	switch (err) {
	case ENXIO:
		nfs_error(_("%s: %s: invalid block device"),
			progname, dev);
		break;
	case EINVAL:
		nfs_error(_("%s: %s: not mounted"),
			progname, dev);
		break;
	case EIO:
		nfs_error(_("%s: %s: can't write superblock"),
			progname, dev);
		break;
	case EBUSY:
		nfs_error(_("%s: %s: device is busy"),
			progname, dev);
		break;
	case ENOENT:
		nfs_error(_("%s: %s: not found"),
			progname, dev);
		break;
	case EPERM:
		nfs_error(_("%s: %s: must be superuser to umount"),
			progname, dev);
		break;
	case EACCES:
		nfs_error(_("%s: %s: block devices not permitted on fs"),
			progname, dev);
		break;
	default:
		nfs_error(_("%s: %s: %s"),
			progname, dev, strerror(err));
		break;
	}
}

/*
 * We need to translate between nfs status return values and
 * the local errno values which may not be the same.
 *
 * Andreas Schwab <schwab@LS5.informatik.uni-dortmund.de>: change errno:
 * "after #include <errno.h> the symbol errno is reserved for any use,
 *  it cannot even be used as a struct tag or field name".
 */

#ifndef EDQUOT
#define EDQUOT	ENOSPC
#endif

#define ARRAY_SIZE(x)		(sizeof(x) / sizeof((x)[0])) 

static struct {
	enum nfsstat stat;
	int errnum;
} nfs_errtbl[] = {
	{ NFS_OK,		0		},
	{ NFSERR_PERM,		EPERM		},
	{ NFSERR_NOENT,		ENOENT		},
	{ NFSERR_IO,		EIO		},
	{ NFSERR_NXIO,		ENXIO		},
	{ NFSERR_ACCES,		EACCES		},
	{ NFSERR_EXIST,		EEXIST		},
	{ NFSERR_NODEV,		ENODEV		},
	{ NFSERR_NOTDIR,	ENOTDIR		},
	{ NFSERR_ISDIR,		EISDIR		},
#ifdef NFSERR_INVAL
	{ NFSERR_INVAL,		EINVAL		},	/* that Sun forgot */
#endif
	{ NFSERR_FBIG,		EFBIG		},
	{ NFSERR_NOSPC,		ENOSPC		},
	{ NFSERR_ROFS,		EROFS		},
	{ NFSERR_NAMETOOLONG,	ENAMETOOLONG	},
	{ NFSERR_NOTEMPTY,	ENOTEMPTY	},
	{ NFSERR_DQUOT,		EDQUOT		},
	{ NFSERR_STALE,		ESTALE		},
#ifdef EWFLUSH
	{ NFSERR_WFLUSH,	EWFLUSH		},
#endif
	/* Throw in some NFSv3 values for even more fun (HP returns these) */
	{ 71,			EREMOTE		},
};

char *nfs_strerror(unsigned int stat)
{
	unsigned int i;
	static char buf[256];

	for (i = 0; i < ARRAY_SIZE(nfs_errtbl); i++) {
		if (nfs_errtbl[i].stat == stat)
			return strerror(nfs_errtbl[i].errnum);
	}
	sprintf(buf, _("unknown nfs status return value: %u"), stat);
	return buf;
}
