1 /* NFSv4.1 client for Windows 2 * Copyright � 2012 The Regents of the University of Michigan 3 * 4 * Olga Kornievskaia <aglo@umich.edu> 5 * Casey Bodley <cbodley@umich.edu> 6 * 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. 11 * 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. 16 * 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 20 */ 21 22 #include <windows.h> 23 #include <stdio.h> 24 #include <strsafe.h> 25 26 #include "from_kernel.h" 27 #include "nfs41_ops.h" 28 #include "delegation.h" 29 #include "name_cache.h" 30 #include "upcall.h" 31 #include "util.h" 32 #include "daemon_debug.h" 33 34 35 /* NFS41_FILE_SET */ 36 static int parse_setattr(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall) 37 { 38 int status; 39 setattr_upcall_args *args = &upcall->args.setattr; 40 41 status = get_name(&buffer, &length, &args->path); 42 if (status) goto out; 43 status = safe_read(&buffer, &length, &args->set_class, sizeof(args->set_class)); 44 if (status) goto out; 45 status = safe_read(&buffer, &length, &args->buf_len, sizeof(args->buf_len)); 46 if (status) goto out; 47 48 args->buf = buffer; 49 args->root = upcall->root_ref; 50 args->state = upcall->state_ref; 51 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); 54 out: 55 return status; 56 } 57 58 static int handle_nfs41_setattr(setattr_upcall_args *args) 59 { 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; 63 stateid_arg stateid; 64 nfs41_file_info info = { 0 }, old_info = { 0 }; 65 int status = NO_ERROR, getattr_status; 66 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); 73 74 if (getattr_status || info.hidden != old_info.hidden) { 75 info.attrmask.arr[0] = FATTR4_WORD0_HIDDEN; 76 info.attrmask.count = 1; 77 } 78 if (getattr_status || info.archive != old_info.archive) { 79 info.attrmask.arr[0] |= FATTR4_WORD0_ARCHIVE; 80 info.attrmask.count = 1; 81 } 82 if (getattr_status || info.system != old_info.system) { 83 info.attrmask.arr[1] = FATTR4_WORD1_SYSTEM; 84 info.attrmask.count = 2; 85 } 86 } 87 if (old_info.mode == 0444 && 88 ((basic_info->FileAttributes & FILE_ATTRIBUTE_READONLY) == 0)) { 89 info.mode = 0644; 90 info.attrmask.arr[1] |= FATTR4_WORD1_MODE; 91 info.attrmask.count = 2; 92 } 93 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; 98 99 /* time_create */ 100 if (basic_info->CreationTime.QuadPart > 0) { 101 file_time_to_nfs_time(&basic_info->CreationTime, 102 &info.time_create); 103 info.attrmask.arr[1] |= FATTR4_WORD1_TIME_CREATE; 104 info.attrmask.count = 2; 105 } 106 /* time_access_set */ 107 if (basic_info->LastAccessTime.QuadPart > 0) { 108 file_time_to_nfs_time(&basic_info->LastAccessTime, 109 &info.time_access); 110 info.attrmask.arr[1] |= FATTR4_WORD1_TIME_ACCESS_SET; 111 info.attrmask.count = 2; 112 } 113 /* time_modify_set */ 114 if (basic_info->LastWriteTime.QuadPart > 0) { 115 file_time_to_nfs_time(&basic_info->LastWriteTime, 116 &info.time_modify); 117 info.attrmask.arr[1] |= FATTR4_WORD1_TIME_MODIFY_SET; 118 info.attrmask.count = 2; 119 } 120 } 121 122 /* mode */ 123 if (basic_info->FileAttributes & FILE_ATTRIBUTE_READONLY) { 124 info.mode = 0444; 125 info.attrmask.arr[1] |= FATTR4_WORD1_MODE; 126 info.attrmask.count = 2; 127 } 128 129 /* mask out unsupported attributes */ 130 nfs41_superblock_supported_attrs(superblock, &info.attrmask); 131 132 if (!info.attrmask.count) 133 goto out; 134 135 /* break read delegations before SETATTR */ 136 nfs41_delegation_return(state->session, &state->file, 137 OPEN_DELEGATE_READ, FALSE); 138 139 nfs41_open_stateid_arg(state, &stateid); 140 141 status = nfs41_setattr(state->session, &state->file, &stateid, &info); 142 if (status) { 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); 146 } 147 args->ctime = info.change; 148 out: 149 return status; 150 } 151 152 static int handle_nfs41_remove(setattr_upcall_args *args) 153 { 154 nfs41_open_state *state = args->state; 155 int status; 156 157 /* break any delegations and truncate before REMOVE */ 158 nfs41_delegation_return(state->session, &state->file, 159 OPEN_DELEGATE_WRITE, TRUE); 160 161 status = nfs41_remove(state->session, &state->parent, 162 &state->file.name, state->file.fh.fileid); 163 if (status) 164 dprintf(1, "nfs41_remove() failed with error %s.\n", 165 nfs_error_string(status)); 166 167 return nfs_to_windows_error(status, ERROR_ACCESS_DENIED); 168 } 169 170 static void open_state_rename( 171 OUT nfs41_open_state *state, 172 IN const nfs41_abs_path *path) 173 { 174 AcquireSRWLockExclusive(&state->path.lock); 175 176 abs_path_copy(&state->path, path); 177 last_component(state->path.path, state->path.path + state->path.len, 178 &state->file.name); 179 last_component(state->path.path, state->file.name.name, 180 &state->parent.name); 181 182 ReleaseSRWLockExclusive(&state->path.lock); 183 } 184 185 static int nfs41_abs_path_compare( 186 IN const struct list_entry *entry, 187 IN const void *value) 188 { 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)) 193 return NO_ERROR; 194 return ERROR_FILE_NOT_FOUND; 195 } 196 197 static int is_dst_name_opened(nfs41_abs_path *dst_path, nfs41_session *dst_session) 198 { 199 int status; 200 nfs41_client *client = dst_session->client; 201 202 EnterCriticalSection(&client->state.lock); 203 if (list_search(&client->state.opens, dst_path, nfs41_abs_path_compare)) 204 status = TRUE; 205 else 206 status = FALSE; 207 LeaveCriticalSection(&client->state.lock); 208 209 return status; 210 } 211 static int handle_nfs41_rename(setattr_upcall_args *args) 212 { 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; 219 uint32_t depth = 0; 220 int status; 221 222 src_name = &state->file.name; 223 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); 230 231 path_fh_init(&dst_dir, &dst_path); 232 fh_copy(&dst_dir.fh, &state->parent.fh); 233 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); 236 237 /* break any delegations and truncate before silly rename */ 238 nfs41_delegation_return(state->session, &state->file, 239 OPEN_DELEGATE_WRITE, TRUE); 240 241 status = nfs41_rename(state->session, 242 &state->parent, src_name, 243 &dst_dir, &dst_name); 244 if (status) { 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); 248 } else { 249 /* rename state->path on success */ 250 open_state_rename(state, &dst_path); 251 } 252 goto out; 253 } 254 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; 262 goto out; 263 } 264 path_fh_init(&dst_dir, &dst_path); 265 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); 269 270 while (status == ERROR_REPARSE) { 271 if (++depth > NFS41_MAX_SYMLINK_DEPTH) { 272 status = ERROR_TOO_MANY_LINKS; 273 goto out; 274 } 275 276 /* replace the path with the symlink target's */ 277 status = nfs41_symlink_target(dst_session, &dst_dir, &dst_path); 278 if (status) { 279 eprintf("nfs41_symlink_target() for %s failed with %d\n", 280 dst_dir.path->path, status); 281 goto out; 282 } 283 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); 287 } 288 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); 292 293 if (status == NO_ERROR) { 294 if (!rename->ReplaceIfExists) { 295 status = ERROR_FILE_EXISTS; 296 goto out; 297 } 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); 304 goto out; 305 } 306 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; 312 goto out; 313 } 314 315 status = is_dst_name_opened(&dst_path, dst_session); 316 if (status) { 317 /* AGLO: 03/21/2011: we can't handle rename of a file with a filename 318 * that is currently opened by this client 319 */ 320 eprintf("handle_nfs41_rename: %s is opened\n", dst_path.path); 321 status = ERROR_FILE_EXISTS; 322 goto out; 323 } 324 325 /* break any delegations on the source file */ 326 nfs41_delegation_return(state->session, &state->file, 327 OPEN_DELEGATE_WRITE, FALSE); 328 329 status = nfs41_rename(state->session, 330 &state->parent, src_name, 331 &dst_dir, &dst_name); 332 if (status) { 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); 336 } else { 337 /* rename state->path on success */ 338 open_state_rename(state, &dst_path); 339 } 340 out: 341 return status; 342 } 343 344 static int handle_nfs41_set_size(setattr_upcall_args *args) 345 { 346 nfs41_file_info info = { 0 }; 347 stateid_arg stateid; 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; 352 int status; 353 354 /* break read delegations before SETATTR */ 355 nfs41_delegation_return(state->session, &state->file, 356 OPEN_DELEGATE_READ, FALSE); 357 358 nfs41_open_stateid_arg(state, &stateid); 359 360 info.size = size->QuadPart; 361 info.attrmask.count = 1; 362 info.attrmask.arr[0] = FATTR4_WORD0_SIZE; 363 364 dprintf(2, "calling setattr() with size=%lld\n", info.size); 365 status = nfs41_setattr(state->session, &state->file, &stateid, &info); 366 if (status) { 367 dprintf(1, "nfs41_setattr() failed with error %s.\n", 368 nfs_error_string(status)); 369 goto out; 370 } 371 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; 377 out: 378 return status = nfs_to_windows_error(status, ERROR_NOT_SUPPORTED); 379 } 380 381 static int handle_nfs41_link(setattr_upcall_args *args) 382 { 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; 389 uint32_t depth = 0; 390 nfs41_file_info info = { 0 }; 391 int status; 392 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; 400 goto out; 401 } 402 path_fh_init(&dst_dir, &dst_path); 403 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); 407 408 while (status == ERROR_REPARSE) { 409 if (++depth > NFS41_MAX_SYMLINK_DEPTH) { 410 status = ERROR_TOO_MANY_LINKS; 411 goto out; 412 } 413 414 /* replace the path with the symlink target's */ 415 status = nfs41_symlink_target(dst_session, &dst_dir, &dst_path); 416 if (status) { 417 eprintf("nfs41_symlink_target() for %s failed with %d\n", 418 dst_dir.path->path, status); 419 goto out; 420 } 421 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); 425 } 426 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); 430 431 if (status == NO_ERROR) { 432 if (!link->ReplaceIfExists) { 433 status = ERROR_FILE_EXISTS; 434 goto out; 435 } 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); 439 goto out; 440 } 441 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; 447 goto out; 448 } 449 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); 454 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); 459 if (status) { 460 dprintf(1, "nfs41_remove() failed with error %s.\n", 461 nfs_error_string(status)); 462 status = ERROR_FILE_EXISTS; 463 goto out; 464 } 465 } 466 467 /* break read delegations on the source file */ 468 nfs41_delegation_return(state->session, &state->file, 469 OPEN_DELEGATE_READ, FALSE); 470 471 status = nfs41_link(state->session, &state->file, &dst_dir, &dst_name, 472 &info); 473 if (status) { 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); 477 } 478 args->ctime = info.change; 479 out: 480 return status; 481 } 482 483 static int handle_setattr(nfs41_upcall *upcall) 484 { 485 setattr_upcall_args *args = &upcall->args.setattr; 486 int status; 487 488 switch (args->set_class) { 489 case FileBasicInformation: 490 status = handle_nfs41_setattr(args); 491 break; 492 case FileDispositionInformation: 493 status = handle_nfs41_remove(args); 494 break; 495 case FileRenameInformation: 496 status = handle_nfs41_rename(args); 497 break; 498 case FileAllocationInformation: 499 case FileEndOfFileInformation: 500 status = handle_nfs41_set_size(args); 501 break; 502 case FileLinkInformation: 503 status = handle_nfs41_link(args); 504 break; 505 default: 506 eprintf("unknown set_file information class %d\n", 507 args->set_class); 508 status = ERROR_NOT_SUPPORTED; 509 break; 510 } 511 512 return status; 513 } 514 515 static int marshall_setattr(unsigned char *buffer, uint32_t *length, nfs41_upcall *upcall) 516 { 517 setattr_upcall_args *args = &upcall->args.setattr; 518 return safe_write(&buffer, length, &args->ctime, sizeof(args->ctime)); 519 } 520 521 522 const nfs41_upcall_op nfs41_op_setattr = { 523 parse_setattr, 524 handle_setattr, 525 marshall_setattr 526 }; 527