0
|
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 |
* Copyright 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T.
|
|
29 |
* All rights reserved.
|
|
30 |
*/
|
|
31 |
|
|
32 |
|
|
33 |
#pragma ident "%Z%%M% %I% %E% SMI"
|
|
34 |
|
|
35 |
#include <sys/types.h>
|
|
36 |
#include <sys/param.h>
|
|
37 |
#include <sys/time.h>
|
|
38 |
#include <sys/vfs.h>
|
|
39 |
#include <sys/vnode.h>
|
|
40 |
#include <sys/socket.h>
|
|
41 |
#include <sys/errno.h>
|
|
42 |
#include <sys/uio.h>
|
|
43 |
#include <sys/proc.h>
|
|
44 |
#include <sys/user.h>
|
|
45 |
#include <sys/file.h>
|
|
46 |
#include <sys/tiuser.h>
|
|
47 |
#include <sys/kmem.h>
|
|
48 |
#include <sys/pathname.h>
|
|
49 |
#include <sys/debug.h>
|
|
50 |
#include <sys/vtrace.h>
|
|
51 |
#include <sys/cmn_err.h>
|
|
52 |
#include <sys/acl.h>
|
|
53 |
#include <sys/utsname.h>
|
|
54 |
#include <netinet/in.h>
|
|
55 |
|
|
56 |
#include <rpc/types.h>
|
|
57 |
#include <rpc/auth.h>
|
|
58 |
#include <rpc/svc.h>
|
|
59 |
|
|
60 |
#include <nfs/nfs.h>
|
|
61 |
#include <nfs/export.h>
|
|
62 |
#include <nfs/nfssys.h>
|
|
63 |
#include <nfs/nfs_clnt.h>
|
|
64 |
#include <nfs/nfs_acl.h>
|
|
65 |
#include <nfs/nfs_log.h>
|
|
66 |
#include <nfs/lm.h>
|
|
67 |
|
|
68 |
#define EXPTABLESIZE 16
|
|
69 |
|
|
70 |
struct exportinfo *exptable[EXPTABLESIZE];
|
|
71 |
|
|
72 |
static int unexport(fsid_t *, fid_t *, vnode_t *);
|
|
73 |
static void exportfree(struct exportinfo *);
|
|
74 |
static int loadindex(struct exportdata *);
|
|
75 |
|
|
76 |
extern void nfsauth_cache_free(struct exportinfo *);
|
|
77 |
extern int sec_svc_loadrootnames(int, int, caddr_t **, model_t);
|
|
78 |
extern void sec_svc_freerootnames(int, int, caddr_t *);
|
|
79 |
|
|
80 |
#ifdef VOLATILE_FH_TEST
|
|
81 |
static struct ex_vol_rename *find_volrnm_fh(struct exportinfo *, nfs_fh4 *);
|
|
82 |
static uint32_t find_volrnm_fh_id(struct exportinfo *, nfs_fh4 *);
|
|
83 |
static void free_volrnm_list(struct exportinfo *);
|
|
84 |
#endif /* VOLATILE_FH_TEST */
|
|
85 |
|
|
86 |
/*
|
|
87 |
* exported_lock Read/Write lock that protects the exportinfo list.
|
|
88 |
* This lock must be held when searching or modifiying
|
|
89 |
* the exportinfo list.
|
|
90 |
*/
|
|
91 |
krwlock_t exported_lock;
|
|
92 |
|
|
93 |
/*
|
|
94 |
* "public" and default (root) location for public filehandle
|
|
95 |
*/
|
|
96 |
struct exportinfo *exi_public, *exi_root;
|
|
97 |
|
|
98 |
fid_t exi_rootfid; /* for checking the default public file handle */
|
|
99 |
|
|
100 |
fhandle_t nullfh2; /* for comparing V2 filehandles */
|
|
101 |
|
|
102 |
#define exptablehash(fsid, fid) (nfs_fhhash((fsid), (fid)) & (EXPTABLESIZE - 1))
|
|
103 |
|
|
104 |
/*
|
|
105 |
* File handle hash function, good for producing hash values 16 bits wide.
|
|
106 |
*/
|
|
107 |
int
|
|
108 |
nfs_fhhash(fsid_t *fsid, fid_t *fid)
|
|
109 |
{
|
|
110 |
short *data;
|
|
111 |
int i, len;
|
|
112 |
short h;
|
|
113 |
|
|
114 |
ASSERT(fid != NULL);
|
|
115 |
|
|
116 |
data = (short *)fid->fid_data;
|
|
117 |
|
|
118 |
/* fid_data must be aligned on a short */
|
|
119 |
ASSERT((((uintptr_t)data) & (sizeof (short) - 1)) == 0);
|
|
120 |
|
|
121 |
if (fid->fid_len == 10) {
|
|
122 |
/*
|
|
123 |
* probably ufs: hash on bytes 4,5 and 8,9
|
|
124 |
*/
|
|
125 |
return (fsid->val[0] ^ data[2] ^ data[4]);
|
|
126 |
}
|
|
127 |
|
|
128 |
if (fid->fid_len == 6) {
|
|
129 |
/*
|
|
130 |
* probably hsfs: hash on bytes 0,1 and 4,5
|
|
131 |
*/
|
|
132 |
return ((fsid->val[0] ^ data[0] ^ data[2]));
|
|
133 |
}
|
|
134 |
|
|
135 |
/*
|
|
136 |
* Some other file system. Assume that every byte is
|
|
137 |
* worth hashing.
|
|
138 |
*/
|
|
139 |
h = (short)fsid->val[0];
|
|
140 |
|
|
141 |
/*
|
|
142 |
* Sanity check the length before using it
|
|
143 |
* blindly in case the client trashed it.
|
|
144 |
*/
|
|
145 |
if (fid->fid_len > NFS_FHMAXDATA)
|
|
146 |
len = 0;
|
|
147 |
else
|
|
148 |
len = fid->fid_len / sizeof (short);
|
|
149 |
|
|
150 |
/*
|
|
151 |
* This will ignore one byte if len is not a multiple of
|
|
152 |
* of sizeof (short). No big deal since we at least get some
|
|
153 |
* variation with fsid->val[0];
|
|
154 |
*/
|
|
155 |
for (i = 0; i < len; i++)
|
|
156 |
h ^= data[i];
|
|
157 |
|
|
158 |
return ((int)h);
|
|
159 |
}
|
|
160 |
|
|
161 |
/*
|
|
162 |
* Free the memory allocated within a secinfo entry.
|
|
163 |
*/
|
|
164 |
void
|
|
165 |
srv_secinfo_entry_free(struct secinfo *secp)
|
|
166 |
{
|
|
167 |
if (secp->s_rootcnt > 0 && secp->s_rootnames != NULL) {
|
|
168 |
sec_svc_freerootnames(secp->s_secinfo.sc_rpcnum,
|
|
169 |
secp->s_rootcnt, secp->s_rootnames);
|
|
170 |
secp->s_rootcnt = 0;
|
|
171 |
}
|
|
172 |
|
|
173 |
if ((secp->s_secinfo.sc_rpcnum == RPCSEC_GSS) &&
|
|
174 |
(secp->s_secinfo.sc_gss_mech_type)) {
|
|
175 |
kmem_free(secp->s_secinfo.sc_gss_mech_type->elements,
|
|
176 |
secp->s_secinfo.sc_gss_mech_type->length);
|
|
177 |
kmem_free(secp->s_secinfo.sc_gss_mech_type,
|
|
178 |
sizeof (rpc_gss_OID_desc));
|
|
179 |
secp->s_secinfo.sc_gss_mech_type = NULL;
|
|
180 |
}
|
|
181 |
|
|
182 |
}
|
|
183 |
|
|
184 |
/*
|
|
185 |
* Free a list of secinfo allocated in the exportdata structure.
|
|
186 |
*/
|
|
187 |
void
|
|
188 |
srv_secinfo_list_free(struct secinfo *secinfo, int cnt)
|
|
189 |
{
|
|
190 |
int i;
|
|
191 |
|
|
192 |
if (cnt == 0)
|
|
193 |
return;
|
|
194 |
|
|
195 |
for (i = 0; i < cnt; i++)
|
|
196 |
srv_secinfo_entry_free(&secinfo[i]);
|
|
197 |
|
|
198 |
kmem_free(secinfo, cnt * sizeof (struct secinfo));
|
|
199 |
}
|
|
200 |
|
|
201 |
/*
|
|
202 |
* Allocate and copy a secinfo data from "from" to "to".
|
|
203 |
*
|
|
204 |
* This routine is used by srv_secinfo_add() to add a new flavor to an
|
|
205 |
* ancestor's export node. The rootnames are not copied because the
|
|
206 |
* allowable rootname access only applies to the explicit exported node,
|
|
207 |
* not its ancestor's.
|
|
208 |
*
|
|
209 |
* "to" should have already been allocated and zeroed before calling
|
|
210 |
* this routine.
|
|
211 |
*
|
|
212 |
* This routine is used under the protection of exported_lock (RW_WRITER).
|
|
213 |
*/
|
|
214 |
void
|
|
215 |
srv_secinfo_copy(struct secinfo *from, struct secinfo *to)
|
|
216 |
{
|
|
217 |
to->s_secinfo.sc_nfsnum = from->s_secinfo.sc_nfsnum;
|
|
218 |
to->s_secinfo.sc_rpcnum = from->s_secinfo.sc_rpcnum;
|
|
219 |
|
|
220 |
if (from->s_secinfo.sc_rpcnum == RPCSEC_GSS) {
|
|
221 |
to->s_secinfo.sc_service = from->s_secinfo.sc_service;
|
|
222 |
bcopy(from->s_secinfo.sc_name, to->s_secinfo.sc_name,
|
|
223 |
strlen(from->s_secinfo.sc_name));
|
|
224 |
bcopy(from->s_secinfo.sc_gss_mech, to->s_secinfo.sc_gss_mech,
|
|
225 |
strlen(from->s_secinfo.sc_gss_mech));
|
|
226 |
|
|
227 |
/* copy mechanism oid */
|
|
228 |
to->s_secinfo.sc_gss_mech_type =
|
|
229 |
kmem_alloc(sizeof (rpc_gss_OID_desc), KM_SLEEP);
|
|
230 |
to->s_secinfo.sc_gss_mech_type->length =
|
|
231 |
from->s_secinfo.sc_gss_mech_type->length;
|
|
232 |
to->s_secinfo.sc_gss_mech_type->elements =
|
|
233 |
kmem_alloc(from->s_secinfo.sc_gss_mech_type->length,
|
|
234 |
KM_SLEEP);
|
|
235 |
bcopy(from->s_secinfo.sc_gss_mech_type->elements,
|
|
236 |
to->s_secinfo.sc_gss_mech_type->elements,
|
|
237 |
from->s_secinfo.sc_gss_mech_type->length);
|
|
238 |
}
|
|
239 |
|
|
240 |
to->s_refcnt = from->s_refcnt;
|
|
241 |
to->s_window = from->s_window;
|
|
242 |
/* no need to copy the mode bits - s_flags */
|
|
243 |
}
|
|
244 |
|
|
245 |
/*
|
|
246 |
* Add the new security flavors from newdata to the current list, curdata.
|
|
247 |
* Upon return, curdata has the newly merged secinfo list.
|
|
248 |
*
|
|
249 |
* There should be at least 1 secinfo entry in newdata.
|
|
250 |
*
|
|
251 |
* This routine is used under the protection of exported_lock (RW_WRITER).
|
|
252 |
*/
|
|
253 |
void
|
|
254 |
srv_secinfo_add(struct exportdata *curdata, struct exportdata *newdata)
|
|
255 |
{
|
|
256 |
int ccnt, c; /* sec count in current data - curdata */
|
|
257 |
int ncnt, n; /* sec count in new data - newdata */
|
|
258 |
int tcnt, mcnt; /* total sec count after merge */
|
|
259 |
struct secinfo *msec; /* merged secinfo list */
|
|
260 |
|
|
261 |
ccnt = curdata->ex_seccnt;
|
|
262 |
ncnt = newdata->ex_seccnt;
|
|
263 |
|
|
264 |
ASSERT(ncnt > 0);
|
|
265 |
tcnt = ccnt + ncnt;
|
|
266 |
|
|
267 |
for (n = 0; n < ncnt; n++) {
|
|
268 |
for (c = 0; c < ccnt; c++) {
|
|
269 |
if (newdata->ex_secinfo[n].s_secinfo.sc_nfsnum ==
|
|
270 |
curdata->ex_secinfo[c].s_secinfo.sc_nfsnum) {
|
|
271 |
|
|
272 |
/*
|
|
273 |
* add the reference count of the newdata
|
|
274 |
* to the curdata for this nfs flavor.
|
|
275 |
*/
|
|
276 |
curdata->ex_secinfo[c].s_refcnt +=
|
|
277 |
newdata->ex_secinfo[n].s_refcnt;
|
|
278 |
|
|
279 |
tcnt--;
|
|
280 |
break;
|
|
281 |
}
|
|
282 |
}
|
|
283 |
}
|
|
284 |
|
|
285 |
if (tcnt == ccnt)
|
|
286 |
return; /* no change; no new flavors */
|
|
287 |
|
|
288 |
msec = kmem_zalloc(tcnt * sizeof (struct secinfo), KM_SLEEP);
|
|
289 |
|
|
290 |
/* move current secinfo list data to the new list */
|
|
291 |
for (c = 0; c < ccnt; c++) {
|
|
292 |
|
|
293 |
bcopy(&curdata->ex_secinfo[c], &msec[c],
|
|
294 |
sizeof (struct secinfo));
|
|
295 |
}
|
|
296 |
|
|
297 |
/* Add the flavor that's not in the current data */
|
|
298 |
mcnt = ccnt;
|
|
299 |
for (n = 0; n < ncnt; n++) {
|
|
300 |
for (c = 0; c < ccnt; c++) {
|
|
301 |
if (newdata->ex_secinfo[n].s_secinfo.sc_nfsnum ==
|
|
302 |
curdata->ex_secinfo[c].s_secinfo.sc_nfsnum)
|
|
303 |
break;
|
|
304 |
}
|
|
305 |
|
|
306 |
/* This is the one. Add it. */
|
|
307 |
if (c == ccnt) {
|
|
308 |
srv_secinfo_copy(&newdata->ex_secinfo[n], &msec[mcnt]);
|
|
309 |
if (curdata->ex_flags & EX_PSEUDO)
|
|
310 |
msec[mcnt].s_flags = M_RO;
|
|
311 |
mcnt++;
|
|
312 |
}
|
|
313 |
}
|
|
314 |
|
|
315 |
ASSERT(mcnt == tcnt);
|
|
316 |
/*
|
|
317 |
* Done. Update curdata.
|
|
318 |
* Free up the existing secinfo list in curdata and
|
|
319 |
* set the new value.
|
|
320 |
*/
|
|
321 |
if (ccnt > 0)
|
|
322 |
kmem_free(curdata->ex_secinfo, ccnt * sizeof (struct secinfo));
|
|
323 |
curdata->ex_seccnt = tcnt;
|
|
324 |
curdata->ex_secinfo = msec;
|
|
325 |
}
|
|
326 |
|
|
327 |
/*
|
|
328 |
* For NFS V4.
|
|
329 |
* Remove the security data of the unexported node from its ancestors.
|
|
330 |
* Assume there is at least one flavor entry in the current data, curdata.
|
|
331 |
*
|
|
332 |
* This routine is used under the protection of exported_lock (RW_WRITER).
|
|
333 |
*/
|
|
334 |
void
|
|
335 |
srv_secinfo_remove(struct exportdata *curdata, struct exportdata *remdata)
|
|
336 |
{
|
|
337 |
int ccnt, c; /* sec count in current data - curdata */
|
|
338 |
int rcnt, r; /* sec count in removal data - remdata */
|
|
339 |
int tcnt, mcnt; /* total sec count after removing */
|
|
340 |
struct secinfo *msec; /* final secinfo list after removing */
|
|
341 |
|
|
342 |
ASSERT(curdata->ex_seccnt > 0);
|
|
343 |
ccnt = curdata->ex_seccnt;
|
|
344 |
rcnt = remdata->ex_seccnt;
|
|
345 |
tcnt = ccnt;
|
|
346 |
|
|
347 |
for (r = 0; r < rcnt; r++) {
|
|
348 |
|
|
349 |
if (SEC_REF_EXPORTED(&remdata->ex_secinfo[r])) {
|
|
350 |
/*
|
|
351 |
* Remove a flavor only if the flavor was a shared flavor for
|
|
352 |
* the remdata exported node that's being unshared. Otherwise,
|
|
353 |
* this flavor is for the children of remdata, need to keep it.
|
|
354 |
*/
|
|
355 |
for (c = 0; c < ccnt; c++) {
|
|
356 |
if (remdata->ex_secinfo[r].s_secinfo.sc_nfsnum ==
|
|
357 |
curdata->ex_secinfo[c].s_secinfo.sc_nfsnum) {
|
|
358 |
|
|
359 |
/*
|
|
360 |
* Decrement secinfo reference count by 1.
|
|
361 |
* If this entry is invalid after decrementing
|
|
362 |
* the count (i.e. count < 1), this entry will
|
|
363 |
* be removed.
|
|
364 |
*/
|
|
365 |
curdata->ex_secinfo[c].s_refcnt--;
|
|
366 |
if (SEC_REF_INVALID(&curdata->ex_secinfo[c]))
|
|
367 |
tcnt--;
|
|
368 |
|
|
369 |
break;
|
|
370 |
}
|
|
371 |
}
|
|
372 |
}
|
|
373 |
}
|
|
374 |
|
|
375 |
ASSERT(tcnt >= 0);
|
|
376 |
if (tcnt == ccnt)
|
|
377 |
return; /* no change; no flavors to remove */
|
|
378 |
|
|
379 |
if (tcnt == 0) {
|
|
380 |
srv_secinfo_list_free(curdata->ex_secinfo, ccnt);
|
|
381 |
curdata->ex_seccnt = 0;
|
|
382 |
curdata->ex_secinfo = NULL;
|
|
383 |
return;
|
|
384 |
}
|
|
385 |
|
|
386 |
msec = kmem_zalloc(tcnt * sizeof (struct secinfo), KM_SLEEP);
|
|
387 |
|
|
388 |
/* walk thru the given secinfo list to remove the flavors */
|
|
389 |
mcnt = 0;
|
|
390 |
for (c = 0; c < ccnt; c++) {
|
|
391 |
|
|
392 |
if (SEC_REF_INVALID(&curdata->ex_secinfo[c])) {
|
|
393 |
srv_secinfo_entry_free(&curdata->ex_secinfo[c]);
|
|
394 |
} else {
|
|
395 |
bcopy(&curdata->ex_secinfo[c], &msec[mcnt],
|
|
396 |
sizeof (struct secinfo));
|
|
397 |
mcnt++;
|
|
398 |
}
|
|
399 |
}
|
|
400 |
|
|
401 |
ASSERT(mcnt == tcnt);
|
|
402 |
/*
|
|
403 |
* Done. Update curdata.
|
|
404 |
* Free the existing secinfo list in curdata. All pointers
|
|
405 |
* within the list have either been moved to msec or freed
|
|
406 |
* if it's invalid.
|
|
407 |
*/
|
|
408 |
kmem_free(curdata->ex_secinfo, ccnt * sizeof (struct secinfo));
|
|
409 |
curdata->ex_seccnt = tcnt;
|
|
410 |
curdata->ex_secinfo = msec;
|
|
411 |
}
|
|
412 |
|
|
413 |
/*
|
|
414 |
* Upon re-sharing an export node, if there is more than 1 export reference
|
|
415 |
* to an old flavor (i.e. some of its children shared with this flavor), this
|
|
416 |
* flavor information needs to be transfered to the new shared node.
|
|
417 |
*
|
|
418 |
* Expect at least 1 secinfo entry in the old shared node - olddata.
|
|
419 |
* Expect both curdata and olddata are not pseudo nodes.
|
|
420 |
*
|
|
421 |
* This routine is used under the protection of exported_lock (RW_WRITER).
|
|
422 |
*/
|
|
423 |
void
|
|
424 |
srv_secinfo_exp2exp(struct exportdata *curdata, struct exportdata *olddata)
|
|
425 |
{
|
|
426 |
int ccnt, c; /* sec count in current data - curdata */
|
|
427 |
int ocnt, o; /* sec count in old data - olddata */
|
|
428 |
int tcnt, mcnt; /* total sec count after the transfer */
|
|
429 |
struct secinfo *msec; /* merged secinfo list */
|
|
430 |
|
|
431 |
ccnt = curdata->ex_seccnt;
|
|
432 |
ocnt = olddata->ex_seccnt;
|
|
433 |
|
|
434 |
ASSERT(ocnt > 0);
|
|
435 |
ASSERT(!(olddata->ex_flags & EX_PSEUDO));
|
|
436 |
ASSERT(!(curdata->ex_flags & EX_PSEUDO));
|
|
437 |
|
|
438 |
/*
|
|
439 |
* If the olddata has flavors with more than 1 reference count,
|
|
440 |
* transfer the information to the curdata.
|
|
441 |
*/
|
|
442 |
tcnt = ccnt + ocnt;
|
|
443 |
|
|
444 |
for (o = 0; o < ocnt; o++) {
|
|
445 |
|
|
446 |
if (SEC_REF_SELF(&olddata->ex_secinfo[o])) {
|
|
447 |
tcnt--;
|
|
448 |
} else {
|
|
449 |
for (c = 0; c < ccnt; c++) {
|
|
450 |
if (olddata->ex_secinfo[o].s_secinfo.sc_nfsnum ==
|
|
451 |
curdata->ex_secinfo[c].s_secinfo.sc_nfsnum) {
|
|
452 |
|
|
453 |
/* add old reference to the current secinfo count */
|
|
454 |
curdata->ex_secinfo[c].s_refcnt +=
|
|
455 |
olddata->ex_secinfo[o].s_refcnt;
|
|
456 |
|
|
457 |
/* delete the old export flavor reference */
|
|
458 |
if (SEC_REF_EXPORTED(&olddata->ex_secinfo[o]))
|
|
459 |
curdata->ex_secinfo[c].s_refcnt--;
|
|
460 |
tcnt--;
|
|
461 |
break;
|
|
462 |
}
|
|
463 |
}
|
|
464 |
}
|
|
465 |
}
|
|
466 |
|
|
467 |
if (tcnt == ccnt)
|
|
468 |
return; /* no more transfer to do */
|
|
469 |
|
|
470 |
/*
|
|
471 |
* olddata has flavors refered by its children that are not
|
|
472 |
* in the current (new) export flavor list. Add these flavors.
|
|
473 |
*/
|
|
474 |
msec = kmem_zalloc(tcnt * sizeof (struct secinfo), KM_SLEEP);
|
|
475 |
|
|
476 |
/* move current secinfo list data to the new list */
|
|
477 |
for (c = 0; c < ccnt; c++) {
|
|
478 |
bcopy(&curdata->ex_secinfo[c], &msec[c],
|
|
479 |
sizeof (struct secinfo));
|
|
480 |
}
|
|
481 |
|
|
482 |
/*
|
|
483 |
* Add the flavor that's not in the new export, but still
|
|
484 |
* referred by its children.
|
|
485 |
*/
|
|
486 |
mcnt = ccnt;
|
|
487 |
for (o = 0; o < ocnt; o++) {
|
|
488 |
if (! SEC_REF_SELF(&olddata->ex_secinfo[o])) {
|
|
489 |
for (c = 0; c < ccnt; c++) {
|
|
490 |
if (olddata->ex_secinfo[o].s_secinfo.sc_nfsnum ==
|
|
491 |
curdata->ex_secinfo[c].s_secinfo.sc_nfsnum)
|
|
492 |
break;
|
|
493 |
}
|
|
494 |
|
|
495 |
/*
|
|
496 |
* This is the one. Add it. Decrement the reference count
|
|
497 |
* by 1 if the flavor is an explicitly shared flavor for
|
|
498 |
* the olddata export node.
|
|
499 |
*/
|
|
500 |
if (c == ccnt) {
|
|
501 |
srv_secinfo_copy(&olddata->ex_secinfo[o], &msec[mcnt]);
|
|
502 |
if (SEC_REF_EXPORTED(&olddata->ex_secinfo[o]))
|
|
503 |
msec[mcnt].s_refcnt--;
|
|
504 |
mcnt++;
|
|
505 |
}
|
|
506 |
}
|
|
507 |
}
|
|
508 |
|
|
509 |
ASSERT(mcnt == tcnt);
|
|
510 |
/*
|
|
511 |
* Done. Update curdata.
|
|
512 |
* Free up the existing secinfo list in curdata and
|
|
513 |
* set the new value.
|
|
514 |
*/
|
|
515 |
if (ccnt > 0)
|
|
516 |
kmem_free(curdata->ex_secinfo, ccnt * sizeof (struct secinfo));
|
|
517 |
curdata->ex_seccnt = tcnt;
|
|
518 |
curdata->ex_secinfo = msec;
|
|
519 |
}
|
|
520 |
|
|
521 |
/*
|
|
522 |
* When unsharing an old export node and the old node becomes a pseudo node,
|
|
523 |
* if there is more than 1 export reference to an old flavor (i.e. some of
|
|
524 |
* its children shared with this flavor), this flavor information needs to
|
|
525 |
* be transfered to the new shared node.
|
|
526 |
*
|
|
527 |
* This routine is used under the protection of exported_lock (RW_WRITER).
|
|
528 |
*/
|
|
529 |
void
|
|
530 |
srv_secinfo_exp2pseu(struct exportdata *curdata, struct exportdata *olddata)
|
|
531 |
{
|
|
532 |
int ocnt, o; /* sec count in transfer data - trandata */
|
|
533 |
int tcnt, mcnt; /* total sec count after transfer */
|
|
534 |
struct secinfo *msec; /* merged secinfo list */
|
|
535 |
|
|
536 |
ASSERT(curdata->ex_flags & EX_PSEUDO);
|
|
537 |
ASSERT(curdata->ex_seccnt == 0);
|
|
538 |
|
|
539 |
ocnt = olddata->ex_seccnt;
|
|
540 |
|
|
541 |
/*
|
|
542 |
* If the olddata has flavors with more than 1 reference count,
|
|
543 |
* transfer the information to the curdata.
|
|
544 |
*/
|
|
545 |
tcnt = ocnt;
|
|
546 |
|
|
547 |
for (o = 0; o < ocnt; o++) {
|
|
548 |
if (SEC_REF_SELF(&olddata->ex_secinfo[o]))
|
|
549 |
tcnt--;
|
|
550 |
}
|
|
551 |
|
|
552 |
if (tcnt == 0)
|
|
553 |
return; /* no transfer to do */
|
|
554 |
|
|
555 |
msec = kmem_zalloc(tcnt * sizeof (struct secinfo), KM_SLEEP);
|
|
556 |
|
|
557 |
mcnt = 0;
|
|
558 |
for (o = 0; o < ocnt; o++) {
|
|
559 |
if (! SEC_REF_SELF(&olddata->ex_secinfo[o])) {
|
|
560 |
|
|
561 |
/*
|
|
562 |
* Decrement the reference count by 1 if the flavor is
|
|
563 |
* an explicitly shared flavor for the olddata export node.
|
|
564 |
*/
|
|
565 |
srv_secinfo_copy(&olddata->ex_secinfo[o], &msec[mcnt]);
|
|
566 |
msec[mcnt].s_flags = M_RO; /* for a pseudo node */
|
|
567 |
if (SEC_REF_EXPORTED(&olddata->ex_secinfo[o]))
|
|
568 |
msec[mcnt].s_refcnt--;
|
|
569 |
mcnt++;
|
|
570 |
}
|
|
571 |
}
|
|
572 |
|
|
573 |
ASSERT(mcnt == tcnt);
|
|
574 |
/*
|
|
575 |
* Done. Update curdata.
|
|
576 |
* Free up the existing secinfo list in curdata and
|
|
577 |
* set the new value.
|
|
578 |
*/
|
|
579 |
curdata->ex_seccnt = tcnt;
|
|
580 |
curdata->ex_secinfo = msec;
|
|
581 |
}
|
|
582 |
|
|
583 |
/*
|
|
584 |
* For NFS V4.
|
|
585 |
* Add or remove the newly exported or unexported security flavors of the
|
|
586 |
* given exportinfo from its ancestors upto the system root.
|
|
587 |
*/
|
|
588 |
int
|
|
589 |
srv_secinfo_treeclimb(struct exportinfo *exip, bool_t isadd)
|
|
590 |
{
|
|
591 |
vnode_t *dvp, *vp;
|
|
592 |
fid_t fid;
|
|
593 |
int error = 0;
|
|
594 |
int exportdir;
|
|
595 |
struct exportinfo *exi;
|
|
596 |
struct exportdata *exdata;
|
|
597 |
|
|
598 |
ASSERT(RW_WRITE_HELD(&exported_lock));
|
|
599 |
|
|
600 |
exdata = &exip->exi_export;
|
|
601 |
if (exdata->ex_seccnt == 0)
|
|
602 |
return (0);
|
|
603 |
|
|
604 |
vp = exip->exi_vp;
|
|
605 |
VN_HOLD(vp);
|
|
606 |
exportdir = 1;
|
|
607 |
|
|
608 |
for (;;) {
|
|
609 |
|
|
610 |
bzero(&fid, sizeof (fid));
|
|
611 |
fid.fid_len = MAXFIDSZ;
|
|
612 |
error = vop_fid_pseudo(vp, &fid);
|
|
613 |
if (error)
|
|
614 |
break;
|
|
615 |
|
|
616 |
if (! exportdir) {
|
|
617 |
|
|
618 |
exi = checkexport4(&vp->v_vfsp->vfs_fsid, &fid, vp);
|
|
619 |
|
|
620 |
if (exi != NULL) {
|
|
621 |
|
|
622 |
if (isadd) {
|
|
623 |
/*
|
|
624 |
* Add the new security flavors to the
|
|
625 |
* export entry of the current directory.
|
|
626 |
*/
|
|
627 |
srv_secinfo_add(&exi->exi_export, exdata);
|
|
628 |
} else {
|
|
629 |
/*
|
|
630 |
* Remove the unexported secinfo entries.
|
|
631 |
*/
|
|
632 |
srv_secinfo_remove(&exi->exi_export, exdata);
|
|
633 |
}
|
|
634 |
}
|
|
635 |
}
|
|
636 |
|
|
637 |
/*
|
|
638 |
* If at the root of the filesystem, need
|
|
639 |
* to traverse across the mountpoint
|
|
640 |
* and continue the climb on the mounted-on
|
|
641 |
* filesystem.
|
|
642 |
*/
|
|
643 |
if (vp->v_flag & VROOT) {
|
|
644 |
|
|
645 |
if (VN_CMP(vp, rootdir)) {
|
|
646 |
/* at system root */
|
|
647 |
break;
|
|
648 |
}
|
|
649 |
|
|
650 |
vp = untraverse(vp);
|
|
651 |
exportdir = 0;
|
|
652 |
continue;
|
|
653 |
}
|
|
654 |
|
|
655 |
/*
|
|
656 |
* Now, do a ".." to find parent dir of vp.
|
|
657 |
*/
|
|
658 |
error = VOP_LOOKUP(vp, "..", &dvp, NULL, 0, NULL, CRED());
|
|
659 |
|
|
660 |
if (error == ENOTDIR && exportdir) {
|
|
661 |
dvp = exip->exi_dvp;
|
|
662 |
ASSERT(dvp != NULL);
|
|
663 |
VN_HOLD(dvp);
|
|
664 |
error = 0;
|
|
665 |
}
|
|
666 |
|
|
667 |
if (error)
|
|
668 |
break;
|
|
669 |
|
|
670 |
exportdir = 0;
|
|
671 |
VN_RELE(vp);
|
|
672 |
vp = dvp;
|
|
673 |
}
|
|
674 |
|
|
675 |
VN_RELE(vp);
|
|
676 |
return (error);
|
|
677 |
}
|
|
678 |
|
|
679 |
void
|
|
680 |
export_link(struct exportinfo *exi) {
|
|
681 |
int exporthash;
|
|
682 |
|
|
683 |
exporthash = exptablehash(&exi->exi_fsid, &exi->exi_fid);
|
|
684 |
exi->exi_hash = exptable[exporthash];
|
|
685 |
exptable[exporthash] = exi;
|
|
686 |
}
|
|
687 |
|
|
688 |
/*
|
|
689 |
* Initialization routine for export routines. Should only be called once.
|
|
690 |
*/
|
|
691 |
int
|
|
692 |
nfs_exportinit(void)
|
|
693 |
{
|
|
694 |
int error;
|
|
695 |
|
|
696 |
rw_init(&exported_lock, NULL, RW_DEFAULT, NULL);
|
|
697 |
|
|
698 |
/*
|
|
699 |
* Allocate the place holder for the public file handle, which
|
|
700 |
* is all zeroes. It is initially set to the root filesystem.
|
|
701 |
*/
|
|
702 |
exi_root = kmem_zalloc(sizeof (*exi_root), KM_SLEEP);
|
|
703 |
exi_public = exi_root;
|
|
704 |
|
|
705 |
exi_root->exi_export.ex_flags = EX_PUBLIC;
|
|
706 |
exi_root->exi_export.ex_pathlen = 2; /* length of "/" */
|
|
707 |
exi_root->exi_export.ex_path =
|
|
708 |
kmem_alloc(exi_root->exi_export.ex_pathlen, KM_SLEEP);
|
|
709 |
exi_root->exi_export.ex_path[0] = '/';
|
|
710 |
exi_root->exi_export.ex_path[1] = '\0';
|
|
711 |
|
|
712 |
exi_root->exi_count = 1;
|
|
713 |
mutex_init(&exi_root->exi_lock, NULL, MUTEX_DEFAULT, NULL);
|
|
714 |
|
|
715 |
exi_root->exi_vp = rootdir;
|
|
716 |
exi_rootfid.fid_len = MAXFIDSZ;
|
|
717 |
error = vop_fid_pseudo(exi_root->exi_vp, &exi_rootfid);
|
|
718 |
if (error) {
|
|
719 |
mutex_destroy(&exi_root->exi_lock);
|
|
720 |
kmem_free(exi_root, sizeof (*exi_root));
|
|
721 |
return (error);
|
|
722 |
}
|
|
723 |
|
|
724 |
/* setup the fhandle template */
|
|
725 |
exi_root->exi_fh.fh_fsid = rootdir->v_vfsp->vfs_fsid;
|
|
726 |
exi_root->exi_fh.fh_xlen = exi_rootfid.fid_len;
|
|
727 |
bcopy(exi_rootfid.fid_data, exi_root->exi_fh.fh_xdata,
|
|
728 |
exi_rootfid.fid_len);
|
|
729 |
exi_root->exi_fh.fh_len = sizeof (exi_root->exi_fh.fh_data);
|
|
730 |
|
|
731 |
/*
|
|
732 |
* Publish the exportinfo in the hash table
|
|
733 |
*/
|
|
734 |
export_link(exi_root);
|
|
735 |
|
|
736 |
nfslog_init();
|
|
737 |
|
|
738 |
return (0);
|
|
739 |
}
|
|
740 |
|
|
741 |
/*
|
|
742 |
* Finalization routine for export routines. Called to cleanup previoulsy
|
|
743 |
* initializtion work when the NFS server module could not be loaded correctly.
|
|
744 |
*/
|
|
745 |
void
|
|
746 |
nfs_exportfini(void)
|
|
747 |
{
|
|
748 |
/*
|
|
749 |
* Deallocate the place holder for the public file handle.
|
|
750 |
*/
|
|
751 |
srv_secinfo_list_free(exi_root->exi_export.ex_secinfo,
|
|
752 |
exi_root->exi_export.ex_seccnt);
|
|
753 |
mutex_destroy(&exi_root->exi_lock);
|
|
754 |
kmem_free(exi_root, sizeof (*exi_root));
|
|
755 |
|
|
756 |
rw_destroy(&exported_lock);
|
|
757 |
}
|
|
758 |
|
|
759 |
/*
|
|
760 |
* Check if 2 gss mechanism identifiers are the same.
|
|
761 |
*
|
|
762 |
* return FALSE if not the same.
|
|
763 |
* return TRUE if the same.
|
|
764 |
*/
|
|
765 |
static bool_t
|
|
766 |
nfs_mech_equal(rpc_gss_OID mech1, rpc_gss_OID mech2)
|
|
767 |
{
|
|
768 |
if ((mech1->length == 0) && (mech2->length == 0))
|
|
769 |
return (TRUE);
|
|
770 |
|
|
771 |
if (mech1->length != mech2->length)
|
|
772 |
return (FALSE);
|
|
773 |
|
|
774 |
return (bcmp(mech1->elements, mech2->elements, mech1->length) == 0);
|
|
775 |
}
|
|
776 |
|
|
777 |
/*
|
|
778 |
* This routine is used by rpc to map rpc security number
|
|
779 |
* to nfs specific security flavor number.
|
|
780 |
*
|
|
781 |
* The gss callback prototype is
|
|
782 |
* callback(struct svc_req *, gss_cred_id_t *, gss_ctx_id_t *,
|
|
783 |
* rpc_gss_lock_t *, void **),
|
|
784 |
* since nfs does not use the gss_cred_id_t/gss_ctx_id_t arguments
|
|
785 |
* we cast them to void.
|
|
786 |
*/
|
|
787 |
/*ARGSUSED*/
|
|
788 |
bool_t
|
|
789 |
rfs_gsscallback(struct svc_req *req, gss_cred_id_t deleg, void *gss_context,
|
|
790 |
rpc_gss_lock_t *lock, void **cookie)
|
|
791 |
{
|
|
792 |
int i, j;
|
|
793 |
rpc_gss_rawcred_t *raw_cred;
|
|
794 |
struct exportinfo *exi;
|
|
795 |
|
|
796 |
/*
|
|
797 |
* We don't deal with delegated credentials.
|
|
798 |
*/
|
|
799 |
if (deleg != GSS_C_NO_CREDENTIAL)
|
|
800 |
return (FALSE);
|
|
801 |
|
|
802 |
raw_cred = lock->raw_cred;
|
|
803 |
*cookie = NULL;
|
|
804 |
|
|
805 |
rw_enter(&exported_lock, RW_READER);
|
|
806 |
for (i = 0; i < EXPTABLESIZE; i++) {
|
|
807 |
exi = exptable[i];
|
|
808 |
while (exi) {
|
|
809 |
if (exi->exi_export.ex_seccnt > 0) {
|
|
810 |
struct secinfo *secp;
|
|
811 |
|
|
812 |
secp = exi->exi_export.ex_secinfo;
|
|
813 |
for (j = 0; j < exi->exi_export.ex_seccnt; j++) {
|
|
814 |
/*
|
|
815 |
* If there is a map of the triplet
|
|
816 |
* (mechanism, service, qop) between raw_cred and
|
|
817 |
* the exported flavor, get the psudo flavor number.
|
|
818 |
* Also qop should not be NULL, it should be "default"
|
|
819 |
* or something else.
|
|
820 |
*/
|
|
821 |
if ((secp[j].s_secinfo.sc_rpcnum == RPCSEC_GSS) &&
|
|
822 |
(nfs_mech_equal(secp[j].s_secinfo.sc_gss_mech_type,
|
|
823 |
raw_cred->mechanism)) &&
|
|
824 |
(secp[j].s_secinfo.sc_service == raw_cred->service) &&
|
|
825 |
(raw_cred->qop == secp[j].s_secinfo.sc_qop)) {
|
|
826 |
*cookie = (void *)(uintptr_t)
|
|
827 |
secp[j].s_secinfo.sc_nfsnum;
|
|
828 |
goto done;
|
|
829 |
}
|
|
830 |
}
|
|
831 |
}
|
|
832 |
exi = exi->exi_hash;
|
|
833 |
}
|
|
834 |
}
|
|
835 |
done:
|
|
836 |
rw_exit(&exported_lock);
|
|
837 |
|
|
838 |
/*
|
|
839 |
* If no nfs pseudo number mapping can be found in the export
|
|
840 |
* table, assign the nfsflavor to NFS_FLAVOR_NOMAP. In V4, we may
|
|
841 |
* recover the flavor mismatch from NFS layer (NFS4ERR_WRONGSEC).
|
|
842 |
*
|
|
843 |
* For example:
|
|
844 |
* server first shares with krb5i;
|
|
845 |
* client mounts with krb5i;
|
|
846 |
* server re-shares with krb5p;
|
|
847 |
* client tries with krb5i, but no mapping can be found;
|
|
848 |
* rpcsec_gss module calls this routine to do the mapping,
|
|
849 |
* if this routine fails, request is rejected from
|
|
850 |
* the rpc layer.
|
|
851 |
* What we need is to let the nfs layer rejects the request.
|
|
852 |
* For V4, we can reject with NFS4ERR_WRONGSEC and the client
|
|
853 |
* may recover from it by getting the new flavor via SECINFO.
|
|
854 |
*
|
|
855 |
* nfs pseudo number for RPCSEC_GSS mapping (see nfssec.conf)
|
|
856 |
* is owned by IANA (see RFC 2623).
|
|
857 |
*
|
|
858 |
* XXX NFS_FLAVOR_NOMAP is defined in Solaris to work around
|
|
859 |
* the implementation issue. This number should not overlap with
|
|
860 |
* any new IANA defined pseudo flavor numbers.
|
|
861 |
*/
|
|
862 |
if (*cookie == NULL)
|
|
863 |
*cookie = (void *)NFS_FLAVOR_NOMAP;
|
|
864 |
|
|
865 |
lock->locked = TRUE;
|
|
866 |
|
|
867 |
return (TRUE);
|
|
868 |
}
|
|
869 |
|
|
870 |
|
|
871 |
/*
|
|
872 |
* Exportfs system call; credentials should be checked before
|
|
873 |
* calling this function.
|
|
874 |
*/
|
|
875 |
int
|
|
876 |
exportfs(struct exportfs_args *args, model_t model, cred_t *cr)
|
|
877 |
{
|
|
878 |
vnode_t *vp;
|
|
879 |
vnode_t *dvp;
|
|
880 |
struct exportdata *kex;
|
|
881 |
struct exportinfo *exi;
|
|
882 |
struct exportinfo *ex, *prev;
|
|
883 |
fid_t fid;
|
|
884 |
fsid_t fsid;
|
|
885 |
int error;
|
|
886 |
size_t allocsize;
|
|
887 |
struct secinfo *sp;
|
|
888 |
struct secinfo *exs;
|
|
889 |
rpc_gss_callback_t cb;
|
|
890 |
char *pathbuf;
|
|
891 |
char *log_buffer;
|
|
892 |
char *tagbuf;
|
|
893 |
int callback;
|
|
894 |
int allocd_seccnt;
|
|
895 |
STRUCT_HANDLE(exportfs_args, uap);
|
|
896 |
STRUCT_DECL(exportdata, uexi);
|
|
897 |
int i;
|
|
898 |
|
|
899 |
STRUCT_SET_HANDLE(uap, model, args);
|
|
900 |
|
|
901 |
error = lookupname(STRUCT_FGETP(uap, dname), UIO_USERSPACE,
|
|
902 |
FOLLOW, &dvp, &vp);
|
|
903 |
if (error == EINVAL) {
|
|
904 |
/*
|
|
905 |
* if fname resolves to / we get EINVAL error
|
|
906 |
* since we wanted the parent vnode. Try again
|
|
907 |
* with NULL dvp.
|
|
908 |
*/
|
|
909 |
error = lookupname(STRUCT_FGETP(uap, dname), UIO_USERSPACE,
|
|
910 |
FOLLOW, NULL, &vp);
|
|
911 |
dvp = NULL;
|
|
912 |
}
|
|
913 |
if (!error && vp == NULL) {
|
|
914 |
/*
|
|
915 |
* Last component of fname not found
|
|
916 |
*/
|
|
917 |
if (dvp != NULL) {
|
|
918 |
VN_RELE(dvp);
|
|
919 |
}
|
|
920 |
error = ENOENT;
|
|
921 |
}
|
|
922 |
if (error)
|
|
923 |
return (error);
|
|
924 |
|
|
925 |
/*
|
|
926 |
* 'vp' may be an AUTOFS node, so we perform a
|
|
927 |
* VOP_ACCESS() to trigger the mount of the
|
|
928 |
* intended filesystem, so we can share the intended
|
|
929 |
* filesystem instead of the AUTOFS filesystem.
|
|
930 |
*/
|
|
931 |
(void) VOP_ACCESS(vp, 0, 0, cr);
|
|
932 |
|
|
933 |
/*
|
|
934 |
* We're interested in the top most filesystem.
|
|
935 |
* This is specially important when uap->dname is a trigger
|
|
936 |
* AUTOFS node, since we're really interested in sharing the
|
|
937 |
* filesystem AUTOFS mounted as result of the VOP_ACCESS()
|
|
938 |
* call not the AUTOFS node itself.
|
|
939 |
*/
|
|
940 |
if (vn_mountedvfs(vp) != NULL) {
|
|
941 |
if (error = traverse(&vp)) {
|
|
942 |
VN_RELE(vp);
|
|
943 |
if (dvp != NULL)
|
|
944 |
VN_RELE(dvp);
|
|
945 |
return (error);
|
|
946 |
}
|
|
947 |
}
|
|
948 |
|
|
949 |
/*
|
|
950 |
* Get the vfs id
|
|
951 |
*/
|
|
952 |
bzero(&fid, sizeof (fid));
|
|
953 |
fid.fid_len = MAXFIDSZ;
|
|
954 |
error = VOP_FID(vp, &fid);
|
|
955 |
fsid = vp->v_vfsp->vfs_fsid;
|
|
956 |
if (error) {
|
|
957 |
VN_RELE(vp);
|
|
958 |
if (dvp != NULL)
|
|
959 |
VN_RELE(dvp);
|
|
960 |
/*
|
|
961 |
* If VOP_FID returns ENOSPC then the fid supplied
|
|
962 |
* is too small. For now we simply return EREMOTE.
|
|
963 |
*/
|
|
964 |
if (error == ENOSPC)
|
|
965 |
error = EREMOTE;
|
|
966 |
return (error);
|
|
967 |
}
|
|
968 |
|
|
969 |
if (STRUCT_FGETP(uap, uex) == NULL) {
|
|
970 |
error = unexport(&fsid, &fid, vp);
|
|
971 |
VN_RELE(vp);
|
|
972 |
if (dvp != NULL)
|
|
973 |
VN_RELE(dvp);
|
|
974 |
return (error);
|
|
975 |
}
|
|
976 |
exi = kmem_zalloc(sizeof (*exi), KM_SLEEP);
|
|
977 |
exi->exi_fsid = fsid;
|
|
978 |
exi->exi_fid = fid;
|
|
979 |
exi->exi_vp = vp;
|
|
980 |
exi->exi_count = 1;
|
|
981 |
exi->exi_volatile_dev = (vfssw[vp->v_vfsp->vfs_fstype].vsw_flag &
|
|
982 |
VSW_VOLATILEDEV) ? 1 : 0;
|
|
983 |
mutex_init(&exi->exi_lock, NULL, MUTEX_DEFAULT, NULL);
|
|
984 |
exi->exi_dvp = dvp;
|
|
985 |
|
|
986 |
/*
|
|
987 |
* Initialize auth cache lock
|
|
988 |
*/
|
|
989 |
rw_init(&exi->exi_cache_lock, NULL, RW_DEFAULT, NULL);
|
|
990 |
|
|
991 |
/*
|
|
992 |
* Build up the template fhandle
|
|
993 |
*/
|
|
994 |
exi->exi_fh.fh_fsid = fsid;
|
|
995 |
if (exi->exi_fid.fid_len > sizeof (exi->exi_fh.fh_xdata)) {
|
|
996 |
error = EREMOTE;
|
|
997 |
goto out1;
|
|
998 |
}
|
|
999 |
exi->exi_fh.fh_xlen = exi->exi_fid.fid_len;
|
|
1000 |
bcopy(exi->exi_fid.fid_data, exi->exi_fh.fh_xdata,
|
|
1001 |
exi->exi_fid.fid_len);
|
|
1002 |
|
|
1003 |
exi->exi_fh.fh_len = sizeof (exi->exi_fh.fh_data);
|
|
1004 |
|
|
1005 |
kex = &exi->exi_export;
|
|
1006 |
|
|
1007 |
/*
|
|
1008 |
* Load in everything, and do sanity checking
|
|
1009 |
*/
|
|
1010 |
STRUCT_INIT(uexi, model);
|
|
1011 |
if (copyin(STRUCT_FGETP(uap, uex), STRUCT_BUF(uexi),
|
|
1012 |
STRUCT_SIZE(uexi))) {
|
|
1013 |
error = EFAULT;
|
|
1014 |
goto out1;
|
|
1015 |
}
|
|
1016 |
|
|
1017 |
kex->ex_version = STRUCT_FGET(uexi, ex_version);
|
|
1018 |
if (kex->ex_version != EX_CURRENT_VERSION) {
|
|
1019 |
error = EINVAL;
|
|
1020 |
cmn_err(CE_WARN,
|
|
1021 |
"NFS: exportfs requires export struct version 2 - got %d\n",
|
|
1022 |
kex->ex_version);
|
|
1023 |
goto out1;
|
|
1024 |
}
|
|
1025 |
|
|
1026 |
/*
|
|
1027 |
* Must have at least one security entry
|
|
1028 |
*/
|
|
1029 |
kex->ex_seccnt = STRUCT_FGET(uexi, ex_seccnt);
|
|
1030 |
if (kex->ex_seccnt < 1) {
|
|
1031 |
error = EINVAL;
|
|
1032 |
goto out1;
|
|
1033 |
}
|
|
1034 |
|
|
1035 |
kex->ex_path = STRUCT_FGETP(uexi, ex_path);
|
|
1036 |
kex->ex_pathlen = STRUCT_FGET(uexi, ex_pathlen);
|
|
1037 |
kex->ex_flags = STRUCT_FGET(uexi, ex_flags);
|
|
1038 |
kex->ex_anon = STRUCT_FGET(uexi, ex_anon);
|
|
1039 |
kex->ex_secinfo = STRUCT_FGETP(uexi, ex_secinfo);
|
|
1040 |
kex->ex_index = STRUCT_FGETP(uexi, ex_index);
|
|
1041 |
kex->ex_log_buffer = STRUCT_FGETP(uexi, ex_log_buffer);
|
|
1042 |
kex->ex_log_bufferlen = STRUCT_FGET(uexi, ex_log_bufferlen);
|
|
1043 |
kex->ex_tag = STRUCT_FGETP(uexi, ex_tag);
|
|
1044 |
kex->ex_taglen = STRUCT_FGET(uexi, ex_taglen);
|
|
1045 |
|
|
1046 |
/*
|
|
1047 |
* Copy the exported pathname into
|
|
1048 |
* an appropriately sized buffer.
|
|
1049 |
*/
|
|
1050 |
pathbuf = kmem_alloc(MAXPATHLEN, KM_SLEEP);
|
|
1051 |
if (copyinstr(kex->ex_path, pathbuf, MAXPATHLEN, &kex->ex_pathlen)) {
|
|
1052 |
kmem_free(pathbuf, MAXPATHLEN);
|
|
1053 |
error = EFAULT;
|
|
1054 |
goto out1;
|
|
1055 |
}
|
|
1056 |
kex->ex_path = kmem_alloc(kex->ex_pathlen + 1, KM_SLEEP);
|
|
1057 |
bcopy(pathbuf, kex->ex_path, kex->ex_pathlen);
|
|
1058 |
kex->ex_path[kex->ex_pathlen] = '\0';
|
|
1059 |
kmem_free(pathbuf, MAXPATHLEN);
|
|
1060 |
|
|
1061 |
/*
|
|
1062 |
* Get the path to the logging buffer and the tag
|
|
1063 |
*/
|
|
1064 |
if (kex->ex_flags & EX_LOG) {
|
|
1065 |
log_buffer = kmem_alloc(MAXPATHLEN, KM_SLEEP);
|
|
1066 |
if (copyinstr(kex->ex_log_buffer, log_buffer, MAXPATHLEN,
|
|
1067 |
&kex->ex_log_bufferlen)) {
|
|
1068 |
kmem_free(log_buffer, MAXPATHLEN);
|
|
1069 |
error = EFAULT;
|
|
1070 |
goto out2;
|
|
1071 |
}
|
|
1072 |
kex->ex_log_buffer =
|
|
1073 |
kmem_alloc(kex->ex_log_bufferlen + 1, KM_SLEEP);
|
|
1074 |
bcopy(log_buffer, kex->ex_log_buffer, kex->ex_log_bufferlen);
|
|
1075 |
kex->ex_log_buffer[kex->ex_log_bufferlen] = '\0';
|
|
1076 |
kmem_free(log_buffer, MAXPATHLEN);
|
|
1077 |
|
|
1078 |
tagbuf = kmem_alloc(MAXPATHLEN, KM_SLEEP);
|
|
1079 |
if (copyinstr(kex->ex_tag, tagbuf, MAXPATHLEN,
|
|
1080 |
&kex->ex_taglen)) {
|
|
1081 |
kmem_free(tagbuf, MAXPATHLEN);
|
|
1082 |
error = EFAULT;
|
|
1083 |
goto out3;
|
|
1084 |
}
|
|
1085 |
kex->ex_tag = kmem_alloc(kex->ex_taglen + 1, KM_SLEEP);
|
|
1086 |
bcopy(tagbuf, kex->ex_tag, kex->ex_taglen);
|
|
1087 |
kex->ex_tag[kex->ex_taglen] = '\0';
|
|
1088 |
kmem_free(tagbuf, MAXPATHLEN);
|
|
1089 |
}
|
|
1090 |
|
|
1091 |
/*
|
|
1092 |
* Load the security information for each flavor
|
|
1093 |
*/
|
|
1094 |
allocsize = kex->ex_seccnt * SIZEOF_STRUCT(secinfo, model);
|
|
1095 |
sp = kmem_zalloc(allocsize, KM_SLEEP);
|
|
1096 |
if (copyin(kex->ex_secinfo, sp, allocsize)) {
|
|
1097 |
kmem_free(sp, allocsize);
|
|
1098 |
error = EFAULT;
|
|
1099 |
goto out4;
|
|
1100 |
}
|
|
1101 |
|
|
1102 |
/*
|
|
1103 |
* All of these nested structures need to be converted to
|
|
1104 |
* the kernel native format.
|
|
1105 |
*/
|
|
1106 |
if (model != DATAMODEL_NATIVE) {
|
|
1107 |
size_t allocsize2;
|
|
1108 |
struct secinfo *sp2;
|
|
1109 |
|
|
1110 |
allocsize2 = kex->ex_seccnt * sizeof (struct secinfo);
|
|
1111 |
sp2 = kmem_zalloc(allocsize2, KM_SLEEP);
|
|
1112 |
|
|
1113 |
for (i = 0; i < kex->ex_seccnt; i++) {
|
|
1114 |
STRUCT_HANDLE(secinfo, usi);
|
|
1115 |
|
|
1116 |
STRUCT_SET_HANDLE(usi, model,
|
|
1117 |
(struct secinfo *)((caddr_t)sp +
|
|
1118 |
(i * SIZEOF_STRUCT(secinfo, model))));
|
|
1119 |
bcopy(STRUCT_FGET(usi, s_secinfo.sc_name),
|
|
1120 |
sp2[i].s_secinfo.sc_name, MAX_NAME_LEN);
|
|
1121 |
sp2[i].s_secinfo.sc_nfsnum =
|
|
1122 |
STRUCT_FGET(usi, s_secinfo.sc_nfsnum);
|
|
1123 |
sp2[i].s_secinfo.sc_rpcnum =
|
|
1124 |
STRUCT_FGET(usi, s_secinfo.sc_rpcnum);
|
|
1125 |
bcopy(STRUCT_FGET(usi, s_secinfo.sc_gss_mech),
|
|
1126 |
sp2[i].s_secinfo.sc_gss_mech, MAX_NAME_LEN);
|
|
1127 |
sp2[i].s_secinfo.sc_gss_mech_type =
|
|
1128 |
STRUCT_FGETP(usi, s_secinfo.sc_gss_mech_type);
|
|
1129 |
sp2[i].s_secinfo.sc_qop =
|
|
1130 |
STRUCT_FGET(usi, s_secinfo.sc_qop);
|
|
1131 |
sp2[i].s_secinfo.sc_service =
|
|
1132 |
STRUCT_FGET(usi, s_secinfo.sc_service);
|
|
1133 |
|
|
1134 |
sp2[i].s_flags = STRUCT_FGET(usi, s_flags);
|
|
1135 |
sp2[i].s_window = STRUCT_FGET(usi, s_window);
|
|
1136 |
sp2[i].s_rootcnt = STRUCT_FGET(usi, s_rootcnt);
|
|
1137 |
sp2[i].s_rootnames = STRUCT_FGETP(usi, s_rootnames);
|
|
1138 |
}
|
|
1139 |
kmem_free(sp, allocsize);
|
|
1140 |
sp = sp2;
|
|
1141 |
allocsize = allocsize2;
|
|
1142 |
}
|
|
1143 |
|
|
1144 |
kex->ex_secinfo = sp;
|
|
1145 |
|
|
1146 |
/*
|
|
1147 |
* And now copy rootnames for each individual secinfo.
|
|
1148 |
*/
|
|
1149 |
callback = 0;
|
|
1150 |
allocd_seccnt = 0;
|
|
1151 |
while (allocd_seccnt < kex->ex_seccnt) {
|
|
1152 |
|
|
1153 |
exs = &sp[allocd_seccnt];
|
|
1154 |
if (exs->s_rootcnt > 0) {
|
|
1155 |
if (!sec_svc_loadrootnames(exs->s_secinfo.sc_rpcnum,
|
|
1156 |
exs->s_rootcnt, &exs->s_rootnames, model)) {
|
|
1157 |
error = EFAULT;
|
|
1158 |
goto out5;
|
|
1159 |
}
|
|
1160 |
}
|
|
1161 |
|
|
1162 |
if (exs->s_secinfo.sc_rpcnum == RPCSEC_GSS) {
|
|
1163 |
rpc_gss_OID mech_tmp;
|
|
1164 |
STRUCT_DECL(rpc_gss_OID_s, umech_tmp);
|
|
1165 |
caddr_t elements_tmp;
|
|
1166 |
|
|
1167 |
/* Copyin mechanism type */
|
|
1168 |
STRUCT_INIT(umech_tmp, model);
|
|
1169 |
mech_tmp = kmem_alloc(sizeof (*mech_tmp), KM_SLEEP);
|
|
1170 |
if (copyin(exs->s_secinfo.sc_gss_mech_type,
|
|
1171 |
STRUCT_BUF(umech_tmp), STRUCT_SIZE(umech_tmp))) {
|
|
1172 |
kmem_free(mech_tmp, sizeof (*mech_tmp));
|
|
1173 |
error = EFAULT;
|
|
1174 |
goto out5;
|
|
1175 |
}
|
|
1176 |
mech_tmp->length = STRUCT_FGET(umech_tmp, length);
|
|
1177 |
mech_tmp->elements = STRUCT_FGETP(umech_tmp, elements);
|
|
1178 |
|
|
1179 |
elements_tmp = kmem_alloc(mech_tmp->length, KM_SLEEP);
|
|
1180 |
if (copyin(mech_tmp->elements, elements_tmp,
|
|
1181 |
mech_tmp->length)) {
|
|
1182 |
kmem_free(elements_tmp, mech_tmp->length);
|
|
1183 |
kmem_free(mech_tmp, sizeof (*mech_tmp));
|
|
1184 |
error = EFAULT;
|
|
1185 |
goto out5;
|
|
1186 |
}
|
|
1187 |
mech_tmp->elements = elements_tmp;
|
|
1188 |
exs->s_secinfo.sc_gss_mech_type = mech_tmp;
|
|
1189 |
allocd_seccnt++;
|
|
1190 |
|
|
1191 |
callback = 1;
|
|
1192 |
} else
|
|
1193 |
allocd_seccnt++;
|
|
1194 |
}
|
|
1195 |
|
|
1196 |
/*
|
|
1197 |
* Init the secinfo reference count and mark these flavors
|
|
1198 |
* explicitly exported flavors.
|
|
1199 |
*/
|
|
1200 |
for (i = 0; i < kex->ex_seccnt; i++) {
|
|
1201 |
kex->ex_secinfo[i].s_flags |= M_4SEC_EXPORTED;
|
|
1202 |
kex->ex_secinfo[i].s_refcnt++; /* 1 reference count */
|
|
1203 |
}
|
|
1204 |
|
|
1205 |
/*
|
|
1206 |
* Set up rpcsec_gss callback routine entry if any.
|
|
1207 |
*/
|
|
1208 |
if (callback) {
|
|
1209 |
cb.callback = rfs_gsscallback;
|
|
1210 |
cb.program = NFS_ACL_PROGRAM;
|
|
1211 |
for (cb.version = NFS_ACL_VERSMIN;
|
|
1212 |
cb.version <= NFS_ACL_VERSMAX; cb.version++) {
|
|
1213 |
(void) sec_svc_control(RPC_SVC_SET_GSS_CALLBACK,
|
|
1214 |
(void *)&cb);
|
|
1215 |
}
|
|
1216 |
|
|
1217 |
cb.program = NFS_PROGRAM;
|
|
1218 |
for (cb.version = NFS_VERSMIN;
|
|
1219 |
cb.version <= NFS_VERSMAX; cb.version++) {
|
|
1220 |
(void) sec_svc_control(RPC_SVC_SET_GSS_CALLBACK,
|
|
1221 |
(void *)&cb);
|
|
1222 |
}
|
|
1223 |
}
|
|
1224 |
|
|
1225 |
/*
|
|
1226 |
* Check the index flag. Do this here to avoid holding the
|
|
1227 |
* lock while dealing with the index option (as we do with
|
|
1228 |
* the public option).
|
|
1229 |
*/
|
|
1230 |
if (kex->ex_flags & EX_INDEX) {
|
|
1231 |
if (!kex->ex_index) { /* sanity check */
|
|
1232 |
error = EINVAL;
|
|
1233 |
goto out5;
|
|
1234 |
}
|
|
1235 |
if (error = loadindex(kex))
|
|
1236 |
goto out5;
|
|
1237 |
}
|
|
1238 |
|
|
1239 |
if (kex->ex_flags & EX_LOG) {
|
|
1240 |
if (error = nfslog_setup(exi))
|
|
1241 |
goto out6;
|
|
1242 |
}
|
|
1243 |
|
|
1244 |
/*
|
|
1245 |
* Insert the new entry at the front of the export list
|
|
1246 |
*/
|
|
1247 |
rw_enter(&exported_lock, RW_WRITER);
|
|
1248 |
|
|
1249 |
export_link(exi);
|
|
1250 |
|
|
1251 |
/*
|
|
1252 |
* Check the rest of the list for an old entry for the fs.
|
|
1253 |
* If one is found then unlink it, wait until this is the
|
|
1254 |
* only reference and then free it.
|
|
1255 |
*/
|
|
1256 |
prev = exi;
|
|
1257 |
for (ex = prev->exi_hash; ex != NULL; prev = ex, ex = ex->exi_hash) {
|
|
1258 |
if (ex != exi_root && VN_CMP(ex->exi_vp, vp)) {
|
|
1259 |
prev->exi_hash = ex->exi_hash;
|
|
1260 |
break;
|
|
1261 |
}
|
|
1262 |
}
|
|
1263 |
|
|
1264 |
/*
|
|
1265 |
* If the public filehandle is pointing at the
|
|
1266 |
* old entry, then point it back at the root.
|
|
1267 |
*/
|
|
1268 |
if (ex != NULL && ex == exi_public)
|
|
1269 |
exi_public = exi_root;
|
|
1270 |
|
|
1271 |
/*
|
|
1272 |
* If the public flag is on, make the global exi_public
|
|
1273 |
* point to this entry and turn off the public bit so that
|
|
1274 |
* we can distinguish it from the place holder export.
|
|
1275 |
*/
|
|
1276 |
if (kex->ex_flags & EX_PUBLIC) {
|
|
1277 |
exi_public = exi;
|
|
1278 |
kex->ex_flags &= ~EX_PUBLIC;
|
|
1279 |
}
|
|
1280 |
|
|
1281 |
#ifdef VOLATILE_FH_TEST
|
|
1282 |
/*
|
|
1283 |
* Set up the volatile_id value if volatile on share.
|
|
1284 |
* The list of volatile renamed filehandles is always destroyed,
|
|
1285 |
* if the fs was reshared.
|
|
1286 |
*/
|
|
1287 |
if (kex->ex_flags & EX_VOLFH)
|
|
1288 |
exi->exi_volatile_id = gethrestime_sec();
|
|
1289 |
|
|
1290 |
mutex_init(&exi->exi_vol_rename_lock, NULL, MUTEX_DEFAULT, NULL);
|
|
1291 |
#endif /* VOLATILE_FH_TEST */
|
|
1292 |
|
|
1293 |
/*
|
|
1294 |
* If this is a new export, then climb up
|
|
1295 |
* the tree and check if any pseudo exports
|
|
1296 |
* need to be created to provide a path for
|
|
1297 |
* NFS v4 clients.
|
|
1298 |
*/
|
|
1299 |
if (ex == NULL)
|
|
1300 |
error = treeclimb_export(exi);
|
|
1301 |
|
|
1302 |
if (!error)
|
|
1303 |
error = srv_secinfo_treeclimb(exi, TRUE);
|
|
1304 |
|
|
1305 |
/*
|
|
1306 |
* If re-sharing an old export entry, update the secinfo data
|
|
1307 |
* depending on if the old entry is a pseudo node or not.
|
|
1308 |
*/
|
|
1309 |
if (!error && ex != NULL) {
|
|
1310 |
if (PSEUDO(ex)) {
|
|
1311 |
srv_secinfo_add(&exi->exi_export, &ex->exi_export);
|
|
1312 |
} else {
|
|
1313 |
srv_secinfo_exp2exp(&exi->exi_export, &ex->exi_export);
|
|
1314 |
error = srv_secinfo_treeclimb(ex, FALSE);
|
|
1315 |
}
|
|
1316 |
}
|
|
1317 |
|
|
1318 |
if (error)
|
|
1319 |
goto out7;
|
|
1320 |
|
|
1321 |
/*
|
|
1322 |
* If it's a re-export and the old entry has a visible list,
|
|
1323 |
* then transfer its visible list to the new export.
|
|
1324 |
* Note: only VROOT node may have a visible list either
|
|
1325 |
* it is a PSEUDO node or a real export node.
|
|
1326 |
*/
|
|
1327 |
if (ex != NULL && (ex->exi_visible != NULL)) {
|
|
1328 |
exi->exi_visible = ex->exi_visible;
|
|
1329 |
ex->exi_visible = NULL;
|
|
1330 |
}
|
|
1331 |
|
|
1332 |
rw_exit(&exported_lock);
|
|
1333 |
|
|
1334 |
if (exi_public == exi || kex->ex_flags & EX_LOG) {
|
|
1335 |
/*
|
|
1336 |
* Log share operation to this buffer only.
|
|
1337 |
*/
|
|
1338 |
nfslog_share_record(exi, cr);
|
|
1339 |
}
|
|
1340 |
|
|
1341 |
if (ex != NULL)
|
|
1342 |
exi_rele(ex);
|
|
1343 |
|
|
1344 |
return (0);
|
|
1345 |
|
|
1346 |
out7:
|
|
1347 |
/*
|
|
1348 |
* Cleaning up the tree. Assuming *treeclimb* routines
|
|
1349 |
* will fail at the same place in the tree.
|
|
1350 |
*/
|
|
1351 |
(void) treeclimb_unexport(exi);
|
|
1352 |
(void) srv_secinfo_treeclimb(exi, FALSE);
|
|
1353 |
|
|
1354 |
/*
|
|
1355 |
* Unlink and re-link the new and old export in exptable.
|
|
1356 |
*/
|
|
1357 |
(void) export_unlink(&exi->exi_fsid, &exi->exi_fid, exi->exi_vp, NULL);
|
|
1358 |
if (ex != NULL)
|
|
1359 |
export_link(ex);
|
|
1360 |
|
|
1361 |
rw_exit(&exported_lock);
|
|
1362 |
out6:
|
|
1363 |
if (kex->ex_flags & EX_INDEX)
|
|
1364 |
kmem_free(kex->ex_index, strlen(kex->ex_index) + 1);
|
|
1365 |
out5:
|
|
1366 |
/* free partially completed allocation */
|
|
1367 |
while (--allocd_seccnt >= 0) {
|
|
1368 |
exs = &kex->ex_secinfo[allocd_seccnt];
|
|
1369 |
srv_secinfo_entry_free(exs);
|
|
1370 |
}
|
|
1371 |
|
|
1372 |
if (kex->ex_secinfo) {
|
|
1373 |
kmem_free(kex->ex_secinfo,
|
|
1374 |
kex->ex_seccnt * sizeof (struct secinfo));
|
|
1375 |
}
|
|
1376 |
|
|
1377 |
out4:
|
|
1378 |
if ((kex->ex_flags & EX_LOG) && kex->ex_tag != NULL)
|
|
1379 |
kmem_free(kex->ex_tag, kex->ex_taglen + 1);
|
|
1380 |
out3:
|
|
1381 |
if ((kex->ex_flags & EX_LOG) && kex->ex_log_buffer != NULL)
|
|
1382 |
kmem_free(kex->ex_log_buffer, kex->ex_log_bufferlen + 1);
|
|
1383 |
out2:
|
|
1384 |
kmem_free(kex->ex_path, kex->ex_pathlen + 1);
|
|
1385 |
out1:
|
|
1386 |
VN_RELE(vp);
|
|
1387 |
if (dvp != NULL)
|
|
1388 |
VN_RELE(dvp);
|
|
1389 |
mutex_destroy(&exi->exi_lock);
|
|
1390 |
rw_destroy(&exi->exi_cache_lock);
|
|
1391 |
kmem_free(exi, sizeof (*exi));
|
|
1392 |
return (error);
|
|
1393 |
}
|
|
1394 |
|
|
1395 |
/*
|
|
1396 |
* Remove the exportinfo from the export list
|
|
1397 |
*/
|
|
1398 |
int
|
|
1399 |
export_unlink(fsid_t *fsid, fid_t *fid, vnode_t *vp, struct exportinfo **exip)
|
|
1400 |
{
|
|
1401 |
struct exportinfo **tail;
|
|
1402 |
|
|
1403 |
ASSERT(RW_WRITE_HELD(&exported_lock));
|
|
1404 |
|
|
1405 |
tail = &exptable[exptablehash(fsid, fid)];
|
|
1406 |
while (*tail != NULL) {
|
|
1407 |
if (exportmatch(*tail, fsid, fid)) {
|
|
1408 |
/*
|
|
1409 |
* If vp is given, check if vp is the
|
|
1410 |
* same vnode as the exported node.
|
|
1411 |
*
|
|
1412 |
* Since VOP_FID of a lofs node returns the
|
|
1413 |
* fid of its real node (ufs), the exported
|
|
1414 |
* node for lofs and (pseudo) ufs may have
|
|
1415 |
* the same fsid and fid.
|
|
1416 |
*/
|
|
1417 |
if (vp == NULL || vp == (*tail)->exi_vp) {
|
|
1418 |
|
|
1419 |
if (exip != NULL)
|
|
1420 |
*exip = *tail;
|
|
1421 |
*tail = (*tail)->exi_hash;
|
|
1422 |
|
|
1423 |
return (0);
|
|
1424 |
}
|
|
1425 |
}
|
|
1426 |
tail = &(*tail)->exi_hash;
|
|
1427 |
}
|
|
1428 |
|
|
1429 |
return (EINVAL);
|
|
1430 |
}
|
|
1431 |
|
|
1432 |
/*
|
|
1433 |
* Unexport an exported filesystem
|
|
1434 |
*/
|
|
1435 |
int
|
|
1436 |
unexport(fsid_t *fsid, fid_t *fid, vnode_t *vp)
|
|
1437 |
{
|
|
1438 |
struct exportinfo *exi = NULL;
|
|
1439 |
int error;
|
|
1440 |
|
|
1441 |
rw_enter(&exported_lock, RW_WRITER);
|
|
1442 |
|
|
1443 |
error = export_unlink(fsid, fid, vp, &exi);
|
|
1444 |
|
|
1445 |
if (error) {
|
|
1446 |
rw_exit(&exported_lock);
|
|
1447 |
return (error);
|
|
1448 |
}
|
|
1449 |
|
|
1450 |
/* pseudo node is not a real exported filesystem */
|
|
1451 |
if (PSEUDO(exi)) {
|
|
1452 |
/*
|
|
1453 |
* Put the pseudo node back into the export table
|
|
1454 |
* before erroring out.
|
|
1455 |
*/
|
|
1456 |
export_link(exi);
|
|
1457 |
rw_exit(&exported_lock);
|
|
1458 |
return (EINVAL);
|
|
1459 |
}
|
|
1460 |
|
|
1461 |
/*
|
|
1462 |
* If there's a visible list, then need to leave
|
|
1463 |
* a pseudo export here to retain the visible list
|
|
1464 |
* for paths to exports below.
|
|
1465 |
*/
|
|
1466 |
if (exi->exi_visible) {
|
|
1467 |
error = pseudo_exportfs(exi->exi_vp, exi->exi_visible,
|
|
1468 |
&exi->exi_export);
|
|
1469 |
if (error)
|
|
1470 |
goto done;
|
|
1471 |
|
|
1472 |
exi->exi_visible = NULL;
|
|
1473 |
} else {
|
|
1474 |
error = treeclimb_unexport(exi);
|
|
1475 |
if (error)
|
|
1476 |
goto done;
|
|
1477 |
}
|
|
1478 |
|
|
1479 |
error = srv_secinfo_treeclimb(exi, FALSE);
|
|
1480 |
if (error)
|
|
1481 |
goto done;
|
|
1482 |
|
|
1483 |
rw_exit(&exported_lock);
|
|
1484 |
|
|
1485 |
/*
|
|
1486 |
* Need to call into the NFSv4 server and release all data
|
|
1487 |
* held on this particular export. This is important since
|
|
1488 |
* the v4 server may be holding file locks or vnodes under
|
|
1489 |
* this export.
|
|
1490 |
*/
|
|
1491 |
rfs4_clean_state_exi(exi);
|
|
1492 |
|
|
1493 |
/*
|
|
1494 |
* Notify the lock manager that the filesystem is being
|
|
1495 |
* unexported.
|
|
1496 |
*/
|
|
1497 |
lm_unexport(exi);
|
|
1498 |
|
|
1499 |
/*
|
|
1500 |
* If this was a public export, restore
|
|
1501 |
* the public filehandle to the root.
|
|
1502 |
*/
|
|
1503 |
if (exi == exi_public) {
|
|
1504 |
exi_public = exi_root;
|
|
1505 |
|
|
1506 |
nfslog_share_record(exi_public, CRED());
|
|
1507 |
}
|
|
1508 |
|
|
1509 |
if (exi->exi_export.ex_flags & EX_LOG) {
|
|
1510 |
nfslog_unshare_record(exi, CRED());
|
|
1511 |
}
|
|
1512 |
|
|
1513 |
exi_rele(exi);
|
|
1514 |
return (error);
|
|
1515 |
|
|
1516 |
done:
|
|
1517 |
rw_exit(&exported_lock);
|
|
1518 |
exi_rele(exi);
|
|
1519 |
return (error);
|
|
1520 |
}
|
|
1521 |
|
|
1522 |
/*
|
|
1523 |
* Get file handle system call.
|
|
1524 |
* Takes file name and returns a file handle for it.
|
|
1525 |
* Credentials must be verified before calling.
|
|
1526 |
*/
|
|
1527 |
int
|
|
1528 |
nfs_getfh(struct nfs_getfh_args *args, model_t model, cred_t *cr)
|
|
1529 |
{
|
|
1530 |
fhandle_t fh;
|
|
1531 |
vnode_t *vp;
|
|
1532 |
vnode_t *dvp;
|
|
1533 |
struct exportinfo *exi;
|
|
1534 |
int error;
|
|
1535 |
STRUCT_HANDLE(nfs_getfh_args, uap);
|
|
1536 |
|
|
1537 |
#ifdef lint
|
|
1538 |
model = model; /* STRUCT macros don't always use it */
|
|
1539 |
#endif
|
|
1540 |
|
|
1541 |
STRUCT_SET_HANDLE(uap, model, args);
|
|
1542 |
|
|
1543 |
error = lookupname(STRUCT_FGETP(uap, fname), UIO_USERSPACE,
|
|
1544 |
FOLLOW, &dvp, &vp);
|
|
1545 |
if (error == EINVAL) {
|
|
1546 |
/*
|
|
1547 |
* if fname resolves to / we get EINVAL error
|
|
1548 |
* since we wanted the parent vnode. Try again
|
|
1549 |
* with NULL dvp.
|
|
1550 |
*/
|
|
1551 |
error = lookupname(STRUCT_FGETP(uap, fname), UIO_USERSPACE,
|
|
1552 |
FOLLOW, NULL, &vp);
|
|
1553 |
dvp = NULL;
|
|
1554 |
}
|
|
1555 |
if (!error && vp == NULL) {
|
|
1556 |
/*
|
|
1557 |
* Last component of fname not found
|
|
1558 |
*/
|
|
1559 |
if (dvp != NULL) {
|
|
1560 |
VN_RELE(dvp);
|
|
1561 |
}
|
|
1562 |
error = ENOENT;
|
|
1563 |
}
|
|
1564 |
if (error)
|
|
1565 |
return (error);
|
|
1566 |
|
|
1567 |
/*
|
|
1568 |
* 'vp' may be an AUTOFS node, so we perform a
|
|
1569 |
* VOP_ACCESS() to trigger the mount of the
|
|
1570 |
* intended filesystem, so we can share the intended
|
|
1571 |
* filesystem instead of the AUTOFS filesystem.
|
|
1572 |
*/
|
|
1573 |
(void) VOP_ACCESS(vp, 0, 0, cr);
|
|
1574 |
|
|
1575 |
/*
|
|
1576 |
* We're interested in the top most filesystem.
|
|
1577 |
* This is specially important when uap->dname is a trigger
|
|
1578 |
* AUTOFS node, since we're really interested in sharing the
|
|
1579 |
* filesystem AUTOFS mounted as result of the VOP_ACCESS()
|
|
1580 |
* call not the AUTOFS node itself.
|
|
1581 |
*/
|
|
1582 |
if (vn_mountedvfs(vp) != NULL) {
|
|
1583 |
if (error = traverse(&vp)) {
|
|
1584 |
VN_RELE(vp);
|
|
1585 |
if (dvp != NULL)
|
|
1586 |
VN_RELE(dvp);
|
|
1587 |
return (error);
|
|
1588 |
}
|
|
1589 |
}
|
|
1590 |
|
|
1591 |
exi = nfs_vptoexi(dvp, vp, cr, NULL, &error, FALSE);
|
|
1592 |
if (!error) {
|
|
1593 |
error = makefh(&fh, vp, exi);
|
|
1594 |
if (!error && exi->exi_export.ex_flags & EX_LOG) {
|
|
1595 |
nfslog_getfh(exi, &fh, STRUCT_FGETP(uap, fname),
|
|
1596 |
UIO_USERSPACE, cr);
|
|
1597 |
}
|
|
1598 |
exi_rele(exi);
|
|
1599 |
if (!error) {
|
|
1600 |
if (copyout(&fh, STRUCT_FGETP(uap, fhp), sizeof (fh)))
|
|
1601 |
error = EFAULT;
|
|
1602 |
}
|
|
1603 |
}
|
|
1604 |
VN_RELE(vp);
|
|
1605 |
if (dvp != NULL) {
|
|
1606 |
VN_RELE(dvp);
|
|
1607 |
}
|
|
1608 |
return (error);
|
|
1609 |
}
|
|
1610 |
|
|
1611 |
/*
|
|
1612 |
* Strategy: if vp is in the export list, then
|
|
1613 |
* return the associated file handle. Otherwise, ".."
|
|
1614 |
* once up the vp and try again, until the root of the
|
|
1615 |
* filesystem is reached.
|
|
1616 |
*/
|
|
1617 |
struct exportinfo *
|
|
1618 |
nfs_vptoexi(vnode_t *dvp, vnode_t *vp, cred_t *cr, int *walk,
|
|
1619 |
int *err, bool_t v4srv)
|
|
1620 |
{
|
|
1621 |
fid_t fid;
|
|
1622 |
int error;
|
|
1623 |
struct exportinfo *exi;
|
|
1624 |
|
|
1625 |
ASSERT(vp);
|
|
1626 |
VN_HOLD(vp);
|
|
1627 |
if (dvp != NULL) {
|
|
1628 |
VN_HOLD(dvp);
|
|
1629 |
}
|
|
1630 |
if (walk != NULL)
|
|
1631 |
*walk = 0;
|
|
1632 |
|
|
1633 |
for (;;) {
|
|
1634 |
bzero(&fid, sizeof (fid));
|
|
1635 |
fid.fid_len = MAXFIDSZ;
|
|
1636 |
error = vop_fid_pseudo(vp, &fid);
|
|
1637 |
if (error) {
|
|
1638 |
/*
|
|
1639 |
* If vop_fid_pseudo returns ENOSPC then the fid
|
|
1640 |
* supplied is too small. For now we simply
|
|
1641 |
* return EREMOTE.
|
|
1642 |
*/
|
|
1643 |
if (error == ENOSPC)
|
|
1644 |
error = EREMOTE;
|
|
1645 |
break;
|
|
1646 |
}
|
|
1647 |
|
|
1648 |
if (v4srv)
|
|
1649 |
exi = checkexport4(&vp->v_vfsp->vfs_fsid, &fid, vp);
|
|
1650 |
else
|
|
1651 |
exi = checkexport(&vp->v_vfsp->vfs_fsid, &fid);
|
|
1652 |
|
|
1653 |
if (exi != NULL) {
|
|
1654 |
/*
|
|
1655 |
* Found the export info
|
|
1656 |
*/
|
|
1657 |
break;
|
|
1658 |
}
|
|
1659 |
|
|
1660 |
/*
|
|
1661 |
* We have just failed finding a matching export.
|
|
1662 |
* If we're at the root of this filesystem, then
|
|
1663 |
* it's time to stop (with failure).
|
|
1664 |
*/
|
|
1665 |
if (vp->v_flag & VROOT) {
|
|
1666 |
error = EINVAL;
|
|
1667 |
break;
|
|
1668 |
}
|
|
1669 |
|
|
1670 |
if (walk != NULL)
|
|
1671 |
(*walk)++;
|
|
1672 |
|
|
1673 |
/*
|
|
1674 |
* Now, do a ".." up vp. If dvp is supplied, use it,
|
|
1675 |
* otherwise, look it up.
|
|
1676 |
*/
|
|
1677 |
if (dvp == NULL) {
|
|
1678 |
error = VOP_LOOKUP(vp, "..", &dvp, NULL, 0, NULL, cr);
|
|
1679 |
if (error)
|
|
1680 |
break;
|
|
1681 |
}
|
|
1682 |
VN_RELE(vp);
|
|
1683 |
vp = dvp;
|
|
1684 |
dvp = NULL;
|
|
1685 |
}
|
|
1686 |
VN_RELE(vp);
|
|
1687 |
if (dvp != NULL) {
|
|
1688 |
VN_RELE(dvp);
|
|
1689 |
}
|
|
1690 |
if (error != 0) {
|
|
1691 |
if (err != NULL)
|
|
1692 |
*err = error;
|
|
1693 |
return (NULL);
|
|
1694 |
}
|
|
1695 |
return (exi);
|
|
1696 |
}
|
|
1697 |
|
|
1698 |
bool_t
|
|
1699 |
chk_clnt_sec(struct exportinfo *exi, struct svc_req *req)
|
|
1700 |
{
|
|
1701 |
int i, nfsflavor;
|
|
1702 |
struct secinfo *sp;
|
|
1703 |
bool_t sec_found = FALSE;
|
|
1704 |
|
|
1705 |
/*
|
|
1706 |
* Get the nfs flavor number from xprt.
|
|
1707 |
*/
|
|
1708 |
nfsflavor = (int)(uintptr_t)req->rq_xprt->xp_cookie;
|
|
1709 |
|
|
1710 |
sp = exi->exi_export.ex_secinfo;
|
|
1711 |
for (i = 0; i < exi->exi_export.ex_seccnt; i++) {
|
|
1712 |
if (nfsflavor == sp[i].s_secinfo.sc_nfsnum) {
|
|
1713 |
sec_found = TRUE;
|
|
1714 |
break;
|
|
1715 |
}
|
|
1716 |
}
|
|
1717 |
return (sec_found);
|
|
1718 |
}
|
|
1719 |
|
|
1720 |
/*
|
|
1721 |
* Make an fhandle from a vnode
|
|
1722 |
*/
|
|
1723 |
int
|
|
1724 |
makefh(fhandle_t *fh, vnode_t *vp, struct exportinfo *exi)
|
|
1725 |
{
|
|
1726 |
int error;
|
|
1727 |
|
|
1728 |
*fh = exi->exi_fh; /* struct copy */
|
|
1729 |
|
|
1730 |
error = VOP_FID(vp, (fid_t *)&fh->fh_len);
|
|
1731 |
if (error) {
|
|
1732 |
/*
|
|
1733 |
* Should be something other than EREMOTE
|
|
1734 |
*/
|
|
1735 |
return (EREMOTE);
|
|
1736 |
}
|
|
1737 |
return (0);
|
|
1738 |
}
|
|
1739 |
|
|
1740 |
/*
|
|
1741 |
* This routine makes an overloaded V2 fhandle which contains
|
|
1742 |
* sec modes.
|
|
1743 |
*
|
|
1744 |
* Note that the first four octets contain the length octet,
|
|
1745 |
* the status octet, and two padded octets to make them XDR
|
|
1746 |
* four-octet aligned.
|
|
1747 |
*
|
|
1748 |
* 1 2 3 4 32
|
|
1749 |
* +---+---+---+---+---+---+---+---+ +---+---+---+---+ +---+
|
|
1750 |
* | l | s | | | sec_1 |...| sec_n |...| |
|
|
1751 |
* +---+---+---+---+---+---+---+---+ +---+---+---+---+ +---+
|
|
1752 |
*
|
|
1753 |
* where
|
|
1754 |
*
|
|
1755 |
* the status octet s indicates whether there are more security
|
|
1756 |
* flavors (1 means yes, 0 means no) that require the client to
|
|
1757 |
* perform another 0x81 LOOKUP to get them,
|
|
1758 |
*
|
|
1759 |
* the length octet l is the length describing the number of
|
|
1760 |
* valid octets that follow. (l = 4 * n, where n is the number
|
|
1761 |
* of security flavors sent in the current overloaded filehandle.)
|
|
1762 |
*/
|
|
1763 |
int
|
|
1764 |
makefh_ol(fhandle_t *fh, struct exportinfo *exi, uint_t sec_index)
|
|
1765 |
{
|
|
1766 |
static int max_cnt = (NFS_FHSIZE/sizeof (int)) - 1;
|
|
1767 |
int totalcnt, i, *ipt, cnt;
|
|
1768 |
char *c;
|
|
1769 |
|
|
1770 |
if (fh == (fhandle_t *)NULL ||
|
|
1771 |
exi == (struct exportinfo *)NULL ||
|
|
1772 |
sec_index > exi->exi_export.ex_seccnt ||
|
|
1773 |
sec_index < 1)
|
|
1774 |
return (EREMOTE);
|
|
1775 |
|
|
1776 |
totalcnt = exi->exi_export.ex_seccnt-sec_index+1;
|
|
1777 |
cnt = totalcnt > max_cnt? max_cnt : totalcnt;
|
|
1778 |
|
|
1779 |
c = (char *)fh;
|
|
1780 |
/*
|
|
1781 |
* Encode the length octet representing the number of
|
|
1782 |
* security flavors (in bytes) in this overloaded fh.
|
|
1783 |
*/
|
|
1784 |
*c = cnt * sizeof (int);
|
|
1785 |
|
|
1786 |
/*
|
|
1787 |
* Encode the status octet that indicates whether there
|
|
1788 |
* are more security flavors the client needs to get.
|
|
1789 |
*/
|
|
1790 |
*(c+1) = totalcnt > max_cnt;
|
|
1791 |
|
|
1792 |
/*
|
|
1793 |
* put security flavors in the overloaded fh
|
|
1794 |
*/
|
|
1795 |
ipt = (int *)(c + sizeof (int32_t));
|
|
1796 |
for (i = 0; i < cnt; i++) {
|
|
1797 |
*ipt++ = htonl(exi->exi_export.ex_secinfo[i+sec_index-1].
|
|
1798 |
s_secinfo.sc_nfsnum);
|
|
1799 |
}
|
|
1800 |
return (0);
|
|
1801 |
}
|
|
1802 |
|
|
1803 |
/*
|
|
1804 |
* Make an nfs_fh3 from a vnode
|
|
1805 |
*/
|
|
1806 |
int
|
|
1807 |
makefh3(nfs_fh3 *fh, vnode_t *vp, struct exportinfo *exi)
|
|
1808 |
{
|
|
1809 |
int error;
|
|
1810 |
|
|
1811 |
fh->fh3_length = sizeof (fh->fh3_u.nfs_fh3_i);
|
|
1812 |
fh->fh3_u.nfs_fh3_i.fh3_i = exi->exi_fh; /* struct copy */
|
|
1813 |
|
|
1814 |
error = VOP_FID(vp, (fid_t *)&fh->fh3_len);
|
|
1815 |
|
|
1816 |
if (error) {
|
|
1817 |
/*
|
|
1818 |
* Should be something other than EREMOTE
|
|
1819 |
*/
|
|
1820 |
return (EREMOTE);
|
|
1821 |
}
|
|
1822 |
return (0);
|
|
1823 |
}
|
|
1824 |
|
|
1825 |
/*
|
|
1826 |
* This routine makes an overloaded V3 fhandle which contains
|
|
1827 |
* sec modes.
|
|
1828 |
*
|
|
1829 |
* 1 4
|
|
1830 |
* +--+--+--+--+
|
|
1831 |
* | len |
|
|
1832 |
* +--+--+--+--+
|
|
1833 |
* up to 64
|
|
1834 |
* +--+--+--+--+--+--+--+--+--+--+--+--+ +--+--+--+--+
|
|
1835 |
* |s | | | | sec_1 | sec_2 | ... | sec_n |
|
|
1836 |
* +--+--+--+--+--+--+--+--+--+--+--+--+ +--+--+--+--+
|
|
1837 |
*
|
|
1838 |
* len = 4 * (n+1), where n is the number of security flavors
|
|
1839 |
* sent in the current overloaded filehandle.
|
|
1840 |
*
|
|
1841 |
* the status octet s indicates whether there are more security
|
|
1842 |
* mechanisms (1 means yes, 0 means no) that require the client
|
|
1843 |
* to perform another 0x81 LOOKUP to get them.
|
|
1844 |
*
|
|
1845 |
* Three octets are padded after the status octet.
|
|
1846 |
*/
|
|
1847 |
int
|
|
1848 |
makefh3_ol(nfs_fh3 *fh, struct exportinfo *exi, uint_t sec_index)
|
|
1849 |
{
|
|
1850 |
static int max_cnt = NFS3_FHSIZE/sizeof (int) - 1;
|
|
1851 |
int totalcnt, cnt, *ipt, i;
|
|
1852 |
char *c;
|
|
1853 |
|
|
1854 |
if (fh == (nfs_fh3 *)NULL ||
|
|
1855 |
exi == (struct exportinfo *)NULL ||
|
|
1856 |
sec_index > exi->exi_export.ex_seccnt ||
|
|
1857 |
sec_index < 1) {
|
|
1858 |
return (EREMOTE);
|
|
1859 |
}
|
|
1860 |
|
|
1861 |
totalcnt = exi->exi_export.ex_seccnt-sec_index+1;
|
|
1862 |
cnt = totalcnt > max_cnt? max_cnt : totalcnt;
|
|
1863 |
|
|
1864 |
/*
|
|
1865 |
* Place the length in fh3_length representing the number
|
|
1866 |
* of security flavors (in bytes) in this overloaded fh.
|
|
1867 |
*/
|
|
1868 |
fh->fh3_length = (cnt+1) * sizeof (int32_t);
|
|
1869 |
|
|
1870 |
c = (char *)&fh->fh3_u.nfs_fh3_i.fh3_i;
|
|
1871 |
/*
|
|
1872 |
* Encode the status octet that indicates whether there
|
|
1873 |
* are more security flavors the client needs to get.
|
|
1874 |
*/
|
|
1875 |
*c = totalcnt > max_cnt;
|
|
1876 |
|
|
1877 |
/*
|
|
1878 |
* put security flavors in the overloaded fh
|
|
1879 |
*/
|
|
1880 |
ipt = (int *)(c + sizeof (int32_t));
|
|
1881 |
for (i = 0; i < cnt; i++) {
|
|
1882 |
*(ipt+i) = htonl(
|
|
1883 |
exi->exi_export.ex_secinfo[i+sec_index-1].s_secinfo.sc_nfsnum);
|
|
1884 |
}
|
|
1885 |
return (0);
|
|
1886 |
}
|
|
1887 |
|
|
1888 |
/*
|
|
1889 |
* Make an nfs_fh4 from a vnode
|
|
1890 |
*/
|
|
1891 |
int
|
|
1892 |
makefh4(nfs_fh4 *fh, vnode_t *vp, struct exportinfo *exi)
|
|
1893 |
{
|
|
1894 |
int error;
|
|
1895 |
nfs_fh4_fmt_t *fh_fmtp = (nfs_fh4_fmt_t *)fh->nfs_fh4_val;
|
|
1896 |
fid_t fid;
|
|
1897 |
|
|
1898 |
bzero(&fid, sizeof (fid));
|
|
1899 |
fid.fid_len = MAXFIDSZ;
|
|
1900 |
/*
|
|
1901 |
* vop_fid_pseudo() is used to set up NFSv4 namespace, so
|
|
1902 |
* use vop_fid_pseudo() here to get the fid instead of VOP_FID.
|
|
1903 |
*/
|
|
1904 |
error = vop_fid_pseudo(vp, &fid);
|
|
1905 |
if (error)
|
|
1906 |
return (error);
|
|
1907 |
|
|
1908 |
fh->nfs_fh4_len = NFS_FH4_LEN;
|
|
1909 |
|
806
|
1910 |
fh_fmtp->fh4_i.fhx_fsid = exi->exi_fh.fh_fsid;
|
|
1911 |
fh_fmtp->fh4_i.fhx_xlen = exi->exi_fh.fh_xlen;
|
|
1912 |
|
|
1913 |
bzero(fh_fmtp->fh4_i.fhx_data, sizeof (fh_fmtp->fh4_i.fhx_data));
|
|
1914 |
bzero(fh_fmtp->fh4_i.fhx_xdata, sizeof (fh_fmtp->fh4_i.fhx_xdata));
|
|
1915 |
bcopy(exi->exi_fh.fh_xdata, fh_fmtp->fh4_i.fhx_xdata,
|
|
1916 |
exi->exi_fh.fh_xlen);
|
|
1917 |
|
0
|
1918 |
fh_fmtp->fh4_len = fid.fid_len;
|
|
1919 |
ASSERT(fid.fid_len <= sizeof (fh_fmtp->fh4_data));
|
|
1920 |
bcopy(fid.fid_data, fh_fmtp->fh4_data, fid.fid_len);
|
|
1921 |
fh_fmtp->fh4_flag = 0;
|
|
1922 |
|
|
1923 |
#ifdef VOLATILE_FH_TEST
|
|
1924 |
/*
|
|
1925 |
* XXX (temporary?)
|
|
1926 |
* Use the rnode volatile_id value to add volatility to the fh.
|
|
1927 |
*
|
|
1928 |
* For testing purposes there are currently two scenarios, based
|
|
1929 |
* on whether the filesystem was shared with "volatile_fh"
|
|
1930 |
* or "expire_on_rename". In the first case, use the value of
|
|
1931 |
* export struct share_time as the volatile_id. In the second
|
|
1932 |
* case use the vnode volatile_id value (which is set to the
|
|
1933 |
* time in which the file was renamed).
|
|
1934 |
*
|
|
1935 |
* Note that the above are temporary constructs for testing only
|
|
1936 |
* XXX
|
|
1937 |
*/
|
|
1938 |
if (exi->exi_export.ex_flags & EX_VOLRNM) {
|
|
1939 |
fh_fmtp->fh4_volatile_id = find_volrnm_fh_id(exi, fh);
|
|
1940 |
} else if (exi->exi_export.ex_flags & EX_VOLFH) {
|
|
1941 |
fh_fmtp->fh4_volatile_id = exi->exi_volatile_id;
|
|
1942 |
} else {
|
|
1943 |
fh_fmtp->fh4_volatile_id = 0;
|
|
1944 |
}
|
|
1945 |
#endif /* VOLATILE_FH_TEST */
|
|
1946 |
|
|
1947 |
return (0);
|
|
1948 |
}
|
|
1949 |
|
|
1950 |
/*
|
|
1951 |
* Convert an fhandle into a vnode.
|
|
1952 |
* Uses the file id (fh_len + fh_data) in the fhandle to get the vnode.
|
|
1953 |
* WARNING: users of this routine must do a VN_RELE on the vnode when they
|
|
1954 |
* are done with it.
|
|
1955 |
*/
|
|
1956 |
vnode_t *
|
|
1957 |
nfs_fhtovp(fhandle_t *fh, struct exportinfo *exi)
|
|
1958 |
{
|
|
1959 |
vfs_t *vfsp;
|
|
1960 |
vnode_t *vp;
|
|
1961 |
int error;
|
|
1962 |
fid_t *fidp;
|
|
1963 |
|
|
1964 |
TRACE_0(TR_FAC_NFS, TR_FHTOVP_START,
|
|
1965 |
"fhtovp_start");
|
|
1966 |
|
|
1967 |
if (exi == NULL) {
|
|
1968 |
TRACE_1(TR_FAC_NFS, TR_FHTOVP_END,
|
|
1969 |
"fhtovp_end:(%S)", "exi NULL");
|
|
1970 |
return (NULL); /* not exported */
|
|
1971 |
}
|
|
1972 |
|
|
1973 |
ASSERT(exi->exi_vp != NULL);
|
|
1974 |
|
|
1975 |
if (PUBLIC_FH2(fh)) {
|
|
1976 |
if (exi->exi_export.ex_flags & EX_PUBLIC) {
|
|
1977 |
TRACE_1(TR_FAC_NFS, TR_FHTOVP_END,
|
|
1978 |
"fhtovp_end:(%S)", "root not exported");
|
|
1979 |
return (NULL);
|
|
1980 |
}
|
|
1981 |
vp = exi->exi_vp;
|
|
1982 |
VN_HOLD(vp);
|
|
1983 |
return (vp);
|
|
1984 |
}
|
|
1985 |
|
|
1986 |
vfsp = exi->exi_vp->v_vfsp;
|
|
1987 |
ASSERT(vfsp != NULL);
|
|
1988 |
fidp = (fid_t *)&fh->fh_len;
|
|
1989 |
|
|
1990 |
error = VFS_VGET(vfsp, &vp, fidp);
|
|
1991 |
if (error || vp == NULL) {
|
|
1992 |
TRACE_1(TR_FAC_NFS, TR_FHTOVP_END,
|
|
1993 |
"fhtovp_end:(%S)", "VFS_GET failed or vp NULL");
|
|
1994 |
return (NULL);
|
|
1995 |
}
|
|
1996 |
TRACE_1(TR_FAC_NFS, TR_FHTOVP_END,
|
|
1997 |
"fhtovp_end:(%S)", "end");
|
|
1998 |
return (vp);
|
|
1999 |
}
|
|
2000 |
|
|
2001 |
/*
|
|
2002 |
* Convert an fhandle into a vnode.
|
|
2003 |
* Uses the file id (fh_len + fh_data) in the fhandle to get the vnode.
|
|
2004 |
* WARNING: users of this routine must do a VN_RELE on the vnode when they
|
|
2005 |
* are done with it.
|
|
2006 |
* This is just like nfs_fhtovp() but without the exportinfo argument.
|
|
2007 |
*/
|
|
2008 |
|
|
2009 |
vnode_t *
|
|
2010 |
lm_fhtovp(fhandle_t *fh)
|
|
2011 |
{
|
|
2012 |
register vfs_t *vfsp;
|
|
2013 |
vnode_t *vp;
|
|
2014 |
int error;
|
|
2015 |
|
|
2016 |
vfsp = getvfs(&fh->fh_fsid);
|
|
2017 |
if (vfsp == NULL)
|
|
2018 |
return (NULL);
|
|
2019 |
|
|
2020 |
error = VFS_VGET(vfsp, &vp, (fid_t *)&(fh->fh_len));
|
|
2021 |
VFS_RELE(vfsp);
|
|
2022 |
if (error || vp == NULL)
|
|
2023 |
return (NULL);
|
|
2024 |
|
|
2025 |
return (vp);
|
|
2026 |
}
|
|
2027 |
|
|
2028 |
/*
|
|
2029 |
* Convert an nfs_fh3 into a vnode.
|
|
2030 |
* Uses the file id (fh_len + fh_data) in the file handle to get the vnode.
|
|
2031 |
* WARNING: users of this routine must do a VN_RELE on the vnode when they
|
|
2032 |
* are done with it.
|
|
2033 |
*/
|
|
2034 |
vnode_t *
|
|
2035 |
nfs3_fhtovp(nfs_fh3 *fh, struct exportinfo *exi)
|
|
2036 |
{
|
|
2037 |
vfs_t *vfsp;
|
|
2038 |
vnode_t *vp;
|
|
2039 |
int error;
|
|
2040 |
fid_t *fidp;
|
|
2041 |
|
|
2042 |
if (exi == NULL)
|
|
2043 |
return (NULL); /* not exported */
|
|
2044 |
|
|
2045 |
ASSERT(exi->exi_vp != NULL);
|
|
2046 |
|
|
2047 |
if (PUBLIC_FH3(fh)) {
|
|
2048 |
if (exi->exi_export.ex_flags & EX_PUBLIC)
|
|
2049 |
return (NULL);
|
|
2050 |
vp = exi->exi_vp;
|
|
2051 |
VN_HOLD(vp);
|
|
2052 |
return (vp);
|
|
2053 |
}
|
|
2054 |
|
|
2055 |
if (fh->fh3_length != NFS3_CURFHSIZE)
|
|
2056 |
return (NULL);
|
|
2057 |
|
|
2058 |
vfsp = exi->exi_vp->v_vfsp;
|
|
2059 |
ASSERT(vfsp != NULL);
|
|
2060 |
fidp = (fid_t *)&fh->fh3_len;
|
|
2061 |
|
|
2062 |
error = VFS_VGET(vfsp, &vp, fidp);
|
|
2063 |
if (error || vp == NULL)
|
|
2064 |
return (NULL);
|
|
2065 |
|
|
2066 |
return (vp);
|
|
2067 |
}
|
|
2068 |
|
|
2069 |
/*
|
|
2070 |
* Convert an nfs_fh3 into a vnode.
|
|
2071 |
* Uses the file id (fh_len + fh_data) in the file handle to get the vnode.
|
|
2072 |
* WARNING: users of this routine must do a VN_RELE on the vnode when they
|
|
2073 |
* are done with it.
|
|
2074 |
* BTW: This is just like nfs3_fhtovp() but without the exportinfo arg.
|
|
2075 |
* Also, vfsp is accessed through getvfs() rather using exportinfo !!
|
|
2076 |
*/
|
|
2077 |
|
|
2078 |
vnode_t *
|
|
2079 |
lm_nfs3_fhtovp(nfs_fh3 *fh)
|
|
2080 |
{
|
|
2081 |
vfs_t *vfsp;
|
|
2082 |
vnode_t *vp;
|
|
2083 |
int error;
|
|
2084 |
|
|
2085 |
if (fh->fh3_length != NFS3_CURFHSIZE)
|
|
2086 |
return (NULL);
|
|
2087 |
|
|
2088 |
vfsp = getvfs(&fh->fh3_fsid);
|
|
2089 |
if (vfsp == NULL)
|
|
2090 |
return (NULL);
|
|
2091 |
|
|
2092 |
error = VFS_VGET(vfsp, &vp, (fid_t *)&(fh->fh3_len));
|
|
2093 |
VFS_RELE(vfsp);
|
|
2094 |
if (error || vp == NULL)
|
|
2095 |
return (NULL);
|
|
2096 |
|
|
2097 |
return (vp);
|
|
2098 |
}
|
|
2099 |
|
|
2100 |
/*
|
|
2101 |
* Convert an nfs_fh4 into a vnode.
|
|
2102 |
* Uses the file id (fh_len + fh_data) in the file handle to get the vnode.
|
|
2103 |
* WARNING: users of this routine must do a VN_RELE on the vnode when they
|
|
2104 |
* are done with it.
|
|
2105 |
*/
|
|
2106 |
vnode_t *
|
|
2107 |
nfs4_fhtovp(nfs_fh4 *fh, struct exportinfo *exi, nfsstat4 *statp)
|
|
2108 |
{
|
|
2109 |
vfs_t *vfsp;
|
|
2110 |
vnode_t *vp = NULL;
|
|
2111 |
int error;
|
|
2112 |
fid_t *fidp;
|
|
2113 |
nfs_fh4_fmt_t *fh_fmtp;
|
|
2114 |
#ifdef VOLATILE_FH_TEST
|
|
2115 |
uint32_t volatile_id = 0;
|
|
2116 |
#endif /* VOLATILE_FH_TEST */
|
|
2117 |
|
|
2118 |
if (exi == NULL) {
|
|
2119 |
*statp = NFS4ERR_STALE;
|
|
2120 |
return (NULL); /* not exported */
|
|
2121 |
}
|
|
2122 |
ASSERT(exi->exi_vp != NULL);
|
|
2123 |
|
|
2124 |
/* caller should have checked this */
|
|
2125 |
ASSERT(fh->nfs_fh4_len >= NFS_FH4_LEN);
|
|
2126 |
|
|
2127 |
fh_fmtp = (nfs_fh4_fmt_t *)fh->nfs_fh4_val;
|
|
2128 |
vfsp = exi->exi_vp->v_vfsp;
|
|
2129 |
ASSERT(vfsp != NULL);
|
|
2130 |
fidp = (fid_t *)&fh_fmtp->fh4_len;
|
|
2131 |
|
|
2132 |
#ifdef VOLATILE_FH_TEST
|
|
2133 |
/* XXX check if volatile - should be changed later */
|
|
2134 |
if (exi->exi_export.ex_flags & (EX_VOLRNM | EX_VOLFH)) {
|
|
2135 |
/*
|
|
2136 |
* Filesystem is shared with volatile filehandles
|
|
2137 |
*/
|
|
2138 |
if (exi->exi_export.ex_flags & EX_VOLRNM)
|
|
2139 |
volatile_id = find_volrnm_fh_id(exi, fh);
|
|
2140 |
else
|
|
2141 |
volatile_id = exi->exi_volatile_id;
|
|
2142 |
|
|
2143 |
if (fh_fmtp->fh4_volatile_id != volatile_id) {
|
|
2144 |
*statp = NFS4ERR_FHEXPIRED;
|
|
2145 |
return (NULL);
|
|
2146 |
}
|
|
2147 |
}
|
|
2148 |
/*
|
|
2149 |
* XXX even if test_volatile_fh false, the fh may contain a
|
|
2150 |
* volatile id if obtained when the test was set.
|
|
2151 |
*/
|
|
2152 |
fh_fmtp->fh4_volatile_id = (uchar_t)0;
|
|
2153 |
#endif /* VOLATILE_FH_TEST */
|
|
2154 |
|
|
2155 |
error = VFS_VGET(vfsp, &vp, fidp);
|
|
2156 |
/*
|
|
2157 |
* If we can not get vp from VFS_VGET, perhaps this is
|
|
2158 |
* an nfs v2/v3/v4 node in an nfsv4 pseudo filesystem.
|
|
2159 |
* Check it out.
|
|
2160 |
*/
|
|
2161 |
if (error && PSEUDO(exi))
|
|
2162 |
error = nfs4_vget_pseudo(exi, &vp, fidp);
|
|
2163 |
|
|
2164 |
if (error || vp == NULL) {
|
|
2165 |
*statp = NFS4ERR_STALE;
|
|
2166 |
return (NULL);
|
|
2167 |
}
|
|
2168 |
/* XXX - disgusting hack */
|
|
2169 |
if (vp->v_type == VNON && vp->v_flag & V_XATTRDIR)
|
|
2170 |
vp->v_type = VDIR;
|
|
2171 |
*statp = NFS4_OK;
|
|
2172 |
return (vp);
|
|
2173 |
}
|
|
2174 |
|
|
2175 |
/*
|
|
2176 |
* Find the export structure associated with the given filesystem.
|
|
2177 |
* If found, then increment the ref count (exi_count).
|
|
2178 |
*/
|
|
2179 |
struct exportinfo *
|
|
2180 |
checkexport(fsid_t *fsid, fid_t *fid)
|
|
2181 |
{
|
|
2182 |
struct exportinfo *exi;
|
|
2183 |
|
|
2184 |
rw_enter(&exported_lock, RW_READER);
|
|
2185 |
for (exi = exptable[exptablehash(fsid, fid)];
|
|
2186 |
exi != NULL;
|
|
2187 |
exi = exi->exi_hash) {
|
|
2188 |
if (exportmatch(exi, fsid, fid)) {
|
|
2189 |
/*
|
|
2190 |
* If this is the place holder for the
|
|
2191 |
* public file handle, then return the
|
|
2192 |
* real export entry for the public file
|
|
2193 |
* handle.
|
|
2194 |
*/
|
|
2195 |
if (exi->exi_export.ex_flags & EX_PUBLIC) {
|
|
2196 |
exi = exi_public;
|
|
2197 |
}
|
|
2198 |
mutex_enter(&exi->exi_lock);
|
|
2199 |
exi->exi_count++;
|
|
2200 |
mutex_exit(&exi->exi_lock);
|
|
2201 |
rw_exit(&exported_lock);
|
|
2202 |
return (exi);
|
|
2203 |
}
|
|
2204 |
}
|
|
2205 |
rw_exit(&exported_lock);
|
|
2206 |
return (NULL);
|
|
2207 |
}
|
|
2208 |
|
|
2209 |
|
|
2210 |
/*
|
|
2211 |
* "old school" version of checkexport() for NFS4. NFS4
|
|
2212 |
* rfs4_compound holds exported_lock for duration of compound
|
|
2213 |
* processing. This version doesn't manipulate exi_count
|
|
2214 |
* since NFS4 breaks fundamental assumptions in the exi_count
|
|
2215 |
* design.
|
|
2216 |
*/
|
|
2217 |
struct exportinfo *
|
|
2218 |
checkexport4(fsid_t *fsid, fid_t *fid, vnode_t *vp)
|
|
2219 |
{
|
|
2220 |
struct exportinfo *exi;
|
|
2221 |
|
|
2222 |
ASSERT(RW_LOCK_HELD(&exported_lock));
|
|
2223 |
|
|
2224 |
for (exi = exptable[exptablehash(fsid, fid)];
|
|
2225 |
exi != NULL;
|
|
2226 |
exi = exi->exi_hash) {
|
|
2227 |
if (exportmatch(exi, fsid, fid)) {
|
|
2228 |
/*
|
|
2229 |
* If this is the place holder for the
|
|
2230 |
* public file handle, then return the
|
|
2231 |
* real export entry for the public file
|
|
2232 |
* handle.
|
|
2233 |
*/
|
|
2234 |
if (exi->exi_export.ex_flags & EX_PUBLIC) {
|
|
2235 |
exi = exi_public;
|
|
2236 |
}
|
|
2237 |
|
|
2238 |
/*
|
|
2239 |
* If vp is given, check if vp is the
|
|
2240 |
* same vnode as the exported node.
|
|
2241 |
*
|
|
2242 |
* Since VOP_FID of a lofs node returns the
|
|
2243 |
* fid of its real node (ufs), the exported
|
|
2244 |
* node for lofs and (pseudo) ufs may have
|
|
2245 |
* the same fsid and fid.
|
|
2246 |
*/
|
|
2247 |
if (vp == NULL || vp == exi->exi_vp)
|
|
2248 |
return (exi);
|
|
2249 |
}
|
|
2250 |
}
|
|
2251 |
|
|
2252 |
return (NULL);
|
|
2253 |
}
|
|
2254 |
|
|
2255 |
/*
|
|
2256 |
* Free an entire export list node
|
|
2257 |
*/
|
|
2258 |
void
|
|
2259 |
exportfree(struct exportinfo *exi)
|
|
2260 |
{
|
|
2261 |
struct exportdata *ex;
|
|
2262 |
|
|
2263 |
ex = &exi->exi_export;
|
|
2264 |
|
|
2265 |
ASSERT(exi->exi_vp != NULL && !(exi->exi_export.ex_flags & EX_PUBLIC));
|
|
2266 |
VN_RELE(exi->exi_vp);
|
|
2267 |
if (exi->exi_dvp != NULL)
|
|
2268 |
VN_RELE(exi->exi_dvp);
|
|
2269 |
|
|
2270 |
if (ex->ex_flags & EX_INDEX)
|
|
2271 |
kmem_free(ex->ex_index, strlen(ex->ex_index) + 1);
|
|
2272 |
|
|
2273 |
kmem_free(ex->ex_path, ex->ex_pathlen + 1);
|
|
2274 |
nfsauth_cache_free(exi);
|
|
2275 |
|
|
2276 |
if (exi->exi_logbuffer != NULL)
|
|
2277 |
nfslog_disable(exi);
|
|
2278 |
|
|
2279 |
if (ex->ex_flags & EX_LOG) {
|
|
2280 |
kmem_free(ex->ex_log_buffer, ex->ex_log_bufferlen + 1);
|
|
2281 |
kmem_free(ex->ex_tag, ex->ex_taglen + 1);
|
|
2282 |
}
|
|
2283 |
|
|
2284 |
if (exi->exi_visible)
|
|
2285 |
free_visible(exi->exi_visible);
|
|
2286 |
|
|
2287 |
srv_secinfo_list_free(ex->ex_secinfo, ex->ex_seccnt);
|
|
2288 |
|
|
2289 |
#ifdef VOLATILE_FH_TEST
|
|
2290 |
free_volrnm_list(exi);
|
|
2291 |
mutex_destroy(&exi->exi_vol_rename_lock);
|
|
2292 |
#endif /* VOLATILE_FH_TEST */
|
|
2293 |
|
|
2294 |
mutex_destroy(&exi->exi_lock);
|
|
2295 |
rw_destroy(&exi->exi_cache_lock);
|
|
2296 |
|
|
2297 |
kmem_free(exi, sizeof (*exi));
|
|
2298 |
}
|
|
2299 |
|
|
2300 |
/*
|
|
2301 |
* load the index file from user space into kernel space.
|
|
2302 |
*/
|
|
2303 |
static int
|
|
2304 |
loadindex(struct exportdata *kex)
|
|
2305 |
{
|
|
2306 |
int error;
|
|
2307 |
char index[MAXNAMELEN+1];
|
|
2308 |
size_t len;
|
|
2309 |
|
|
2310 |
/*
|
|
2311 |
* copyinstr copies the complete string including the NULL and
|
|
2312 |
* returns the len with the NULL byte included in the calculation
|
|
2313 |
* as long as the max length is not exceeded.
|
|
2314 |
*/
|
|
2315 |
if (error = copyinstr(kex->ex_index, index, sizeof (index), &len))
|
|
2316 |
return (error);
|
|
2317 |
|
|
2318 |
kex->ex_index = kmem_alloc(len, KM_SLEEP);
|
|
2319 |
bcopy(index, kex->ex_index, len);
|
|
2320 |
|
|
2321 |
return (0);
|
|
2322 |
}
|
|
2323 |
|
|
2324 |
/*
|
|
2325 |
* When a thread completes using exi, it should call exi_rele().
|
|
2326 |
* exi_rele() decrements exi_count. It releases exi if exi_count == 0, i.e.
|
|
2327 |
* if this is the last user of exi and exi is not on exportinfo list anymore
|
|
2328 |
*/
|
|
2329 |
void
|
|
2330 |
exi_rele(struct exportinfo *exi)
|
|
2331 |
{
|
|
2332 |
mutex_enter(&exi->exi_lock);
|
|
2333 |
exi->exi_count--;
|
|
2334 |
if (exi->exi_count == 0) {
|
|
2335 |
mutex_exit(&exi->exi_lock);
|
|
2336 |
exportfree(exi);
|
|
2337 |
} else
|
|
2338 |
mutex_exit(&exi->exi_lock);
|
|
2339 |
}
|
|
2340 |
|
|
2341 |
#ifdef VOLATILE_FH_TEST
|
|
2342 |
/*
|
|
2343 |
* Test for volatile fh's - add file handle to list and set its volatile id
|
|
2344 |
* to time it was renamed. If EX_VOLFH is also on and the fs is reshared,
|
|
2345 |
* the vol_rename queue is purged.
|
|
2346 |
*
|
|
2347 |
* XXX This code is for unit testing purposes only... To correctly use it, it
|
|
2348 |
* needs to tie a rename list to the export struct and (more
|
|
2349 |
* important), protect access to the exi rename list using a write lock.
|
|
2350 |
*/
|
|
2351 |
|
|
2352 |
/*
|
|
2353 |
* get the fh vol record if it's in the volatile on rename list. Don't check
|
|
2354 |
* volatile_id in the file handle - compare only the file handles.
|
|
2355 |
*/
|
|
2356 |
static struct ex_vol_rename *
|
|
2357 |
find_volrnm_fh(struct exportinfo *exi, nfs_fh4 *fh4p)
|
|
2358 |
{
|
|
2359 |
struct ex_vol_rename *p = NULL;
|
806
|
2360 |
fhandle_ext_t *fhp;
|
0
|
2361 |
|
|
2362 |
/* XXX shouldn't we assert &exported_lock held? */
|
|
2363 |
ASSERT(MUTEX_HELD(&exi->exi_vol_rename_lock));
|
|
2364 |
|
|
2365 |
if (fh4p->nfs_fh4_len != NFS_FH4_LEN) {
|
|
2366 |
return (NULL);
|
|
2367 |
}
|
806
|
2368 |
fhp = &((nfs_fh4_fmt_t *)fh4p->nfs_fh4_val)->fh4_i;
|
0
|
2369 |
for (p = exi->exi_vol_rename; p != NULL; p = p->vrn_next) {
|
806
|
2370 |
if (bcmp(fhp, &p->vrn_fh_fmt.fh4_i,
|
|
2371 |
sizeof (fhandle_ext_t)) == 0)
|
0
|
2372 |
break;
|
|
2373 |
}
|
|
2374 |
return (p);
|
|
2375 |
}
|
|
2376 |
|
|
2377 |
/*
|
|
2378 |
* get the volatile id for the fh (if there is - else return 0). Ignore the
|
|
2379 |
* volatile_id in the file handle - compare only the file handles.
|
|
2380 |
*/
|
|
2381 |
static uint32_t
|
|
2382 |
find_volrnm_fh_id(struct exportinfo *exi, nfs_fh4 *fh4p)
|
|
2383 |
{
|
|
2384 |
struct ex_vol_rename *p;
|
|
2385 |
uint32_t volatile_id;
|
|
2386 |
|
|
2387 |
mutex_enter(&exi->exi_vol_rename_lock);
|
|
2388 |
p = find_volrnm_fh(exi, fh4p);
|
|
2389 |
volatile_id = (p ? p->vrn_fh_fmt.fh4_volatile_id :
|
|
2390 |
exi->exi_volatile_id);
|
|
2391 |
mutex_exit(&exi->exi_vol_rename_lock);
|
|
2392 |
return (volatile_id);
|
|
2393 |
}
|
|
2394 |
|
|
2395 |
/*
|
|
2396 |
* Free the volatile on rename list - will be called if a filesystem is
|
|
2397 |
* unshared or reshared without EX_VOLRNM
|
|
2398 |
*/
|
|
2399 |
static void
|
|
2400 |
free_volrnm_list(struct exportinfo *exi)
|
|
2401 |
{
|
|
2402 |
struct ex_vol_rename *p, *pnext;
|
|
2403 |
|
|
2404 |
/* no need to hold mutex lock - this one is called from exportfree */
|
|
2405 |
for (p = exi->exi_vol_rename; p != NULL; p = pnext) {
|
|
2406 |
pnext = p->vrn_next;
|
|
2407 |
kmem_free(p, sizeof (*p));
|
|
2408 |
}
|
|
2409 |
exi->exi_vol_rename = NULL;
|
|
2410 |
}
|
|
2411 |
|
|
2412 |
/*
|
|
2413 |
* Add a file handle to the volatile on rename list.
|
|
2414 |
*/
|
|
2415 |
void
|
|
2416 |
add_volrnm_fh(struct exportinfo *exi, vnode_t *vp)
|
|
2417 |
{
|
|
2418 |
struct ex_vol_rename *p;
|
|
2419 |
char fhbuf[NFS4_FHSIZE];
|
|
2420 |
nfs_fh4 fh4;
|
|
2421 |
int error;
|
|
2422 |
|
|
2423 |
fh4.nfs_fh4_val = fhbuf;
|
|
2424 |
error = makefh4(&fh4, vp, exi);
|
|
2425 |
if ((error) || (fh4.nfs_fh4_len != sizeof (p->vrn_fh_fmt))) {
|
|
2426 |
return;
|
|
2427 |
}
|
|
2428 |
|
|
2429 |
mutex_enter(&exi->exi_vol_rename_lock);
|
|
2430 |
|
|
2431 |
p = find_volrnm_fh(exi, &fh4);
|
|
2432 |
|
|
2433 |
if (p == NULL) {
|
|
2434 |
p = kmem_alloc(sizeof (*p), KM_SLEEP);
|
|
2435 |
bcopy(fh4.nfs_fh4_val, &p->vrn_fh_fmt, sizeof (p->vrn_fh_fmt));
|
|
2436 |
p->vrn_next = exi->exi_vol_rename;
|
|
2437 |
exi->exi_vol_rename = p;
|
|
2438 |
}
|
|
2439 |
|
|
2440 |
p->vrn_fh_fmt.fh4_volatile_id = gethrestime_sec();
|
|
2441 |
mutex_exit(&exi->exi_vol_rename_lock);
|
|
2442 |
}
|
|
2443 |
|
|
2444 |
#endif /* VOLATILE_FH_TEST */
|