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"
29 #include "name_cache.h"
32 #include "daemon_debug.h"
36 static int parse_setattr(unsigned char *buffer
, uint32_t length
, nfs41_upcall
*upcall
)
39 setattr_upcall_args
*args
= &upcall
->args
.setattr
;
41 status
= get_name(&buffer
, &length
, &args
->path
);
43 status
= safe_read(&buffer
, &length
, &args
->set_class
, sizeof(args
->set_class
));
45 status
= safe_read(&buffer
, &length
, &args
->buf_len
, sizeof(args
->buf_len
));
49 args
->root
= upcall
->root_ref
;
50 args
->state
= upcall
->state_ref
;
52 dprintf(1, "parsing NFS41_FILE_SET: filename='%s' info_class=%d "
53 "buf_len=%d\n", args
->path
, args
->set_class
, args
->buf_len
);
58 static int handle_nfs41_setattr(setattr_upcall_args
*args
)
60 PFILE_BASIC_INFO basic_info
= (PFILE_BASIC_INFO
)args
->buf
;
61 nfs41_open_state
*state
= args
->state
;
62 nfs41_superblock
*superblock
= state
->file
.fh
.superblock
;
64 nfs41_file_info info
= { 0 }, old_info
= { 0 };
65 int status
= NO_ERROR
, getattr_status
;
67 if (basic_info
->FileAttributes
) {
68 info
.hidden
= basic_info
->FileAttributes
& FILE_ATTRIBUTE_HIDDEN
? 1 : 0;
69 info
.system
= basic_info
->FileAttributes
& FILE_ATTRIBUTE_SYSTEM
? 1 : 0;
70 info
.archive
= basic_info
->FileAttributes
& FILE_ATTRIBUTE_ARCHIVE
? 1 : 0;
71 getattr_status
= nfs41_attr_cache_lookup(session_name_cache(state
->session
),
72 state
->file
.fh
.fileid
, &old_info
);
74 if (getattr_status
|| info
.hidden
!= old_info
.hidden
) {
75 info
.attrmask
.arr
[0] = FATTR4_WORD0_HIDDEN
;
76 info
.attrmask
.count
= 1;
78 if (getattr_status
|| info
.archive
!= old_info
.archive
) {
79 info
.attrmask
.arr
[0] |= FATTR4_WORD0_ARCHIVE
;
80 info
.attrmask
.count
= 1;
82 if (getattr_status
|| info
.system
!= old_info
.system
) {
83 info
.attrmask
.arr
[1] = FATTR4_WORD1_SYSTEM
;
84 info
.attrmask
.count
= 2;
87 if (old_info
.mode
== 0444 &&
88 ((basic_info
->FileAttributes
& FILE_ATTRIBUTE_READONLY
) == 0)) {
90 info
.attrmask
.arr
[1] |= FATTR4_WORD1_MODE
;
91 info
.attrmask
.count
= 2;
94 if (superblock
->cansettime
) {
95 /* set the time_delta so xdr_settime4() can decide
96 * whether or not to use SET_TO_SERVER_TIME4 */
97 info
.time_delta
= &superblock
->time_delta
;
100 if (basic_info
->CreationTime
.QuadPart
> 0) {
101 file_time_to_nfs_time(&basic_info
->CreationTime
,
103 info
.attrmask
.arr
[1] |= FATTR4_WORD1_TIME_CREATE
;
104 info
.attrmask
.count
= 2;
106 /* time_access_set */
107 if (basic_info
->LastAccessTime
.QuadPart
> 0) {
108 file_time_to_nfs_time(&basic_info
->LastAccessTime
,
110 info
.attrmask
.arr
[1] |= FATTR4_WORD1_TIME_ACCESS_SET
;
111 info
.attrmask
.count
= 2;
113 /* time_modify_set */
114 if (basic_info
->LastWriteTime
.QuadPart
> 0) {
115 file_time_to_nfs_time(&basic_info
->LastWriteTime
,
117 info
.attrmask
.arr
[1] |= FATTR4_WORD1_TIME_MODIFY_SET
;
118 info
.attrmask
.count
= 2;
123 if (basic_info
->FileAttributes
& FILE_ATTRIBUTE_READONLY
) {
125 info
.attrmask
.arr
[1] |= FATTR4_WORD1_MODE
;
126 info
.attrmask
.count
= 2;
129 /* mask out unsupported attributes */
130 nfs41_superblock_supported_attrs(superblock
, &info
.attrmask
);
132 if (!info
.attrmask
.count
)
135 /* break read delegations before SETATTR */
136 nfs41_delegation_return(state
->session
, &state
->file
,
137 OPEN_DELEGATE_READ
, FALSE
);
139 nfs41_open_stateid_arg(state
, &stateid
);
141 status
= nfs41_setattr(state
->session
, &state
->file
, &stateid
, &info
);
143 dprintf(1, "nfs41_setattr() failed with error %s.\n",
144 nfs_error_string(status
));
145 status
= nfs_to_windows_error(status
, ERROR_NOT_SUPPORTED
);
147 args
->ctime
= info
.change
;
152 static int handle_nfs41_remove(setattr_upcall_args
*args
)
154 nfs41_open_state
*state
= args
->state
;
157 /* break any delegations and truncate before REMOVE */
158 nfs41_delegation_return(state
->session
, &state
->file
,
159 OPEN_DELEGATE_WRITE
, TRUE
);
161 status
= nfs41_remove(state
->session
, &state
->parent
,
162 &state
->file
.name
, state
->file
.fh
.fileid
);
164 dprintf(1, "nfs41_remove() failed with error %s.\n",
165 nfs_error_string(status
));
167 return nfs_to_windows_error(status
, ERROR_ACCESS_DENIED
);
170 static void open_state_rename(
171 OUT nfs41_open_state
*state
,
172 IN
const nfs41_abs_path
*path
)
174 AcquireSRWLockExclusive(&state
->path
.lock
);
176 abs_path_copy(&state
->path
, path
);
177 last_component(state
->path
.path
, state
->path
.path
+ state
->path
.len
,
179 last_component(state
->path
.path
, state
->file
.name
.name
,
180 &state
->parent
.name
);
182 ReleaseSRWLockExclusive(&state
->path
.lock
);
185 static int nfs41_abs_path_compare(
186 IN
const struct list_entry
*entry
,
187 IN
const void *value
)
189 nfs41_open_state
*client
= list_container(entry
, nfs41_open_state
, client_entry
);
190 const nfs41_abs_path
*name
= (const nfs41_abs_path
*)value
;
191 if (client
->path
.len
== name
->len
&&
192 !strncmp(client
->path
.path
, name
->path
, client
->path
.len
))
194 return ERROR_FILE_NOT_FOUND
;
197 static int is_dst_name_opened(nfs41_abs_path
*dst_path
, nfs41_session
*dst_session
)
200 nfs41_client
*client
= dst_session
->client
;
202 EnterCriticalSection(&client
->state
.lock
);
203 if (list_search(&client
->state
.opens
, dst_path
, nfs41_abs_path_compare
))
207 LeaveCriticalSection(&client
->state
.lock
);
211 static int handle_nfs41_rename(setattr_upcall_args
*args
)
213 nfs41_open_state
*state
= args
->state
;
214 nfs41_session
*dst_session
;
215 PFILE_RENAME_INFO rename
= (PFILE_RENAME_INFO
)args
->buf
;
216 nfs41_abs_path dst_path
= { 0 };
217 nfs41_path_fh dst_dir
, dst
;
218 nfs41_component dst_name
, *src_name
;
222 src_name
= &state
->file
.name
;
224 if (rename
->FileNameLength
== 0) {
225 /* start from state->path instead of args->path, in case we got
226 * the file from a referred server */
227 AcquireSRWLockShared(&state
->path
.lock
);
228 abs_path_copy(&dst_path
, &state
->path
);
229 ReleaseSRWLockShared(&state
->path
.lock
);
231 path_fh_init(&dst_dir
, &dst_path
);
232 fh_copy(&dst_dir
.fh
, &state
->parent
.fh
);
234 create_silly_rename(&dst_path
, &state
->file
.fh
, &dst_name
);
235 dprintf(1, "silly rename: %s -> %s\n", src_name
->name
, dst_name
.name
);
237 /* break any delegations and truncate before silly rename */
238 nfs41_delegation_return(state
->session
, &state
->file
,
239 OPEN_DELEGATE_WRITE
, TRUE
);
241 status
= nfs41_rename(state
->session
,
242 &state
->parent
, src_name
,
243 &dst_dir
, &dst_name
);
245 dprintf(1, "nfs41_rename() failed with error %s.\n",
246 nfs_error_string(status
));
247 status
= nfs_to_windows_error(status
, ERROR_ACCESS_DENIED
);
249 /* rename state->path on success */
250 open_state_rename(state
, &dst_path
);
255 dst_path
.len
= (unsigned short)WideCharToMultiByte(CP_UTF8
, 0,
256 rename
->FileName
, rename
->FileNameLength
/sizeof(WCHAR
),
257 dst_path
.path
, NFS41_MAX_PATH_LEN
, NULL
, NULL
);
258 if (dst_path
.len
== 0) {
259 eprintf("WideCharToMultiByte failed to convert destination "
260 "filename %S.\n", rename
->FileName
);
261 status
= ERROR_INVALID_PARAMETER
;
264 path_fh_init(&dst_dir
, &dst_path
);
266 /* the destination path is absolute, so start from the root session */
267 status
= nfs41_lookup(args
->root
, nfs41_root_session(args
->root
),
268 &dst_path
, &dst_dir
, &dst
, NULL
, &dst_session
);
270 while (status
== ERROR_REPARSE
) {
271 if (++depth
> NFS41_MAX_SYMLINK_DEPTH
) {
272 status
= ERROR_TOO_MANY_LINKS
;
276 /* replace the path with the symlink target's */
277 status
= nfs41_symlink_target(dst_session
, &dst_dir
, &dst_path
);
279 eprintf("nfs41_symlink_target() for %s failed with %d\n",
280 dst_dir
.path
->path
, status
);
284 /* redo the lookup until it doesn't return REPARSE */
285 status
= nfs41_lookup(args
->root
, dst_session
,
286 &dst_path
, &dst_dir
, NULL
, NULL
, &dst_session
);
289 /* get the components after lookup in case a referral changed its path */
290 last_component(dst_path
.path
, dst_path
.path
+ dst_path
.len
, &dst_name
);
291 last_component(dst_path
.path
, dst_name
.name
, &dst_dir
.name
);
293 if (status
== NO_ERROR
) {
294 if (!rename
->ReplaceIfExists
) {
295 status
= ERROR_FILE_EXISTS
;
298 /* break any delegations and truncate the destination file */
299 nfs41_delegation_return(dst_session
, &dst
,
300 OPEN_DELEGATE_WRITE
, TRUE
);
301 } else if (status
!= ERROR_FILE_NOT_FOUND
) {
302 dprintf(1, "nfs41_lookup('%s') failed to find destination "
303 "directory with %d\n", dst_path
.path
, status
);
307 /* http://tools.ietf.org/html/rfc5661#section-18.26.3
308 * "Source and target directories MUST reside on the same
309 * file system on the server." */
310 if (state
->parent
.fh
.superblock
!= dst_dir
.fh
.superblock
) {
311 status
= ERROR_NOT_SAME_DEVICE
;
315 status
= is_dst_name_opened(&dst_path
, dst_session
);
317 /* AGLO: 03/21/2011: we can't handle rename of a file with a filename
318 * that is currently opened by this client
320 eprintf("handle_nfs41_rename: %s is opened\n", dst_path
.path
);
321 status
= ERROR_FILE_EXISTS
;
325 /* break any delegations on the source file */
326 nfs41_delegation_return(state
->session
, &state
->file
,
327 OPEN_DELEGATE_WRITE
, FALSE
);
329 status
= nfs41_rename(state
->session
,
330 &state
->parent
, src_name
,
331 &dst_dir
, &dst_name
);
333 dprintf(1, "nfs41_rename() failed with error %s.\n",
334 nfs_error_string(status
));
335 status
= nfs_to_windows_error(status
, ERROR_ACCESS_DENIED
);
337 /* rename state->path on success */
338 open_state_rename(state
, &dst_path
);
344 static int handle_nfs41_set_size(setattr_upcall_args
*args
)
346 nfs41_file_info info
= { 0 };
348 /* note: this is called with either FILE_END_OF_FILE_INFO or
349 * FILE_ALLOCATION_INFO, both of which contain a single LARGE_INTEGER */
350 PLARGE_INTEGER size
= (PLARGE_INTEGER
)args
->buf
;
351 nfs41_open_state
*state
= args
->state
;
354 /* break read delegations before SETATTR */
355 nfs41_delegation_return(state
->session
, &state
->file
,
356 OPEN_DELEGATE_READ
, FALSE
);
358 nfs41_open_stateid_arg(state
, &stateid
);
360 info
.size
= size
->QuadPart
;
361 info
.attrmask
.count
= 1;
362 info
.attrmask
.arr
[0] = FATTR4_WORD0_SIZE
;
364 dprintf(2, "calling setattr() with size=%lld\n", info
.size
);
365 status
= nfs41_setattr(state
->session
, &state
->file
, &stateid
, &info
);
367 dprintf(1, "nfs41_setattr() failed with error %s.\n",
368 nfs_error_string(status
));
372 /* update the last offset for LAYOUTCOMMIT */
373 AcquireSRWLockExclusive(&state
->lock
);
374 state
->pnfs_last_offset
= info
.size
? info
.size
- 1 : 0;
375 ReleaseSRWLockExclusive(&state
->lock
);
376 args
->ctime
= info
.change
;
378 return status
= nfs_to_windows_error(status
, ERROR_NOT_SUPPORTED
);
381 static int handle_nfs41_link(setattr_upcall_args
*args
)
383 nfs41_open_state
*state
= args
->state
;
384 PFILE_LINK_INFORMATION link
= (PFILE_LINK_INFORMATION
)args
->buf
;
385 nfs41_session
*dst_session
;
386 nfs41_abs_path dst_path
= { 0 };
387 nfs41_path_fh dst_dir
, dst
;
388 nfs41_component dst_name
;
390 nfs41_file_info info
= { 0 };
393 dst_path
.len
= (unsigned short)WideCharToMultiByte(CP_UTF8
, 0,
394 link
->FileName
, link
->FileNameLength
/sizeof(WCHAR
),
395 dst_path
.path
, NFS41_MAX_PATH_LEN
, NULL
, NULL
);
396 if (dst_path
.len
== 0) {
397 eprintf("WideCharToMultiByte failed to convert destination "
398 "filename %S.\n", link
->FileName
);
399 status
= ERROR_INVALID_PARAMETER
;
402 path_fh_init(&dst_dir
, &dst_path
);
404 /* the destination path is absolute, so start from the root session */
405 status
= nfs41_lookup(args
->root
, nfs41_root_session(args
->root
),
406 &dst_path
, &dst_dir
, &dst
, NULL
, &dst_session
);
408 while (status
== ERROR_REPARSE
) {
409 if (++depth
> NFS41_MAX_SYMLINK_DEPTH
) {
410 status
= ERROR_TOO_MANY_LINKS
;
414 /* replace the path with the symlink target's */
415 status
= nfs41_symlink_target(dst_session
, &dst_dir
, &dst_path
);
417 eprintf("nfs41_symlink_target() for %s failed with %d\n",
418 dst_dir
.path
->path
, status
);
422 /* redo the lookup until it doesn't return REPARSE */
423 status
= nfs41_lookup(args
->root
, dst_session
,
424 &dst_path
, &dst_dir
, &dst
, NULL
, &dst_session
);
427 /* get the components after lookup in case a referral changed its path */
428 last_component(dst_path
.path
, dst_path
.path
+ dst_path
.len
, &dst_name
);
429 last_component(dst_path
.path
, dst_name
.name
, &dst_dir
.name
);
431 if (status
== NO_ERROR
) {
432 if (!link
->ReplaceIfExists
) {
433 status
= ERROR_FILE_EXISTS
;
436 } else if (status
!= ERROR_FILE_NOT_FOUND
) {
437 dprintf(1, "nfs41_lookup('%s') failed to find destination "
438 "directory with %d\n", dst_path
.path
, status
);
442 /* http://tools.ietf.org/html/rfc5661#section-18.9.3
443 * "The existing file and the target directory must reside within
444 * the same file system on the server." */
445 if (state
->file
.fh
.superblock
!= dst_dir
.fh
.superblock
) {
446 status
= ERROR_NOT_SAME_DEVICE
;
450 if (status
== NO_ERROR
) {
451 /* break any delegations and truncate the destination file */
452 nfs41_delegation_return(dst_session
, &dst
,
453 OPEN_DELEGATE_WRITE
, TRUE
);
455 /* LINK will return NFS4ERR_EXIST if the target file exists,
456 * so we have to remove it ourselves */
457 status
= nfs41_remove(state
->session
,
458 &dst_dir
, &dst_name
, dst
.fh
.fileid
);
460 dprintf(1, "nfs41_remove() failed with error %s.\n",
461 nfs_error_string(status
));
462 status
= ERROR_FILE_EXISTS
;
467 /* break read delegations on the source file */
468 nfs41_delegation_return(state
->session
, &state
->file
,
469 OPEN_DELEGATE_READ
, FALSE
);
471 status
= nfs41_link(state
->session
, &state
->file
, &dst_dir
, &dst_name
,
474 dprintf(1, "nfs41_link() failed with error %s.\n",
475 nfs_error_string(status
));
476 status
= nfs_to_windows_error(status
, ERROR_INVALID_PARAMETER
);
478 args
->ctime
= info
.change
;
483 static int handle_setattr(nfs41_upcall
*upcall
)
485 setattr_upcall_args
*args
= &upcall
->args
.setattr
;
488 switch (args
->set_class
) {
489 case FileBasicInformation
:
490 status
= handle_nfs41_setattr(args
);
492 case FileDispositionInformation
:
493 status
= handle_nfs41_remove(args
);
495 case FileRenameInformation
:
496 status
= handle_nfs41_rename(args
);
498 case FileAllocationInformation
:
499 case FileEndOfFileInformation
:
500 status
= handle_nfs41_set_size(args
);
502 case FileLinkInformation
:
503 status
= handle_nfs41_link(args
);
506 eprintf("unknown set_file information class %d\n",
508 status
= ERROR_NOT_SUPPORTED
;
515 static int marshall_setattr(unsigned char *buffer
, uint32_t *length
, nfs41_upcall
*upcall
)
517 setattr_upcall_args
*args
= &upcall
->args
.setattr
;
518 return safe_write(&buffer
, length
, &args
->ctime
, sizeof(args
->ctime
));
522 const nfs41_upcall_op nfs41_op_setattr
= {