1 /* NFSv4.1 client for Windows
2 * Copyright © 2012 The Regents of the University of Michigan
4 * Olga Kornievskaia <aglo@umich.edu>
5 * Casey Bodley <cbodley@umich.edu>
7 * This library is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation; either version 2.1 of the License, or (at
10 * your option) any later version.
12 * This library is distributed in the hope that it will be useful, but
13 * without any warranty; without even the implied warranty of merchantability
14 * or fitness for a particular purpose. See the GNU Lesser General Public
15 * License for more details.
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this library; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 #include "from_kernel.h"
27 #include "nfs41_ops.h"
28 #include "delegation.h"
30 #include "daemon_debug.h"
33 #define EALVL 2 /* dprintf level for extended attribute logging */
36 static int set_ea_value(
37 IN nfs41_session
*session
,
38 IN nfs41_path_fh
*parent
,
39 IN state_owner4
*owner
,
40 IN PFILE_FULL_EA_INFORMATION ea
)
42 nfs41_path_fh file
= { 0 };
43 nfs41_file_info createattrs
;
46 open_delegation4 delegation
= { 0 };
47 nfs41_write_verf verf
;
48 uint32_t bytes_written
;
51 /* don't allow values larger than NFS4_EASIZE */
52 if (ea
->EaValueLength
> NFS4_EASIZE
) {
53 eprintf("trying to write extended attribute value of size %d, "
54 "max allowed %d\n", ea
->EaValueLength
, NFS4_EASIZE
);
55 status
= NFS4ERR_FBIG
;
58 /* remove the file on empty value */
59 if (ea
->EaValueLength
== 0) {
61 name
.name
= ea
->EaName
;
62 name
.len
= ea
->EaNameLength
;
63 nfs41_remove(session
, parent
, &name
, 0);
68 claim
.claim
= CLAIM_NULL
;
69 claim
.u
.null
.filename
= &file
.name
;
70 file
.name
.name
= ea
->EaName
;
71 file
.name
.len
= ea
->EaNameLength
;
73 createattrs
.attrmask
.count
= 2;
74 createattrs
.attrmask
.arr
[0] = FATTR4_WORD0_SIZE
;
75 createattrs
.attrmask
.arr
[1] = FATTR4_WORD1_MODE
;
77 createattrs
.mode
= 0664;
79 status
= nfs41_open(session
, parent
, &file
, owner
, &claim
,
80 OPEN4_SHARE_ACCESS_WRITE
| OPEN4_SHARE_ACCESS_WANT_NO_DELEG
,
81 OPEN4_SHARE_DENY_BOTH
, OPEN4_CREATE
, UNCHECKED4
,
82 &createattrs
, TRUE
, &stateid
.stateid
, &delegation
, NULL
);
84 eprintf("nfs41_open() failed with %s\n", nfs_error_string(status
));
88 status
= nfs41_write(session
, &file
, &stateid
,
89 (unsigned char*)ea
->EaName
+ ea
->EaNameLength
+ 1,
90 ea
->EaValueLength
, 0, FILE_SYNC4
, &bytes_written
,
93 eprintf("nfs41_write() failed with %s\n", nfs_error_string(status
));
98 nfs41_close(session
, &file
, &stateid
);
103 static int is_cygwin_ea(
104 PFILE_FULL_EA_INFORMATION ea
)
106 return (strncmp("NfsV3Attributes", ea
->EaName
, ea
->EaNameLength
) == 0
107 && sizeof("NfsV3Attributes")-1 == ea
->EaNameLength
)
108 || (strncmp("NfsActOnLink", ea
->EaName
, ea
->EaNameLength
) == 0
109 && sizeof("NfsActOnLink")-1 == ea
->EaNameLength
)
110 || (strncmp("NfsSymlinkTargetName", ea
->EaName
, ea
->EaNameLength
) == 0
111 && sizeof("NfsSymlinkTargetName")-1 == ea
->EaNameLength
);
114 #define NEXT_ENTRY(ea) ((PBYTE)(ea) + (ea)->NextEntryOffset)
117 IN nfs41_open_state
*state
,
118 IN PFILE_FULL_EA_INFORMATION ea
)
120 nfs41_path_fh attrdir
= { 0 };
123 status
= nfs41_rpc_openattr(state
->session
, &state
->file
, TRUE
, &attrdir
.fh
);
125 eprintf("nfs41_rpc_openattr() failed with error %s\n",
126 nfs_error_string(status
));
130 while (status
== NFS4_OK
) {
131 if (!is_cygwin_ea(ea
))
132 status
= set_ea_value(state
->session
, &attrdir
, &state
->owner
, ea
);
134 if (ea
->NextEntryOffset
== 0)
136 ea
= (PFILE_FULL_EA_INFORMATION
)NEXT_ENTRY(ea
);
144 static int parse_setexattr(unsigned char *buffer
, uint32_t length
, nfs41_upcall
*upcall
)
147 setexattr_upcall_args
*args
= &upcall
->args
.setexattr
;
149 status
= get_name(&buffer
, &length
, &args
->path
);
150 if (status
) goto out
;
151 status
= safe_read(&buffer
, &length
, &args
->mode
, sizeof(args
->mode
));
152 if (status
) goto out
;
153 status
= safe_read(&buffer
, &length
, &args
->buf_len
, sizeof(args
->buf_len
));
154 if (status
) goto out
;
157 dprintf(1, "parsing NFS41_EA_SET: mode=%o\n", args
->mode
);
162 static int handle_setexattr(nfs41_upcall
*upcall
)
165 setexattr_upcall_args
*args
= &upcall
->args
.setexattr
;
166 nfs41_open_state
*state
= upcall
->state_ref
;
167 PFILE_FULL_EA_INFORMATION ea
=
168 (PFILE_FULL_EA_INFORMATION
)args
->buf
;
170 /* break read delegations before SETATTR */
171 nfs41_delegation_return(state
->session
, &state
->file
,
172 OPEN_DELEGATE_READ
, FALSE
);
174 if (strncmp("NfsV3Attributes", ea
->EaName
, ea
->EaNameLength
) == 0
175 && sizeof("NfsV3Attributes")-1 == ea
->EaNameLength
) {
176 nfs41_file_info info
;
179 nfs41_open_stateid_arg(state
, &stateid
);
181 info
.mode
= args
->mode
;
182 info
.attrmask
.arr
[0] = 0;
183 info
.attrmask
.arr
[1] = FATTR4_WORD1_MODE
;
184 info
.attrmask
.count
= 2;
186 status
= nfs41_setattr(state
->session
, &state
->file
, &stateid
, &info
);
188 dprintf(1, "nfs41_setattr() failed with error %s.\n",
189 nfs_error_string(status
));
193 args
->ctime
= info
.change
;
197 status
= nfs41_ea_set(state
, ea
);
199 return nfs_to_windows_error(status
, ERROR_NOT_SUPPORTED
);
202 static int marshall_setexattr(unsigned char *buffer
, uint32_t *length
, nfs41_upcall
*upcall
)
204 setexattr_upcall_args
*args
= &upcall
->args
.setexattr
;
205 return safe_write(&buffer
, length
, &args
->ctime
, sizeof(args
->ctime
));
210 static int parse_getexattr(unsigned char *buffer
, uint32_t length
, nfs41_upcall
*upcall
)
213 getexattr_upcall_args
*args
= &upcall
->args
.getexattr
;
215 status
= get_name(&buffer
, &length
, &args
->path
);
216 if (status
) goto out
;
217 status
= safe_read(&buffer
, &length
, &args
->eaindex
, sizeof(args
->eaindex
));
218 if (status
) goto out
;
219 status
= safe_read(&buffer
, &length
, &args
->restart
, sizeof(args
->restart
));
220 if (status
) goto out
;
221 status
= safe_read(&buffer
, &length
, &args
->single
, sizeof(args
->single
));
222 if (status
) goto out
;
223 status
= safe_read(&buffer
, &length
, &args
->buf_len
, sizeof(args
->buf_len
));
224 if (status
) goto out
;
225 status
= safe_read(&buffer
, &length
, &args
->ealist_len
, sizeof(args
->ealist_len
));
226 if (status
) goto out
;
227 args
->ealist
= args
->ealist_len
? buffer
: NULL
;
229 dprintf(1, "parsing NFS41_EA_GET: buf_len=%d Index %d Restart %d "
230 "Single %d\n", args
->buf_len
,args
->eaindex
, args
->restart
, args
->single
);
235 #define READDIR_LEN_INITIAL 8192
236 #define READDIR_LEN_MIN 2048
238 /* call readdir repeatedly to get a complete list of entries */
239 static int read_entire_dir(
240 IN nfs41_session
*session
,
241 IN nfs41_path_fh
*eadir
,
242 OUT
unsigned char **buffer_out
,
243 OUT
uint32_t *length_out
)
245 nfs41_readdir_cookie cookie
= { 0 };
246 bitmap4 attr_request
;
247 nfs41_readdir_entry
*last_entry
;
248 unsigned char *buffer
;
249 uint32_t buffer_len
, len
, total_len
;
251 int status
= NO_ERROR
;
253 attr_request
.count
= 0; /* don't request attributes */
255 /* allocate the buffer for readdir entries */
256 buffer_len
= READDIR_LEN_INITIAL
;
257 buffer
= calloc(1, buffer_len
);
258 if (buffer
== NULL
) {
259 status
= GetLastError();
268 len
= buffer_len
- total_len
;
269 if (len
< READDIR_LEN_MIN
) {
270 const ptrdiff_t diff
= (unsigned char*)last_entry
- buffer
;
271 /* realloc the buffer to fit more entries */
272 unsigned char *tmp
= realloc(buffer
, buffer_len
* 2);
274 status
= GetLastError();
278 if (last_entry
) /* fix last_entry pointer */
279 last_entry
= (nfs41_readdir_entry
*)(tmp
+ diff
);
282 len
= buffer_len
- total_len
;
285 /* fetch the next group of entries */
286 status
= nfs41_readdir(session
, eadir
, &attr_request
,
287 &cookie
, buffer
+ total_len
, &len
, &eof
);
291 if (last_entry
== NULL
) {
292 /* initialize last_entry to the front of the list */
293 last_entry
= (nfs41_readdir_entry
*)(buffer
+ total_len
);
295 /* link the previous list to the new one */
296 last_entry
->next_entry_offset
= (uint32_t)FIELD_OFFSET(
297 nfs41_readdir_entry
, name
) + last_entry
->name_len
;
300 /* find the new last entry */
301 while (last_entry
->next_entry_offset
) {
302 last_entry
= (nfs41_readdir_entry
*)((char*)last_entry
+
303 last_entry
->next_entry_offset
);
306 cookie
.cookie
= last_entry
->cookie
;
310 *buffer_out
= buffer
;
311 *length_out
= total_len
;
320 #define ALIGNED_EASIZE(len) (align4(sizeof(FILE_GET_EA_INFORMATION) + len))
322 static uint32_t calculate_ea_list_length(
323 IN
const unsigned char *position
,
324 IN
uint32_t remaining
)
326 const nfs41_readdir_entry
*entry
;
330 entry
= (const nfs41_readdir_entry
*)position
;
331 length
+= ALIGNED_EASIZE(entry
->name_len
);
333 if (!entry
->next_entry_offset
)
336 position
+= entry
->next_entry_offset
;
337 remaining
-= entry
->next_entry_offset
;
342 static void populate_ea_list(
343 IN
const unsigned char *position
,
344 OUT PFILE_GET_EA_INFORMATION ea_list
)
346 const nfs41_readdir_entry
*entry
;
347 PFILE_GET_EA_INFORMATION ea
= ea_list
, prev
= NULL
;
350 entry
= (const nfs41_readdir_entry
*)position
;
351 StringCchCopyA(ea
->EaName
, entry
->name_len
, entry
->name
);
352 ea
->EaNameLength
= (UCHAR
)entry
->name_len
- 1;
354 if (!entry
->next_entry_offset
) {
355 ea
->NextEntryOffset
= 0;
360 ea
->NextEntryOffset
= ALIGNED_EASIZE(ea
->EaNameLength
);
361 ea
= (PFILE_GET_EA_INFORMATION
)NEXT_ENTRY(ea
);
362 position
+= entry
->next_entry_offset
;
366 static int get_ea_list(
367 IN OUT nfs41_open_state
*state
,
368 IN nfs41_path_fh
*eadir
,
369 OUT PFILE_GET_EA_INFORMATION
*ealist_out
,
370 OUT
uint32_t *eaindex_out
)
372 unsigned char *entry_list
;
373 PFILE_GET_EA_INFORMATION ea_list
;
374 uint32_t entry_len
, ea_size
;
375 int status
= NO_ERROR
;
377 EnterCriticalSection(&state
->ea
.lock
);
379 if (state
->ea
.list
!= INVALID_HANDLE_VALUE
) {
380 /* use cached ea names */
381 *ealist_out
= state
->ea
.list
;
382 *eaindex_out
= state
->ea
.index
;
386 /* read the entire directory into a nfs41_readdir_entry buffer */
387 status
= read_entire_dir(state
->session
, eadir
, &entry_list
, &entry_len
);
391 ea_size
= calculate_ea_list_length(entry_list
, entry_len
);
393 *ealist_out
= state
->ea
.list
= NULL
;
396 ea_list
= calloc(1, ea_size
);
397 if (ea_list
== NULL
) {
398 status
= GetLastError();
402 populate_ea_list(entry_list
, ea_list
);
404 *ealist_out
= state
->ea
.list
= ea_list
;
405 *eaindex_out
= state
->ea
.index
;
407 free(entry_list
); /* allocated by read_entire_dir() */
409 LeaveCriticalSection(&state
->ea
.lock
);
413 static int get_ea_value(
414 IN nfs41_session
*session
,
415 IN nfs41_path_fh
*parent
,
416 IN state_owner4
*owner
,
417 OUT PFILE_FULL_EA_INFORMATION ea
,
419 OUT
uint32_t *needed
)
421 nfs41_path_fh file
= { 0 };
424 open_delegation4 delegation
= { 0 };
425 nfs41_file_info info
;
426 unsigned char *buffer
;
427 uint32_t diff
, bytes_read
;
431 if (parent
->fh
.len
== 0) /* no named attribute directory */
434 claim
.claim
= CLAIM_NULL
;
435 claim
.u
.null
.filename
= &file
.name
;
436 file
.name
.name
= ea
->EaName
;
437 file
.name
.len
= ea
->EaNameLength
;
439 status
= nfs41_open(session
, parent
, &file
, owner
, &claim
,
440 OPEN4_SHARE_ACCESS_READ
| OPEN4_SHARE_ACCESS_WANT_NO_DELEG
,
441 OPEN4_SHARE_DENY_WRITE
, OPEN4_NOCREATE
, UNCHECKED4
, NULL
, TRUE
,
442 &stateid
.stateid
, &delegation
, &info
);
444 eprintf("nfs41_open() failed with %s\n", nfs_error_string(status
));
445 if (status
== NFS4ERR_NOENT
)
450 if (info
.size
> NFS4_EASIZE
) {
451 status
= NFS4ERR_FBIG
;
452 eprintf("EA value for '%s' longer than maximum %u "
453 "(%llu bytes), returning %s\n", ea
->EaName
, NFS4_EASIZE
,
454 info
.size
, nfs_error_string(status
));
458 buffer
= (unsigned char*)ea
->EaName
+ ea
->EaNameLength
+ 1;
459 diff
= (uint32_t)(buffer
- (unsigned char*)ea
);
461 /* make sure we have room for the value */
462 if (length
< diff
+ info
.size
) {
463 *needed
= (uint32_t)(sizeof(FILE_FULL_EA_INFORMATION
) +
464 ea
->EaNameLength
+ info
.size
);
465 status
= NFS4ERR_TOOSMALL
;
469 /* read directly into the ea buffer */
470 status
= nfs41_read(session
, &file
, &stateid
,
471 0, length
- diff
, buffer
, &bytes_read
, &eof
);
473 eprintf("nfs41_read() failed with %s\n", nfs_error_string(status
));
477 *needed
= (uint32_t)(sizeof(FILE_FULL_EA_INFORMATION
) +
478 ea
->EaNameLength
+ NFS4_EASIZE
);
479 status
= NFS4ERR_TOOSMALL
;
483 ea
->EaValueLength
= (USHORT
)bytes_read
;
486 nfs41_close(session
, &file
, &stateid
);
490 out_empty
: /* return an empty value */
491 ea
->EaValueLength
= 0;
496 static int empty_ea_error(
500 /* choose an error value depending on the arguments */
502 return ERROR_INVALID_EA_HANDLE
;
505 return ERROR_NO_MORE_FILES
; /* -> STATUS_NO_MORE_EAS */
507 return ERROR_FILE_NOT_FOUND
; /* -> STATUS_NO_EAS_ON_FILE */
510 static int overflow_error(
511 IN OUT getexattr_upcall_args
*args
,
512 IN PFILE_FULL_EA_INFORMATION prev
,
516 /* unlink the overflowing entry, but copy the entries that fit */
517 prev
->NextEntryOffset
= 0;
518 args
->overflow
= ERROR_BUFFER_OVERFLOW
;
520 /* no entries fit; return only the length needed */
521 args
->buf_len
= needed
;
522 args
->overflow
= ERROR_INSUFFICIENT_BUFFER
;
525 /* in either case, the upcall must return NO_ERROR so we
526 * can copy this information down to the driver */
530 static int handle_getexattr(nfs41_upcall
*upcall
)
532 getexattr_upcall_args
*args
= &upcall
->args
.getexattr
;
533 PFILE_GET_EA_INFORMATION query
= (PFILE_GET_EA_INFORMATION
)args
->ealist
;
534 PFILE_FULL_EA_INFORMATION ea
, prev
= NULL
;
535 nfs41_open_state
*state
= upcall
->state_ref
;
536 nfs41_path_fh parent
= { 0 };
537 uint32_t remaining
, needed
, index
= 0;
540 status
= nfs41_rpc_openattr(state
->session
, &state
->file
, FALSE
, &parent
.fh
);
541 if (status
== NFS4ERR_NOENT
) { /* no named attribute directory */
542 dprintf(EALVL
, "no named attribute directory for '%s'\n", args
->path
);
544 status
= empty_ea_error(args
->eaindex
, args
->restart
);
548 eprintf("nfs41_rpc_openattr() failed with %s\n",
549 nfs_error_string(status
));
550 status
= nfs_to_windows_error(status
, ERROR_EAS_NOT_SUPPORTED
);
555 /* if no names are queried, use READDIR to list them all */
557 status
= get_ea_list(state
, &parent
, &query
, &index
);
561 if (query
== NULL
) { /* the file has no EAs */
562 dprintf(EALVL
, "empty named attribute directory for '%s'\n",
564 status
= empty_ea_error(args
->eaindex
, args
->restart
);
569 index
= args
->eaindex
- 1; /* convert to zero-based index */
570 else if (args
->restart
)
573 /* advance the list to the specified index */
574 for (i
= 0; i
< index
; i
++) {
575 if (query
->NextEntryOffset
== 0) {
577 status
= ERROR_INVALID_EA_HANDLE
;
579 status
= ERROR_NO_MORE_FILES
; /* STATUS_NO_MORE_EAS */
582 query
= (PFILE_GET_EA_INFORMATION
)NEXT_ENTRY(query
);
586 /* returned ea information can't exceed the downcall buffer size */
587 if (args
->buf_len
> UPCALL_BUF_SIZE
- 2 * sizeof(uint32_t))
588 args
->buf_len
= UPCALL_BUF_SIZE
- 2 * sizeof(uint32_t);
590 args
->buf
= malloc(args
->buf_len
);
591 if (args
->buf
== NULL
) {
592 status
= GetLastError();
596 ea
= (PFILE_FULL_EA_INFORMATION
)args
->buf
;
597 remaining
= args
->buf_len
;
600 /* make sure we have room for at least the name */
601 needed
= sizeof(FILE_FULL_EA_INFORMATION
) + query
->EaNameLength
;
602 if (needed
> remaining
) {
603 status
= overflow_error(args
, prev
, needed
+ NFS4_EASIZE
);
607 ea
->EaNameLength
= query
->EaNameLength
;
608 StringCchCopy(ea
->EaName
, ea
->EaNameLength
+ 1, query
->EaName
);
611 /* read the value from file */
612 status
= get_ea_value(state
->session
, &parent
,
613 &state
->owner
, ea
, remaining
, &needed
);
614 if (status
== NFS4ERR_TOOSMALL
) {
615 status
= overflow_error(args
, prev
, needed
);
619 status
= nfs_to_windows_error(status
, ERROR_EA_FILE_CORRUPT
);
623 needed
= align4(FIELD_OFFSET(FILE_FULL_EA_INFORMATION
, EaName
) +
624 ea
->EaNameLength
+ 1 + ea
->EaValueLength
);
626 if (remaining
< needed
) {
627 /* align4 may push NextEntryOffset past our buffer, but we
628 * were still able to fit the ea value. set remaining = 0
629 * so we'll fail on the next ea (if any) */
635 if (query
->NextEntryOffset
== 0 || args
->single
)
639 ea
->NextEntryOffset
= needed
;
640 ea
= (PFILE_FULL_EA_INFORMATION
)NEXT_ENTRY(ea
);
641 query
= (PFILE_GET_EA_INFORMATION
)NEXT_ENTRY(query
);
644 ea
->NextEntryOffset
= 0;
645 args
->buf_len
-= remaining
;
647 if (args
->ealist
== NULL
) { /* update the ea index */
648 EnterCriticalSection(&state
->ea
.lock
);
649 state
->ea
.index
= index
;
650 if (status
== NO_ERROR
&& !args
->overflow
&& !args
->single
) {
651 /* listing was completed, free the cache */
652 free(state
->ea
.list
);
653 state
->ea
.list
= INVALID_HANDLE_VALUE
;
655 LeaveCriticalSection(&state
->ea
.lock
);
664 static int marshall_getexattr(unsigned char *buffer
, uint32_t *length
, nfs41_upcall
*upcall
)
666 int status
= NO_ERROR
;
667 getexattr_upcall_args
*args
= &upcall
->args
.getexattr
;
669 status
= safe_write(&buffer
, length
, &args
->overflow
, sizeof(args
->overflow
));
670 if (status
) goto out
;
671 status
= safe_write(&buffer
, length
, &args
->buf_len
, sizeof(args
->buf_len
));
672 if (status
) goto out
;
673 if (args
->overflow
== ERROR_INSUFFICIENT_BUFFER
)
675 status
= safe_write(&buffer
, length
, args
->buf
, args
->buf_len
);
676 if (status
) goto out
;
683 const nfs41_upcall_op nfs41_op_setexattr
= {
689 const nfs41_upcall_op nfs41_op_getexattr
= {