1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License").  You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22/*
23 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27/*
28 * This file contains code to support better NFS error messages.  Death to
29 * integer codes in user error messages!
30 *
31 * XXX Ideally this code should be more general and available to the entire
32 * kernel (see RFE 1101936).  When this happens, this file can go away.
33 */
34
35#include <nfs/nfs.h>
36#include <sys/null.h>
37#include <sys/systm.h>
38#include <sys/cmn_err.h>
39#include <sys/errno.h>
40#include <sys/varargs.h>
41
42/* size of a temporary printf format buffer. */
43#define	FMT_BUF_SIZE	1024
44
45static void expand_format_string(int, const char *, char *, int);
46static char *nfs_strerror(int);
47
48/*
49 * nfs_perror: Works like printf (format string and variable args) except
50 * that it will substitute an error message for a "%m" string (like
51 * syslog), using the given errno value.
52 */
53
54void
55nfs_perror(int error, char *fmt, ...)
56{
57	va_list ap;
58	char buf[FMT_BUF_SIZE];		/* massaged version of fmt */
59
60	/* Expand %m */
61
62	expand_format_string(error, fmt, buf, FMT_BUF_SIZE);
63
64	/*
65	 * Now pass the massaged format string and its arguments off to
66	 * printf.
67	 */
68
69	va_start(ap, fmt);
70	(void) vzprintf(getzoneid(), buf, ap);
71	va_end(ap);
72}
73
74/*
75 * nfs_cmn_err: Works like cmn_err (error level, format string, and
76 * variable args) except that it will substitute an error message for a
77 * "%m" string (like syslog), using the given errno value.
78 */
79
80void
81nfs_cmn_err(int error, int level, char *fmt, ...)
82{
83	va_list ap;
84	char buf[FMT_BUF_SIZE];		/* massaged version of fmt */
85
86	/* Expand %m */
87
88	expand_format_string(error, fmt, buf, FMT_BUF_SIZE);
89
90	/*
91	 * Now pass the massaged format string and its arguments off to
92	 * cmn_err.
93	 */
94
95	va_start(ap, fmt);
96	(void) vzcmn_err(getzoneid(), level, buf, ap);
97	va_end(ap);
98}
99
100/*
101 * expand_format_string: copy the printf format string from "fmt" to "buf",
102 * expanding %m to the error string for "error".
103 */
104
105static void
106expand_format_string(int error, const char *fmt, char *buf, int buf_chars)
107{
108	const char *from;		/* pointer into fmt */
109	char *to;			/* pointer into buf */
110	char *errmsg;			/* expansion for %m */
111	char *trunc_msg = "Truncated NFS error message: ";
112	zoneid_t zoneid = getzoneid();
113
114	/*
115	 * Copy the given format string into the result buffer, expanding
116	 * %m as we go.  If the result buffer is too short, complain and
117	 * truncate the message.  (We don't expect this to ever happen,
118	 * though.)
119	 */
120
121	for (from = fmt, to = buf; *from; from++) {
122		if (to >= buf + buf_chars - 1) {
123			zprintf(zoneid, trunc_msg);
124			break;
125		}
126		if (*from == '%' && *(from+1) == 'm') {
127			errmsg = nfs_strerror(error);
128			/*
129			 * If there's an error message and room to display
130			 * it, copy it in.  If there's no message or not
131			 * enough room, try just printing an error number.
132			 * (We assume that the error value is in a
133			 * reasonable range.)  If there's no room for
134			 * anything, bail out.
135			 */
136			if (errmsg != NULL &&
137			    strlen(buf) + strlen(errmsg) < buf_chars) {
138				(void) strcpy(to, errmsg);
139				to += strlen(errmsg);
140			} else if (strlen(buf) + strlen("error XXX") <
141			    buf_chars) {
142				(void) sprintf(to, "error %d", error);
143				/*
144				 * Don't try to guess how many characters
145				 * were laid down.
146				 */
147				to = buf + strlen(buf);
148			} else {
149				zprintf(zoneid, trunc_msg);
150				break;
151			}
152			from++;
153		} else {
154			*to++ = *from;
155		}
156	}
157	*to = '\0';
158}
159
160/*
161 * nfs_strerror: map an errno value to a string.  Not all possible errno
162 * values are supported.
163 *
164 * If there is no string for the given errno value, return NULL.
165 */
166
167static char *
168nfs_strerror(int errcode)
169{
170	char *result;
171
172	switch (errcode) {
173	case EPERM:
174		result = "Not owner";
175		break;
176	case ENOENT:
177		result = "No such file or directory";
178		break;
179	case EIO:
180		result = "I/O error";
181		break;
182	case EACCES:
183		result = "Permission denied";
184		break;
185	case EEXIST:
186		result = "File exists";
187		break;
188	case ENOTDIR:
189		result = "Not a directory";
190		break;
191	case EISDIR:
192		result = "Is a directory";
193		break;
194	case EINVAL:
195		result = "Invalid argument";
196		break;
197	case EFBIG:
198		result = "File too large";
199		break;
200	case ENOSPC:
201		result = "No space left on device";
202		break;
203	case EROFS:
204		result = "Read-only file system";
205		break;
206	case EDQUOT:
207		result = "Disc quota exceeded";
208		break;
209	case ENOTEMPTY:
210		result = "Directory not empty";
211		break;
212	case ESTALE:
213		result = "Stale NFS file handle";
214		break;
215	case ENOMEM:
216		result = "Not enough memory";
217		break;
218	default:
219		result = NULL;
220		break;
221	}
222
223	return (result);
224}
225