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 <strsafe.h> 24 #include <time.h> 25 26 #include "nfs41_compound.h" 27 #include "nfs41_ops.h" 28 #include "name_cache.h" 29 #include "util.h" 30 #include "daemon_debug.h" 31 32 33 #define LULVL 2 /* dprintf level for lookup logging */ 34 35 36 #define MAX_LOOKUP_COMPONENTS 8 37 38 /* map NFS4ERR_MOVED to an arbitrary windows error */ 39 #define ERROR_FILESYSTEM_ABSENT ERROR_DEVICE_REMOVED 40 41 struct lookup_referral { 42 nfs41_path_fh parent; 43 nfs41_component name; 44 }; 45 46 47 typedef struct __nfs41_lookup_component_args { 48 nfs41_sequence_args sequence; 49 nfs41_putfh_args putfh; 50 nfs41_lookup_args lookup[MAX_LOOKUP_COMPONENTS]; 51 nfs41_getattr_args getrootattr; 52 nfs41_getattr_args getattr[MAX_LOOKUP_COMPONENTS]; 53 bitmap4 attr_request; 54 } nfs41_lookup_component_args; 55 56 typedef struct __nfs41_lookup_component_res { 57 nfs41_sequence_res sequence; 58 nfs41_putfh_res putfh; 59 nfs41_lookup_res lookup[MAX_LOOKUP_COMPONENTS]; 60 nfs41_getfh_res getrootfh; 61 nfs41_getfh_res getfh[MAX_LOOKUP_COMPONENTS]; 62 nfs41_path_fh root; 63 nfs41_path_fh file[MAX_LOOKUP_COMPONENTS]; 64 nfs41_getattr_res getrootattr; 65 nfs41_getattr_res getattr[MAX_LOOKUP_COMPONENTS]; 66 nfs41_file_info rootinfo; 67 nfs41_file_info info[MAX_LOOKUP_COMPONENTS]; 68 struct lookup_referral *referral; 69 } nfs41_lookup_component_res; 70 71 72 static void init_component_args( 73 IN nfs41_lookup_component_args *args, 74 IN nfs41_lookup_component_res *res, 75 IN nfs41_abs_path *path, 76 IN struct lookup_referral *referral) 77 { 78 uint32_t i; 79 80 args->attr_request.count = 2; 81 args->attr_request.arr[0] = FATTR4_WORD0_TYPE 82 | FATTR4_WORD0_CHANGE | FATTR4_WORD0_SIZE 83 | FATTR4_WORD0_FSID | FATTR4_WORD0_FILEID 84 | FATTR4_WORD0_HIDDEN | FATTR4_WORD0_ARCHIVE; 85 args->attr_request.arr[1] = FATTR4_WORD1_MODE 86 | FATTR4_WORD1_NUMLINKS | FATTR4_WORD1_SYSTEM 87 | FATTR4_WORD1_TIME_ACCESS | FATTR4_WORD1_TIME_CREATE 88 | FATTR4_WORD1_TIME_MODIFY; 89 90 args->getrootattr.attr_request = &args->attr_request; 91 res->root.path = path; 92 res->getrootfh.fh = &res->root.fh; 93 res->getrootattr.info = &res->rootinfo; 94 res->getrootattr.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT; 95 res->referral = referral; 96 97 for (i = 0; i < MAX_LOOKUP_COMPONENTS; i++) { 98 args->getattr[i].attr_request = &args->attr_request; 99 res->file[i].path = path; 100 args->lookup[i].name = &res->file[i].name; 101 res->getfh[i].fh = &res->file[i].fh; 102 res->getattr[i].info = &res->info[i]; 103 res->getattr[i].obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT; 104 } 105 } 106 107 static int lookup_rpc( 108 IN nfs41_session *session, 109 IN nfs41_path_fh *dir, 110 IN uint32_t component_count, 111 IN nfs41_lookup_component_args *args, 112 OUT nfs41_lookup_component_res *res) 113 { 114 int status; 115 uint32_t i; 116 nfs41_compound compound; 117 nfs_argop4 argops[4+MAX_LOOKUP_COMPONENTS*3]; 118 nfs_resop4 resops[4+MAX_LOOKUP_COMPONENTS*3]; 119 120 compound_init(&compound, argops, resops, "lookup"); 121 122 compound_add_op(&compound, OP_SEQUENCE, &args->sequence, &res->sequence); 123 nfs41_session_sequence(&args->sequence, session, 0); 124 125 if (dir == &res->root) { 126 compound_add_op(&compound, OP_PUTROOTFH, NULL, &res->putfh); 127 compound_add_op(&compound, OP_GETFH, NULL, &res->getrootfh); 128 compound_add_op(&compound, OP_GETATTR, &args->getrootattr, 129 &res->getrootattr); 130 } else { 131 args->putfh.file = dir; 132 compound_add_op(&compound, OP_PUTFH, &args->putfh, &res->putfh); 133 } 134 135 for (i = 0; i < component_count; i++) { 136 compound_add_op(&compound, OP_LOOKUP, &args->lookup[i], &res->lookup[i]); 137 compound_add_op(&compound, OP_GETFH, NULL, &res->getfh[i]); 138 compound_add_op(&compound, OP_GETATTR, &args->getattr[i], 139 &res->getattr[i]); 140 } 141 142 status = compound_encode_send_decode(session, &compound, TRUE); 143 if (status) 144 goto out; 145 146 compound_error(status = compound.res.status); 147 out: 148 return status; 149 } 150 151 static int map_lookup_error(int status, bool_t last_component) 152 { 153 switch (status) { 154 case NFS4ERR_NOENT: 155 if (last_component) return ERROR_FILE_NOT_FOUND; 156 else return ERROR_PATH_NOT_FOUND; 157 case NFS4ERR_SYMLINK: return ERROR_REPARSE; 158 case NFS4ERR_MOVED: return ERROR_FILESYSTEM_ABSENT; 159 default: return nfs_to_windows_error(status, ERROR_FILE_NOT_FOUND); 160 } 161 } 162 163 static int server_lookup( 164 IN nfs41_session *session, 165 IN nfs41_path_fh *dir, 166 IN const char *path, 167 IN const char *path_end, 168 IN uint32_t count, 169 IN nfs41_lookup_component_args *args, 170 IN nfs41_lookup_component_res *res, 171 OUT OPTIONAL nfs41_path_fh **parent_out, 172 OUT OPTIONAL nfs41_path_fh **target_out, 173 OUT OPTIONAL nfs41_file_info *info_out) 174 { 175 nfs41_path_fh *file, *parent; 176 uint32_t i = 0; 177 int status; 178 179 if (parent_out) *parent_out = NULL; 180 if (target_out) *target_out = NULL; 181 182 lookup_rpc(session, dir, count, args, res); 183 184 status = res->sequence.sr_status; if (status) goto out; 185 status = res->putfh.status; if (status) goto out; 186 status = res->getrootfh.status; if (status) goto out; 187 status = res->getrootattr.status; if (status) goto out; 188 189 if (dir == &res->root) { 190 nfs41_component name = { 0 }; 191 192 /* fill in the file handle's fileid and superblock */ 193 dir->fh.fileid = res->getrootattr.info->fileid; 194 status = nfs41_superblock_for_fh(session, 195 &res->getrootattr.info->fsid, NULL, dir); 196 if (status) 197 goto out; 198 199 /* get the name of the parent (empty if its the root) */ 200 last_component(path, count ? args->lookup[0].name->name : path_end, &name); 201 202 /* add the file handle and attributes to the name cache */ 203 memcpy(&res->getrootattr.info->attrmask, 204 &res->getrootattr.obj_attributes.attrmask, sizeof(bitmap4)); 205 nfs41_name_cache_insert(session_name_cache(session), path, &name, 206 &dir->fh, res->getrootattr.info, NULL, OPEN_DELEGATE_NONE); 207 } 208 file = dir; 209 210 if (count == 0) { 211 if (target_out) 212 *target_out = dir; 213 if (info_out) 214 memcpy(info_out, res->getrootattr.info, sizeof(nfs41_file_info)); 215 } else if (count == 1) { 216 if (parent_out) 217 *parent_out = dir; 218 } 219 220 for (i = 0; i < count; i++) { 221 if (res->lookup[i].status == NFS4ERR_SYMLINK) { 222 /* return the symlink as the parent file */ 223 last_component(path, args->lookup[i].name->name, &file->name); 224 if (parent_out) *parent_out = file; 225 } else if (res->lookup[i].status == NFS4ERR_NOENT) { 226 /* insert a negative lookup entry */ 227 nfs41_name_cache_insert(session_name_cache(session), path, 228 args->lookup[i].name, NULL, NULL, NULL, OPEN_DELEGATE_NONE); 229 } 230 status = res->lookup[i].status; if (status) break; 231 232 if (res->getfh[i].status == NFS4ERR_MOVED) { 233 /* save enough information to follow the referral */ 234 path_fh_copy(&res->referral->parent, file); 235 res->referral->name.name = args->lookup[i].name->name; 236 res->referral->name.len = args->lookup[i].name->len; 237 } 238 status = res->getfh[i].status; if (status) break; 239 status = res->getattr[i].status; if (status) break; 240 241 parent = file; 242 file = &res->file[i]; 243 244 /* fill in the file handle's fileid and superblock */ 245 file->fh.fileid = res->getattr[i].info->fileid; 246 status = nfs41_superblock_for_fh(session, 247 &res->getattr[i].info->fsid, &parent->fh, file); 248 if (status) 249 break; 250 251 /* add the file handle and attributes to the name cache */ 252 memcpy(&res->getattr[i].info->attrmask, 253 &res->getattr[i].obj_attributes.attrmask, sizeof(bitmap4)); 254 nfs41_name_cache_insert(session_name_cache(session), 255 path, args->lookup[i].name, &res->file[i].fh, 256 res->getattr[i].info, NULL, OPEN_DELEGATE_NONE); 257 258 if (i == count-1) { 259 if (target_out) 260 *target_out = file; 261 if (info_out) 262 memcpy(info_out, res->getattr[i].info, sizeof(nfs41_file_info)); 263 } else if (i == count-2) { 264 if (parent_out) 265 *parent_out = file; 266 } 267 } 268 out: 269 return map_lookup_error(status, i == count-1); 270 } 271 272 static uint32_t max_lookup_components( 273 IN const nfs41_session *session) 274 { 275 const uint32_t comps = (session->fore_chan_attrs.ca_maxoperations - 4) / 3; 276 return min(comps, MAX_LOOKUP_COMPONENTS); 277 } 278 279 static uint32_t get_component_array( 280 IN OUT const char **path_pos, 281 IN const char *path_end, 282 IN uint32_t max_components, 283 OUT nfs41_path_fh *components, 284 OUT uint32_t *component_count) 285 { 286 uint32_t i; 287 288 for (i = 0; i < max_components; i++) { 289 if (!next_component(*path_pos, path_end, &components[i].name)) 290 break; 291 *path_pos = components[i].name.name + components[i].name.len; 292 } 293 294 *component_count = i; 295 return i; 296 } 297 298 static int server_lookup_loop( 299 IN nfs41_session *session, 300 IN OPTIONAL nfs41_path_fh *parent_in, 301 IN nfs41_abs_path *path, 302 IN const char *path_pos, 303 IN struct lookup_referral *referral, 304 OUT OPTIONAL nfs41_path_fh *parent_out, 305 OUT OPTIONAL nfs41_path_fh *target_out, 306 OUT OPTIONAL nfs41_file_info *info_out) 307 { 308 nfs41_lookup_component_args args = { 0 }; 309 nfs41_lookup_component_res res = { 0 }; 310 nfs41_path_fh *dir, *parent, *target; 311 const char *path_end; 312 const uint32_t max_components = max_lookup_components(session); 313 uint32_t count; 314 int status = NO_ERROR; 315 316 init_component_args(&args, &res, path, referral); 317 parent = NULL; 318 target = NULL; 319 320 path_end = path->path + path->len; 321 dir = parent_in ? parent_in : &res.root; 322 323 while (get_component_array(&path_pos, path_end, 324 max_components, res.file, &count)) { 325 326 status = server_lookup(session, dir, path->path, path_end, count, 327 &args, &res, &parent, &target, info_out); 328 329 if (status == ERROR_REPARSE) { 330 /* copy the component name of the symlink */ 331 if (parent_out && parent) { 332 const ptrdiff_t offset = parent->name.name - path->path; 333 parent_out->name.name = parent_out->path->path + offset; 334 parent_out->name.len = parent->name.len; 335 } 336 goto out_parent; 337 } 338 if (status == ERROR_FILE_NOT_FOUND && is_last_component(path_pos, path_end)) 339 goto out_parent; 340 if (status) 341 goto out; 342 343 dir = target; 344 } 345 346 if (dir == &res.root && (target_out || info_out)) { 347 /* didn't get any components, so we just need the root */ 348 status = server_lookup(session, dir, path->path, path_end, 349 0, &args, &res, &parent, &target, info_out); 350 if (status) 351 goto out; 352 } 353 354 if (target_out && target) fh_copy(&target_out->fh, &target->fh); 355 out_parent: 356 if (parent_out && parent) fh_copy(&parent_out->fh, &parent->fh); 357 out: 358 return status; 359 } 360 361 362 static void referral_locations_free( 363 IN fs_locations4 *locations) 364 { 365 uint32_t i; 366 if (locations->locations) { 367 for (i = 0; i < locations->location_count; i++) 368 free(locations->locations[i].servers); 369 free(locations->locations); 370 } 371 } 372 373 static int referral_resolve( 374 IN nfs41_root *root, 375 IN nfs41_session *session_in, 376 IN struct lookup_referral *referral, 377 OUT nfs41_abs_path *path_out, 378 OUT nfs41_session **session_out) 379 { 380 char rest_of_path[NFS41_MAX_PATH_LEN]; 381 fs_locations4 locations = { 0 }; 382 const fs_location4 *location; 383 nfs41_client *client; 384 int status; 385 386 /* get fs_locations */ 387 status = nfs41_fs_locations(session_in, &referral->parent, 388 &referral->name, &locations); 389 if (status) { 390 eprintf("nfs41_fs_locations() failed with %s\n", 391 nfs_error_string(status)); 392 status = nfs_to_windows_error(status, ERROR_PATH_NOT_FOUND); 393 goto out; 394 } 395 396 /* mount the first location available */ 397 status = nfs41_root_mount_referral(root, &locations, &location, &client); 398 if (status) { 399 eprintf("nfs41_root_mount_referral() failed with %d\n", 400 status); 401 goto out; 402 } 403 404 /* format a new path from that location's root */ 405 if (FAILED(StringCchCopyA(rest_of_path, NFS41_MAX_PATH_LEN, 406 referral->name.name + referral->name.len))) { 407 status = ERROR_FILENAME_EXCED_RANGE; 408 goto out; 409 } 410 411 AcquireSRWLockExclusive(&path_out->lock); 412 abs_path_copy(path_out, &location->path); 413 if (FAILED(StringCchCatA(path_out->path, NFS41_MAX_PATH_LEN, rest_of_path))) 414 status = ERROR_FILENAME_EXCED_RANGE; 415 path_out->len = path_out->len + (unsigned short)strlen(rest_of_path); 416 ReleaseSRWLockExclusive(&path_out->lock); 417 418 if (session_out) *session_out = client->session; 419 out: 420 referral_locations_free(&locations); 421 return status; 422 } 423 424 int nfs41_lookup( 425 IN nfs41_root *root, 426 IN nfs41_session *session, 427 IN OUT nfs41_abs_path *path_inout, 428 OUT OPTIONAL nfs41_path_fh *parent_out, 429 OUT OPTIONAL nfs41_path_fh *target_out, 430 OUT OPTIONAL nfs41_file_info *info_out, 431 OUT nfs41_session **session_out) 432 { 433 nfs41_abs_path path; 434 struct nfs41_name_cache *cache = session_name_cache(session); 435 nfs41_path_fh parent, target, *server_start; 436 const char *path_pos, *path_end; 437 struct lookup_referral referral; 438 bool_t negative = 0; 439 int status; 440 441 if (session_out) *session_out = session; 442 443 InitializeSRWLock(&path.lock); 444 445 /* to avoid holding this lock over multiple rpcs, 446 * make a copy of the path and use that instead */ 447 AcquireSRWLockShared(&path_inout->lock); 448 abs_path_copy(&path, path_inout); 449 ReleaseSRWLockShared(&path_inout->lock); 450 451 path_pos = path.path; 452 path_end = path.path + path.len; 453 454 dprintf(LULVL, "--> nfs41_lookup('%s')\n", path.path); 455 456 if (parent_out == NULL) parent_out = &parent; 457 if (target_out == NULL) target_out = ⌖ 458 parent_out->fh.len = target_out->fh.len = 0; 459 460 status = nfs41_name_cache_lookup(cache, path_pos, path_end, &path_pos, 461 &parent_out->fh, &target_out->fh, info_out, &negative); 462 if (status == NO_ERROR || negative) 463 goto out; 464 465 if (parent_out->fh.len) { 466 /* start where the name cache left off */ 467 if (&parent != parent_out) { 468 /* must make a copy for server_start, because 469 * server_lookup_loop() will overwrite parent_out */ 470 path_fh_copy(&parent, parent_out); 471 } 472 server_start = &parent; 473 } else { 474 /* start with PUTROOTFH */ 475 server_start = NULL; 476 } 477 478 status = server_lookup_loop(session, server_start, 479 &path, path_pos, &referral, parent_out, target_out, info_out); 480 481 if (status == ERROR_FILESYSTEM_ABSENT) { 482 nfs41_session *new_session; 483 484 /* create a session to the referred server and 485 * reformat the path relative to that server's root */ 486 status = referral_resolve(root, session, 487 &referral, path_inout, &new_session); 488 if (status) { 489 eprintf("referral_resolve() failed with %d\n", status); 490 goto out; 491 } 492 493 /* update the positions of the parent and target components */ 494 last_component(path_inout->path, path_inout->path + path_inout->len, 495 &target_out->name); 496 last_component(path_inout->path, target_out->name.name, 497 &parent_out->name); 498 499 if (session_out) *session_out = new_session; 500 501 /* look up the new path */ 502 status = nfs41_lookup(root, new_session, path_inout, 503 parent_out, target_out, info_out, session_out); 504 } 505 out: 506 dprintf(LULVL, "<-- nfs41_lookup() returning %d\n", status); 507 return status; 508 } 509