From: Pierre Schweitzer Date: Mon, 19 Jun 2017 07:57:04 +0000 (+0000) Subject: [NFSD] X-Git-Tag: ReactOS-0.4.6~243 X-Git-Url: https://git.reactos.org/?p=reactos.git;a=commitdiff_plain;h=f445db2c5c4cf34b24d9e57fad078f6b0299b34b [NFSD] Import the nfsd deamon from the nfs41 project. CORE-8204 svn path=/trunk/; revision=75114 --- diff --git a/reactos/base/services/CMakeLists.txt b/reactos/base/services/CMakeLists.txt index 41557e9ad63..e49a3401993 100644 --- a/reactos/base/services/CMakeLists.txt +++ b/reactos/base/services/CMakeLists.txt @@ -2,6 +2,7 @@ add_subdirectory(audiosrv) add_subdirectory(dhcpcsvc) add_subdirectory(eventlog) +add_subdirectory(nfsd) add_subdirectory(rpcss) add_subdirectory(schedsvc) add_subdirectory(shsvcs) diff --git a/reactos/base/services/nfsd/CMakeLists.txt b/reactos/base/services/nfsd/CMakeLists.txt new file mode 100644 index 00000000000..d7f3df353d3 --- /dev/null +++ b/reactos/base/services/nfsd/CMakeLists.txt @@ -0,0 +1,61 @@ +remove_definitions(-D_WIN32_WINNT=0x502) +add_definitions(-D_WIN32_WINNT=0x600) +add_definitions(-DNTDDI_VERSION=0x06010000) + +include_directories( + ${REACTOS_SOURCE_DIR}/dll/3rdparty/libtirpc/tirpc + ${REACTOS_SOURCE_DIR}/drivers/filesystems/nfs + ${REACTOS_SOURCE_DIR}/dll/np/nfs) + +list(APPEND SOURCE + nfs41_daemon.c + daemon_debug.c + nfs41_ops.c + nfs41_compound.c + nfs41_xdr.c + nfs41_server.c + nfs41_client.c + nfs41_superblock.c + nfs41_session.c + lookup.c + mount.c + open.c + readwrite.c + lock.c + readdir.c + getattr.c + setattr.c + upcall.c + nfs41_rpc.c + util.c + pnfs_layout.c + pnfs_device.c + pnfs_debug.c + pnfs_io.c + name_cache.c + namespace.c + volume.c + callback_server.c + callback_xdr.c + service.c + symlink.c + idmap.c + delegation.c + recovery.c + acl.c + ea.c) + +add_executable(nfsd ${SOURCE} nfsd.rc) + +if(MSVC) +else() + # FIXME: Tons of warnings. + replace_compile_flags("-Werror" " ") +endif() + +set_module_type(nfsd win32cui) +target_link_libraries(nfsd wldap32) +add_importlibs(nfsd advapi32 iphlpapi kernel32 kernel32_vista tirpc ntdll msvcrt shell32 ws2_32) +add_cd_file(TARGET nfsd DESTINATION reactos/system32 FOR all) +add_cd_file(FILE "${CMAKE_CURRENT_SOURCE_DIR}/netconfig" DESTINATION reactos/system32/drivers/etc FOR all) +add_cd_file(FILE "${CMAKE_CURRENT_SOURCE_DIR}/ms-nfs41-idmap.conf" DESTINATION reactos/system32/drivers/etc FOR all) diff --git a/reactos/base/services/nfsd/acl.c b/reactos/base/services/nfsd/acl.c new file mode 100644 index 00000000000..1d3774a9264 --- /dev/null +++ b/reactos/base/services/nfsd/acl.c @@ -0,0 +1,801 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include +#include +#include + +#include "nfs41_ops.h" +#include "delegation.h" +#include "daemon_debug.h" +#include "util.h" +#include "upcall.h" +#include "nfs41_xdr.h" + +//#define DEBUG_ACLS +#define ACLLVL 2 /* dprintf level for acl logging */ + +extern char localdomain_name[NFS41_HOSTNAME_LEN]; + +static int parse_getacl(unsigned char *buffer, uint32_t length, + nfs41_upcall *upcall) +{ + int status; + getacl_upcall_args *args = &upcall->args.getacl; + + status = safe_read(&buffer, &length, &args->query, sizeof(args->query)); + if (status) goto out; + + dprintf(1, "parsing NFS41_ACL_QUERY: info_class=%d\n", args->query); +out: + return status; +} + +static int create_unknownsid(WELL_KNOWN_SID_TYPE type, PSID *sid, + DWORD *sid_len) +{ + int status; + *sid_len = 0; + *sid = NULL; + + status = CreateWellKnownSid(type, NULL, *sid, sid_len); + dprintf(ACLLVL, "create_unknownsid: CreateWellKnownSid type %d returned %d " + "GetLastError %d sid len %d needed\n", type, status, + GetLastError(), *sid_len); + if (status) + return ERROR_INTERNAL_ERROR; + status = GetLastError(); + if (status != ERROR_INSUFFICIENT_BUFFER) + return status; + *sid = malloc(*sid_len); + if (*sid == NULL) + return ERROR_INSUFFICIENT_BUFFER; + status = CreateWellKnownSid(type, NULL, *sid, sid_len); + if (status) + return ERROR_SUCCESS; + free(*sid); + status = GetLastError(); + eprintf("create_unknownsid: CreateWellKnownSid failed with %d\n", status); + return status; +} + +static void convert_nfs4name_2_user_domain(LPSTR nfs4name, + LPSTR *domain) +{ + LPSTR p = nfs4name; + for(; p[0] != '\0'; p++) { + if (p[0] == '@') { + p[0] = '\0'; + *domain = &p[1]; + break; + } + } +} + +static int map_name_2_sid(DWORD *sid_len, PSID *sid, LPCSTR name) +{ + int status = ERROR_INTERNAL_ERROR; + SID_NAME_USE sid_type; + LPSTR tmp_buf = NULL; + DWORD tmp = 0; + + status = LookupAccountName(NULL, name, NULL, sid_len, NULL, &tmp, &sid_type); + dprintf(ACLLVL, "map_name_2_sid: LookupAccountName for %s returned %d " + "GetLastError %d name len %d domain len %d\n", name, status, + GetLastError(), *sid_len, tmp); + if (status) + return ERROR_INTERNAL_ERROR; + + status = GetLastError(); + switch(status) { + case ERROR_INSUFFICIENT_BUFFER: + *sid = malloc(*sid_len); + if (*sid == NULL) { + status = GetLastError(); + goto out; + } + tmp_buf = (LPSTR) malloc(tmp); + if (tmp_buf == NULL) + goto out_free_sid; + status = LookupAccountName(NULL, name, *sid, sid_len, tmp_buf, + &tmp, &sid_type); + free(tmp_buf); + if (!status) { + eprintf("map_name_2_sid: LookupAccountName for %s failed " + "with %d\n", name, GetLastError()); + goto out_free_sid; + } else { +#ifdef DEBUG_ACLS + LPSTR ssid = NULL; + if (IsValidSid(*sid)) + if (ConvertSidToStringSidA(*sid, &ssid)) + dprintf(1, "map_name_2_sid: sid_type = %d SID %s\n", + sid_type, ssid); + else + dprintf(1, "map_name_2_sid: ConvertSidToStringSidA failed " + "with %d\n", GetLastError()); + else + dprintf(1, "map_name_2_sid: Invalid Sid ?\n"); + if (ssid) LocalFree(ssid); +#endif + } + status = ERROR_SUCCESS; + break; + case ERROR_NONE_MAPPED: + status = create_unknownsid(WinNullSid, sid, sid_len); + if (status) + goto out_free_sid; + } +out: + return status; +out_free_sid: + status = GetLastError(); + free(*sid); + goto out; +} + +static void free_sids(PSID *sids, int count) +{ + int i; + for(i = 0; i < count; i++) + free(sids[i]); + free(sids); +} + +static int check_4_special_identifiers(char *who, PSID *sid, DWORD *sid_len, + BOOLEAN *flag) +{ + int status = ERROR_SUCCESS; + WELL_KNOWN_SID_TYPE type = 0; + *flag = TRUE; + if (!strncmp(who, ACE4_OWNER, strlen(ACE4_OWNER)-1)) + type = WinCreatorOwnerSid; + else if (!strncmp(who, ACE4_GROUP, strlen(ACE4_GROUP)-1)) + type = WinCreatorGroupSid; + else if (!strncmp(who, ACE4_EVERYONE, strlen(ACE4_EVERYONE)-1)) + type = WinWorldSid; + else if (!strncmp(who, ACE4_NOBODY, strlen(ACE4_NOBODY))) + type = WinNullSid; + else + *flag = FALSE; + if (*flag) + status = create_unknownsid(type, sid, sid_len); + return status; +} + +static int convert_nfs4acl_2_dacl(nfsacl41 *acl, int file_type, + PACL *dacl_out, PSID **sids_out) +{ + int status = ERROR_NOT_SUPPORTED, size = 0; + uint32_t i; + DWORD sid_len; + PSID *sids; + PACL dacl; + LPSTR domain = NULL; + BOOLEAN flag; + + sids = malloc(acl->count * sizeof(PSID)); + if (sids == NULL) { + status = GetLastError(); + goto out; + } + for (i = 0; i < acl->count; i++) { + convert_nfs4name_2_user_domain(acl->aces[i].who, &domain); + dprintf(ACLLVL, "handle_getacl: for user=%s domain=%s\n", + acl->aces[i].who, domain?domain:""); + status = check_4_special_identifiers(acl->aces[i].who, &sids[i], + &sid_len, &flag); + if (status) { + free_sids(sids, i); + goto out; + } + if (!flag) { + status = map_name_2_sid(&sid_len, &sids[i], acl->aces[i].who); + if (status) { + free_sids(sids, i); + goto out; + } + } + size += sid_len - sizeof(DWORD); + } + size += sizeof(ACL) + (sizeof(ACCESS_ALLOWED_ACE)*acl->count); + size = (size + sizeof(DWORD) - 1) & 0xfffffffc; //align size on word boundry + dacl = malloc(size); + if (dacl == NULL) + goto out_free_sids; + + if (InitializeAcl(dacl, size, ACL_REVISION)) { + ACCESS_MASK mask; + for (i = 0; i < acl->count; i++) { + // nfs4 acemask should be exactly the same as file access mask + mask = acl->aces[i].acemask; + dprintf(ACLLVL, "access mask %x ace type %s\n", mask, + acl->aces[i].acetype?"DENIED ACE":"ALLOWED ACE"); + if (acl->aces[i].acetype == ACE4_ACCESS_ALLOWED_ACE_TYPE) { + status = AddAccessAllowedAce(dacl, ACL_REVISION, mask, sids[i]); + if (!status) { + eprintf("convert_nfs4acl_2_dacl: AddAccessAllowedAce failed " + "with %d\n", status); + goto out_free_dacl; + } + else status = ERROR_SUCCESS; + } else if (acl->aces[i].acetype == ACE4_ACCESS_DENIED_ACE_TYPE) { + status = AddAccessDeniedAce(dacl, ACL_REVISION, mask, sids[i]); + if (!status) { + eprintf("convert_nfs4acl_2_dacl: AddAccessDeniedAce failed " + "with %d\n", status); + goto out_free_dacl; + } + else status = ERROR_SUCCESS; + } else { + eprintf("convert_nfs4acl_2_dacl: unknown acetype %d\n", + acl->aces[i].acetype); + status = ERROR_INTERNAL_ERROR; + free(dacl); + free_sids(sids, acl->count); + goto out; + } + } + } else { + eprintf("convert_nfs4acl_2_dacl: InitializeAcl failed with %d\n", status); + goto out_free_dacl; + } + status = ERROR_SUCCESS; + *sids_out = sids; + *dacl_out = dacl; +out: + return status; +out_free_dacl: + free(dacl); +out_free_sids: + free_sids(sids, acl->count); + status = GetLastError(); + goto out; +} + +static int handle_getacl(nfs41_upcall *upcall) +{ + int status = ERROR_NOT_SUPPORTED; + getacl_upcall_args *args = &upcall->args.getacl; + nfs41_open_state *state = upcall->state_ref; + nfs41_file_info info = { 0 }; + bitmap4 attr_request = { 0 }; + LPSTR domain = NULL; + SECURITY_DESCRIPTOR sec_desc; + PACL dacl = NULL; + PSID *sids = NULL; + PSID osid = NULL, gsid = NULL; + DWORD sid_len; + char owner[NFS4_OPAQUE_LIMIT], group[NFS4_OPAQUE_LIMIT]; + nfsacl41 acl = { 0 }; + + // need to cache owner/group information XX + attr_request.count = 2; + attr_request.arr[1] = FATTR4_WORD1_OWNER | FATTR4_WORD1_OWNER_GROUP; + if (args->query & DACL_SECURITY_INFORMATION) { + info.acl = &acl; + attr_request.arr[0] |= FATTR4_WORD0_ACL; + } + info.owner = owner; + info.owner_group = group; + status = nfs41_getattr(state->session, &state->file, &attr_request, &info); + if (status) { + eprintf("handle_getacl: nfs41_cached_getattr() failed with %d\n", + status); + goto out; + } + + status = InitializeSecurityDescriptor(&sec_desc, + SECURITY_DESCRIPTOR_REVISION); + if (!status) { + status = GetLastError(); + eprintf("handle_getacl: InitializeSecurityDescriptor failed with %d\n", + status); + goto out; + } + /* can't (re)use the same sid variable for both owner and group sids + * because security descriptor is created in absolute-form and it just + * stores pointers to the sids. thus each owner and group needs its own + * memory. free them after creating self-relative security descriptor. + */ + if (args->query & OWNER_SECURITY_INFORMATION) { + // parse user@domain. currently ignoring domain part XX + convert_nfs4name_2_user_domain(info.owner, &domain); + dprintf(ACLLVL, "handle_getacl: OWNER_SECURITY_INFORMATION: for user=%s " + "domain=%s\n", info.owner, domain?domain:""); + sid_len = 0; + status = map_name_2_sid(&sid_len, &osid, info.owner); + if (status) + goto out; + status = SetSecurityDescriptorOwner(&sec_desc, osid, TRUE); + if (!status) { + status = GetLastError(); + eprintf("handle_getacl: SetSecurityDescriptorOwner failed with " + "%d\n", status); + goto out; + } + } + if (args->query & GROUP_SECURITY_INFORMATION) { + convert_nfs4name_2_user_domain(info.owner_group, &domain); + dprintf(ACLLVL, "handle_getacl: GROUP_SECURITY_INFORMATION: for %s " + "domain=%s\n", info.owner_group, domain?domain:""); + sid_len = 0; + status = map_name_2_sid(&sid_len, &gsid, info.owner_group); + if (status) + goto out; + status = SetSecurityDescriptorGroup(&sec_desc, gsid, TRUE); + if (!status) { + status = GetLastError(); + eprintf("handle_getacl: SetSecurityDescriptorGroup failed with " + "%d\n", status); + goto out; + } + } + if (args->query & DACL_SECURITY_INFORMATION) { + dprintf(ACLLVL, "handle_getacl: DACL_SECURITY_INFORMATION\n"); + status = convert_nfs4acl_2_dacl(info.acl, state->type, &dacl, &sids); + if (status) + goto out; + status = SetSecurityDescriptorDacl(&sec_desc, TRUE, dacl, TRUE); + if (!status) { + status = GetLastError(); + eprintf("handle_getacl: SetSecurityDescriptorDacl failed with " + "%d\n", status); + goto out; + } + } + + args->sec_desc_len = 0; + status = MakeSelfRelativeSD(&sec_desc, args->sec_desc, &args->sec_desc_len); + if (status) { + status = ERROR_INTERNAL_ERROR; + goto out; + } + status = GetLastError(); + if (status != ERROR_INSUFFICIENT_BUFFER) { + eprintf("handle_getacl: MakeSelfRelativeSD failes with %d\n", status); + goto out; + } + args->sec_desc = malloc(args->sec_desc_len); + if (args->sec_desc == NULL) { + status = GetLastError(); + goto out; + } + status = MakeSelfRelativeSD(&sec_desc, args->sec_desc, &args->sec_desc_len); + if (!status) { + status = GetLastError(); + eprintf("handle_getacl: MakeSelfRelativeSD failes with %d\n", status); + free(args->sec_desc); + goto out; + } else status = ERROR_SUCCESS; + +out: + if (args->query & OWNER_SECURITY_INFORMATION) { + if (osid) free(osid); + } + if (args->query & GROUP_SECURITY_INFORMATION) { + if (gsid) free(gsid); + } + if (args->query & DACL_SECURITY_INFORMATION) { + if (sids) free_sids(sids, info.acl->count); + free(dacl); + nfsacl41_free(info.acl); + } + return status; +} + +static int marshall_getacl(unsigned char *buffer, uint32_t *length, + nfs41_upcall *upcall) +{ + int status = ERROR_NOT_SUPPORTED; + getacl_upcall_args *args = &upcall->args.getacl; + + status = safe_write(&buffer, length, &args->sec_desc_len, sizeof(DWORD)); + if (status) goto out; + status = safe_write(&buffer, length, args->sec_desc, args->sec_desc_len); + free(args->sec_desc); + if (status) goto out; +out: + return status; +} + +const nfs41_upcall_op nfs41_op_getacl = { + parse_getacl, + handle_getacl, + marshall_getacl +}; + +static int parse_setacl(unsigned char *buffer, uint32_t length, + nfs41_upcall *upcall) +{ + int status; + setacl_upcall_args *args = &upcall->args.setacl; + ULONG sec_desc_len; + + status = safe_read(&buffer, &length, &args->query, sizeof(args->query)); + if (status) goto out; + status = safe_read(&buffer, &length, &sec_desc_len, sizeof(ULONG)); + if (status) goto out; + args->sec_desc = (PSECURITY_DESCRIPTOR)buffer; + + dprintf(1, "parsing NFS41_ACL_SET: info_class=%d sec_desc_len=%d\n", + args->query, sec_desc_len); +out: + return status; +} + +static int is_well_known_sid(PSID sid, char *who) +{ + int status, i; + for (i = 0; i < 78; i++) { + status = IsWellKnownSid(sid, (WELL_KNOWN_SID_TYPE)i); + if (!status) continue; + else { + dprintf(ACLLVL, "WELL_KNOWN_SID_TYPE %d\n", i); + switch((WELL_KNOWN_SID_TYPE)i) { + case WinCreatorOwnerSid: + memcpy(who, ACE4_OWNER, strlen(ACE4_OWNER)+1); + return TRUE; + case WinNullSid: + memcpy(who, ACE4_NOBODY, strlen(ACE4_NOBODY)+1); + return TRUE; + case WinAnonymousSid: + memcpy(who, ACE4_ANONYMOUS, strlen(ACE4_ANONYMOUS)+1); + return TRUE; + case WinWorldSid: + memcpy(who, ACE4_EVERYONE, strlen(ACE4_EVERYONE)+1); + return TRUE; + case WinCreatorGroupSid: + case WinBuiltinUsersSid: + memcpy(who, ACE4_GROUP, strlen(ACE4_GROUP)+1); + return TRUE; + case WinAuthenticatedUserSid: + memcpy(who, ACE4_AUTHENTICATED, strlen(ACE4_AUTHENTICATED)+1); + return TRUE; + case WinDialupSid: + memcpy(who, ACE4_DIALUP, strlen(ACE4_DIALUP)+1); + return TRUE; + case WinNetworkSid: + memcpy(who, ACE4_NETWORK, strlen(ACE4_NETWORK)+1); + return TRUE; + case WinBatchSid: + memcpy(who, ACE4_BATCH, strlen(ACE4_BATCH)+1); + return TRUE; + case WinInteractiveSid: + memcpy(who, ACE4_INTERACTIVE, strlen(ACE4_INTERACTIVE)+1); + return TRUE; + case WinNetworkServiceSid: + case WinLocalServiceSid: + case WinServiceSid: + memcpy(who, ACE4_SERVICE, strlen(ACE4_SERVICE)+1); + return TRUE; + default: return FALSE; + } + } + } + return FALSE; +} + +static void map_aceflags(BYTE win_aceflags, uint32_t *nfs4_aceflags) +{ + if (win_aceflags & OBJECT_INHERIT_ACE) + *nfs4_aceflags |= ACE4_FILE_INHERIT_ACE; + if (win_aceflags & CONTAINER_INHERIT_ACE) + *nfs4_aceflags |= ACE4_DIRECTORY_INHERIT_ACE; + if (win_aceflags & NO_PROPAGATE_INHERIT_ACE) + *nfs4_aceflags |= ACE4_NO_PROPAGATE_INHERIT_ACE; + if (win_aceflags & INHERIT_ONLY_ACE) + *nfs4_aceflags |= ACE4_INHERIT_ONLY_ACE; + if (win_aceflags & INHERITED_ACE) + *nfs4_aceflags |= ACE4_INHERITED_ACE; + dprintf(ACLLVL, "ACE FLAGS: %x nfs4 aceflags %x\n", + win_aceflags, *nfs4_aceflags); +} + +static void map_acemask(ACCESS_MASK mask, int file_type, uint32_t *nfs4_mask) +{ + dprintf(ACLLVL, "ACE MASK: %x\n", mask); + print_windows_access_mask(0, mask); + /* check if any GENERIC bits set */ + if (mask & 0xf000000) { + if (mask & GENERIC_ALL) { + if (file_type == NF4DIR) + *nfs4_mask |= ACE4_ALL_DIR; + else + *nfs4_mask |= ACE4_ALL_FILE; + } else { + if (mask & GENERIC_READ) + *nfs4_mask |= ACE4_GENERIC_READ; + if (mask & GENERIC_WRITE) + *nfs4_mask |= ACE4_GENERIC_WRITE; + if (mask & GENERIC_EXECUTE) + *nfs4_mask |= ACE4_GENERIC_EXECUTE; + } + } + else /* ignoring generic and reserved bits */ + *nfs4_mask = mask & 0x00ffffff; + print_nfs_access_mask(0, *nfs4_mask); +} + +static int map_nfs4ace_who(PSID sid, PSID owner_sid, PSID group_sid, char *who_out, char *domain) +{ + int status = ERROR_INTERNAL_ERROR; + DWORD size = 0, tmp_size = 0; + SID_NAME_USE sid_type; + LPSTR tmp_buf = NULL, who = NULL; + + /* for ace mapping, we want to map owner's sid into "owner@" + * but for set_owner attribute we want to map owner into a user name + * same applies to group + */ + status = 0; + if (owner_sid) { + if (EqualSid(sid, owner_sid)) { + dprintf(ACLLVL, "map_nfs4ace_who: this is owner's sid\n"); + memcpy(who_out, ACE4_OWNER, strlen(ACE4_OWNER)+1); + return ERROR_SUCCESS; + } + } + if (group_sid) { + if (EqualSid(sid, group_sid)) { + dprintf(ACLLVL, "map_nfs4ace_who: this is group's sid\n"); + memcpy(who_out, ACE4_GROUP, strlen(ACE4_GROUP)+1); + return ERROR_SUCCESS; + } + } + status = is_well_known_sid(sid, who_out); + if (status) { + if (!strncmp(who_out, ACE4_NOBODY, strlen(ACE4_NOBODY))) { + size = (DWORD)strlen(ACE4_NOBODY); + goto add_domain; + } + else + return ERROR_SUCCESS; + } + + status = LookupAccountSid(NULL, sid, who, &size, tmp_buf, + &tmp_size, &sid_type); + dprintf(ACLLVL, "map_nfs4ace_who: LookupAccountSid returned %d GetLastError " + "%d name len %d domain len %d\n", status, GetLastError(), + size, tmp_size); + if (status) + return ERROR_INTERNAL_ERROR; + status = GetLastError(); + if (status != ERROR_INSUFFICIENT_BUFFER) + return ERROR_INTERNAL_ERROR; + who = malloc(size); + if (who == NULL) { + status = GetLastError(); + goto out; + } + tmp_buf = malloc(tmp_size); + if (tmp_buf == NULL) + goto out_free_who; + status = LookupAccountSid(NULL, sid, who, &size, tmp_buf, + &tmp_size, &sid_type); + free(tmp_buf); + if (!status) { + eprintf("map_nfs4ace_who: LookupAccountSid failed with %d\n", + GetLastError()); + goto out_free_who; + } + memcpy(who_out, who, size); +add_domain: + memcpy(who_out+size, "@", sizeof(char)); + memcpy(who_out+size+1, domain, strlen(domain)+1); + dprintf(ACLLVL, "map_nfs4ace_who: who=%s\n", who_out); + if (who) free(who); + status = ERROR_SUCCESS; +out: + return status; +out_free_who: + free(who); + status = GetLastError(); + goto out; +} +static int map_dacl_2_nfs4acl(PACL acl, PSID sid, PSID gsid, nfsacl41 *nfs4_acl, + int file_type, char *domain) +{ + int status; + if (acl == NULL) { + dprintf(ACLLVL, "this is a NULL dacl: all access to an object\n"); + nfs4_acl->count = 1; + nfs4_acl->aces = calloc(1, sizeof(nfsace4)); + if (nfs4_acl->aces == NULL) { + status = GetLastError(); + goto out; + } + nfs4_acl->flag = 0; + memcpy(nfs4_acl->aces->who, ACE4_EVERYONE, strlen(ACE4_EVERYONE)+1); + nfs4_acl->aces->acetype = ACE4_ACCESS_ALLOWED_ACE_TYPE; + if (file_type == NF4DIR) + nfs4_acl->aces->acemask = ACE4_ALL_DIR; + else + nfs4_acl->aces->acemask = ACE4_ALL_FILE; + nfs4_acl->aces->aceflag = 0; + } else { + int i; + PACE_HEADER ace; + PBYTE tmp_pointer; + + dprintf(ACLLVL, "NON-NULL dacl with %d ACEs\n", acl->AceCount); + print_hexbuf_no_asci(3, (unsigned char *)"ACL\n", + (unsigned char *)acl, acl->AclSize); + nfs4_acl->count = acl->AceCount; + nfs4_acl->aces = calloc(nfs4_acl->count, sizeof(nfsace4)); + if (nfs4_acl->aces == NULL) { + status = GetLastError(); + goto out; + } + nfs4_acl->flag = 0; + for (i = 0; i < acl->AceCount; i++) { + status = GetAce(acl, i, &ace); + if (!status) { + status = GetLastError(); + eprintf("map_dacl_2_nfs4acl: GetAce failed with %d\n", status); + goto out_free; + } + tmp_pointer = (PBYTE)ace; + print_hexbuf_no_asci(3, (unsigned char *)"ACE\n", + (unsigned char *)ace, ace->AceSize); + dprintf(ACLLVL, "ACE TYPE: %x\n", ace->AceType); + if (ace->AceType == ACCESS_ALLOWED_ACE_TYPE) + nfs4_acl->aces[i].acetype = ACE4_ACCESS_ALLOWED_ACE_TYPE; + else if (ace->AceType == ACCESS_DENIED_ACE_TYPE) + nfs4_acl->aces[i].acetype = ACE4_ACCESS_DENIED_ACE_TYPE; + else { + eprintf("map_dacl_2_nfs4acl: unsupported ACE type %d\n", + ace->AceType); + status = ERROR_NOT_SUPPORTED; + goto out_free; + } + + map_aceflags(ace->AceFlags, &nfs4_acl->aces[i].aceflag); + map_acemask(*(PACCESS_MASK)(ace + 1), file_type, + &nfs4_acl->aces[i].acemask); + + tmp_pointer += sizeof(ACCESS_MASK) + sizeof(ACE_HEADER); + status = map_nfs4ace_who(tmp_pointer, sid, gsid, nfs4_acl->aces[i].who, + domain); + if (status) + goto out_free; + } + } + status = ERROR_SUCCESS; +out: + return status; +out_free: + free(nfs4_acl->aces); + goto out; +} + +static int handle_setacl(nfs41_upcall *upcall) +{ + int status = ERROR_NOT_SUPPORTED; + setacl_upcall_args *args = &upcall->args.setacl; + nfs41_open_state *state = upcall->state_ref; + nfs41_file_info info = { 0 }; + stateid_arg stateid; + nfsacl41 nfs4_acl = { 0 }; + PSID sid = NULL, gsid = NULL; + BOOL sid_default, gsid_default; + + if (args->query & OWNER_SECURITY_INFORMATION) { + char owner[NFS4_OPAQUE_LIMIT]; + dprintf(ACLLVL, "handle_setacl: OWNER_SECURITY_INFORMATION\n"); + status = GetSecurityDescriptorOwner(args->sec_desc, &sid, &sid_default); + if (!status) { + status = GetLastError(); + eprintf("GetSecurityDescriptorOwner failed with %d\n", status); + goto out; + } + info.owner = owner; + status = map_nfs4ace_who(sid, NULL, NULL, info.owner, localdomain_name); + if (status) + goto out; + else { + info.attrmask.arr[1] |= FATTR4_WORD1_OWNER; + info.attrmask.count = 2; + } + } + if (args->query & GROUP_SECURITY_INFORMATION) { + char group[NFS4_OPAQUE_LIMIT]; + dprintf(ACLLVL, "handle_setacl: GROUP_SECURITY_INFORMATION\n"); + status = GetSecurityDescriptorGroup(args->sec_desc, &sid, &sid_default); + if (!status) { + status = GetLastError(); + eprintf("GetSecurityDescriptorOwner failed with %d\n", status); + goto out; + } + info.owner_group = group; + status = map_nfs4ace_who(sid, NULL, NULL, info.owner_group, + localdomain_name); + if (status) + goto out; + else { + info.attrmask.arr[1] |= FATTR4_WORD1_OWNER_GROUP; + info.attrmask.count = 2; + } + } + if (args->query & DACL_SECURITY_INFORMATION) { + BOOL dacl_present, dacl_default; + PACL acl; + dprintf(ACLLVL, "handle_setacl: DACL_SECURITY_INFORMATION\n"); + status = GetSecurityDescriptorDacl(args->sec_desc, &dacl_present, + &acl, &dacl_default); + if (!status) { + status = GetLastError(); + eprintf("GetSecurityDescriptorDacl failed with %d\n", status); + goto out; + } + status = GetSecurityDescriptorOwner(args->sec_desc, &sid, &sid_default); + if (!status) { + status = GetLastError(); + eprintf("GetSecurityDescriptorOwner failed with %d\n", status); + goto out; + } + status = GetSecurityDescriptorGroup(args->sec_desc, &gsid, &gsid_default); + if (!status) { + status = GetLastError(); + eprintf("GetSecurityDescriptorOwner failed with %d\n", status); + goto out; + } + status = map_dacl_2_nfs4acl(acl, sid, gsid, &nfs4_acl, state->type, + localdomain_name); + if (status) + goto out; + else { + info.acl = &nfs4_acl; + info.attrmask.arr[0] |= FATTR4_WORD0_ACL; + if (!info.attrmask.count) + info.attrmask.count = 1; + } + } + + /* break read delegations before SETATTR */ + nfs41_delegation_return(state->session, &state->file, + OPEN_DELEGATE_WRITE, FALSE); + + nfs41_open_stateid_arg(state, &stateid); + status = nfs41_setattr(state->session, &state->file, &stateid, &info); + if (status) { + dprintf(ACLLVL, "handle_setacl: nfs41_setattr() failed with error %s.\n", + nfs_error_string(status)); + status = nfs_to_windows_error(status, ERROR_NOT_SUPPORTED); + } + args->ctime = info.change; + if (args->query & DACL_SECURITY_INFORMATION) + free(nfs4_acl.aces); +out: + return status; +} + +static int marshall_setacl(unsigned char *buffer, uint32_t *length, nfs41_upcall *upcall) +{ + setacl_upcall_args *args = &upcall->args.setacl; + return safe_write(&buffer, length, &args->ctime, sizeof(args->ctime)); +} + +const nfs41_upcall_op nfs41_op_setacl = { + parse_setacl, + handle_setacl, + marshall_setacl +}; \ No newline at end of file diff --git a/reactos/base/services/nfsd/callback_server.c b/reactos/base/services/nfsd/callback_server.c new file mode 100644 index 00000000000..dc569b61c65 --- /dev/null +++ b/reactos/base/services/nfsd/callback_server.c @@ -0,0 +1,547 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include +#include + +#include "nfs41_ops.h" +#include "delegation.h" +#include "nfs41_callback.h" +#include "daemon_debug.h" + + +#define CBSLVL 2 /* dprintf level for callback server logging */ + + +static const char g_server_tag[] = "ms-nfs41-callback"; + + +/* callback session */ +static void replay_cache_write( + IN nfs41_cb_session *session, + IN struct cb_compound_args *args, + IN struct cb_compound_res *res, + IN bool_t cachethis); + +void nfs41_callback_session_init( + IN nfs41_session *session) +{ + /* initialize the replay cache with status NFS4ERR_SEQ_MISORDERED */ + struct cb_compound_res res = { 0 }; + StringCchCopyA(res.tag.str, CB_COMPOUND_MAX_TAG, g_server_tag); + res.tag.len = sizeof(g_server_tag); + res.status = NFS4ERR_SEQ_MISORDERED; + + session->cb_session.cb_sessionid = session->session_id; + + replay_cache_write(&session->cb_session, NULL, &res, FALSE); +} + + +/* OP_CB_LAYOUTRECALL */ +static enum_t handle_cb_layoutrecall( + IN nfs41_rpc_clnt *rpc_clnt, + IN struct cb_layoutrecall_args *args, + OUT struct cb_layoutrecall_res *res) +{ + enum pnfs_status status; + + status = pnfs_file_layout_recall(rpc_clnt->client, args); + switch (status) { + case PNFS_PENDING: + /* not enough information to process the recall yet */ + res->status = NFS4ERR_DELAY; + break; + default: + /* forgetful model for layout recalls */ + res->status = NFS4ERR_NOMATCHING_LAYOUT; + break; + } + + dprintf(CBSLVL, " OP_CB_LAYOUTRECALL { %s, %s, recall %u } %s\n", + pnfs_layout_type_string(args->type), + pnfs_iomode_string(args->iomode), args->recall.type, + nfs_error_string(res->status)); + return res->status; +} + +/* OP_CB_RECALL_SLOT */ +static enum_t handle_cb_recall_slot( + IN nfs41_rpc_clnt *rpc_clnt, + IN struct cb_recall_slot_args *args, + OUT struct cb_recall_slot_res *res) +{ + res->status = nfs41_session_recall_slot(rpc_clnt->client->session, + args->target_highest_slotid); + + dprintf(CBSLVL, " OP_CB_RECALL_SLOT { %u } %s\n", + args->target_highest_slotid, nfs_error_string(res->status)); + return res->status; +} + +/* OP_CB_SEQUENCE */ +static enum_t handle_cb_sequence( + IN nfs41_rpc_clnt *rpc_clnt, + IN struct cb_sequence_args *args, + OUT struct cb_sequence_res *res, + OUT nfs41_cb_session **session_out, + OUT bool_t *cachethis) +{ + nfs41_cb_session *cb_session = &rpc_clnt->client->session->cb_session; + uint32_t status = NFS4_OK; + res->status = NFS4_OK; + + *session_out = cb_session; + + /* validate the sessionid */ + if (memcmp(cb_session->cb_sessionid, args->sessionid, + NFS4_SESSIONID_SIZE)) { + eprintf("[cb] received sessionid doesn't match session\n"); + res->status = NFS4ERR_BADSESSION; + goto out; + } + + /* we only support 1 slot for the back channel so slotid MUST be 0 */ + if (args->slotid != 0) { + eprintf("[cb] received unexpected slotid=%d\n", args->slotid); + res->status = NFS4ERR_BADSLOT; + goto out; + } + if (args->highest_slotid != 0) { + eprintf("[cb] received unexpected highest_slotid=%d\n", + args->highest_slotid); + res->status = NFS4ERR_BAD_HIGH_SLOT; + goto out; + } + + /* check for a retry with the same seqid */ + if (args->sequenceid == cb_session->cb_seqnum) { + if (!cb_session->replay.res.length) { + /* return success for sequence, but fail the next operation */ + res->status = NFS4_OK; + status = NFS4ERR_RETRY_UNCACHED_REP; + } else { + /* return NFS4ERR_SEQ_FALSE_RETRY for all replays; if the retry + * turns out to be valid, this response will be replaced anyway */ + status = res->status = NFS4ERR_SEQ_FALSE_RETRY; + } + goto out; + } + + /* error on any unexpected seqids */ + if (args->sequenceid != cb_session->cb_seqnum+1) { + eprintf("[cb] bad received seq#=%d, expected=%d\n", + args->sequenceid, cb_session->cb_seqnum+1); + res->status = NFS4ERR_SEQ_MISORDERED; + goto out; + } + + cb_session->cb_seqnum = args->sequenceid; + *cachethis = args->cachethis; + + memcpy(res->ok.sessionid, args->sessionid, NFS4_SESSIONID_SIZE); + res->ok.sequenceid = args->sequenceid; + res->ok.slotid = args->slotid; + res->ok.highest_slotid = args->highest_slotid; + res->ok.target_highest_slotid = args->highest_slotid; + +out: + dprintf(CBSLVL, " OP_CB_SEQUENCE { seqid %u, slot %u, cachethis %d } " + "%s\n", args->sequenceid, args->slotid, args->cachethis, + nfs_error_string(res->status)); + return status; +} + +/* OP_CB_GETATTR */ +static enum_t handle_cb_getattr( + IN nfs41_rpc_clnt *rpc_clnt, + IN struct cb_getattr_args *args, + OUT struct cb_getattr_res *res) +{ + /* look up cached attributes for the given filehandle */ + res->status = nfs41_delegation_getattr(rpc_clnt->client, + &args->fh, &args->attr_request, &res->info); + return res->status; +} + +/* OP_CB_RECALL */ +static enum_t handle_cb_recall( + IN nfs41_rpc_clnt *rpc_clnt, + IN struct cb_recall_args *args, + OUT struct cb_recall_res *res) +{ + /* return the delegation asynchronously */ + res->status = nfs41_delegation_recall(rpc_clnt->client, + &args->fh, &args->stateid, args->truncate); + return res->status; +} + +/* OP_CB_NOTIFY_DEVICEID */ +static enum_t handle_cb_notify_deviceid( + IN nfs41_rpc_clnt *rpc_clnt, + IN struct cb_notify_deviceid_args *args, + OUT struct cb_notify_deviceid_res *res) +{ + uint32_t i; + for (i = 0; i < args->change_count; i++) { + pnfs_file_device_notify(rpc_clnt->client->devices, + &args->change_list[i]); + } + res->status = NFS4_OK; + return res->status; +} + +static void replay_cache_write( + IN nfs41_cb_session *session, + IN OPTIONAL struct cb_compound_args *args, + IN struct cb_compound_res *res, + IN bool_t cachethis) +{ + XDR xdr; + uint32_t i; + + session->replay.arg.length = 0; + session->replay.res.length = 0; + + /* encode the reply directly into the replay cache */ + xdrmem_create(&xdr, (char*)session->replay.res.buffer, + NFS41_MAX_SERVER_CACHE, XDR_ENCODE); + + /* always try to cache the result */ + if (proc_cb_compound_res(&xdr, res)) { + session->replay.res.length = XDR_GETPOS(&xdr); + + if (args) { + /* encode the arguments into the request cache */ + xdrmem_create(&xdr, (char*)session->replay.arg.buffer, + NFS41_MAX_SERVER_CACHE, XDR_ENCODE); + + if (proc_cb_compound_args(&xdr, args)) + session->replay.arg.length = XDR_GETPOS(&xdr); + } + } else if (cachethis) { + /* on failure, only return errors if caching was requested */ + res->status = NFS4ERR_REP_TOO_BIG_TO_CACHE; + + /* find the first operation that failed to encode */ + for (i = 0; i < res->resarray_count; i++) { + if (!res->resarray[i].xdr_ok) { + res->resarray[i].res.status = NFS4ERR_REP_TOO_BIG_TO_CACHE; + res->resarray_count = i + 1; + break; + } + } + } +} + +static bool_t replay_validate_args( + IN struct cb_compound_args *args, + IN const struct replay_cache *cache) +{ + char buffer[NFS41_MAX_SERVER_CACHE]; + XDR xdr; + + /* encode the current arguments into a temporary buffer */ + xdrmem_create(&xdr, buffer, NFS41_MAX_SERVER_CACHE, XDR_ENCODE); + + if (!proc_cb_compound_args(&xdr, args)) + return FALSE; + + /* must match the cached length */ + if (XDR_GETPOS(&xdr) != cache->length) + return FALSE; + + /* must match the cached buffer contents */ + return memcmp(cache->buffer, buffer, cache->length) == 0; +} + +static bool_t replay_validate_ops( + IN const struct cb_compound_args *args, + IN const struct cb_compound_res *res) +{ + uint32_t i; + for (i = 0; i < res->resarray_count; i++) { + /* can't have more operations than the request */ + if (i >= args->argarray_count) + return FALSE; + + /* each opnum must match the request */ + if (args->argarray[i].opnum != res->resarray[i].opnum) + return FALSE; + + if (res->resarray[i].res.status) + break; + } + return TRUE; +} + +static int replay_cache_read( + IN nfs41_cb_session *session, + IN struct cb_compound_args *args, + OUT struct cb_compound_res **res_out) +{ + XDR xdr; + struct cb_compound_res *replay; + struct cb_compound_res *res = *res_out; + uint32_t status = NFS4_OK; + + replay = calloc(1, sizeof(struct cb_compound_res)); + if (replay == NULL) { + eprintf("[cb] failed to allocate replay buffer\n"); + status = NFS4ERR_SERVERFAULT; + goto out; + } + + /* decode the response from the replay cache */ + xdrmem_create(&xdr, (char*)session->replay.res.buffer, + NFS41_MAX_SERVER_CACHE, XDR_DECODE); + if (!proc_cb_compound_res(&xdr, replay)) { + eprintf("[cb] failed to decode replay buffer\n"); + status = NFS4ERR_SEQ_FALSE_RETRY; + goto out_free_replay; + } + + /* if we cached the arguments, use them to validate the retry */ + if (session->replay.arg.length) { + if (!replay_validate_args(args, &session->replay.arg)) { + eprintf("[cb] retry attempt with different arguments\n"); + status = NFS4ERR_SEQ_FALSE_RETRY; + goto out_free_replay; + } + } else { /* otherwise, comparing opnums is the best we can do */ + if (!replay_validate_ops(args, replay)) { + eprintf("[cb] retry attempt with different operations\n"); + status = NFS4ERR_SEQ_FALSE_RETRY; + goto out_free_replay; + } + } + + /* free previous response and replace it with the replay */ + xdr.x_op = XDR_FREE; + proc_cb_compound_res(&xdr, res); + + dprintf(2, "[cb] retry: returning cached response\n"); + + *res_out = replay; +out: + return status; + +out_free_replay: + xdr.x_op = XDR_FREE; + proc_cb_compound_res(&xdr, replay); + goto out; +} + +/* CB_COMPOUND */ +static void handle_cb_compound(nfs41_rpc_clnt *rpc_clnt, cb_req *req, struct cb_compound_res **reply) +{ + struct cb_compound_args args = { 0 }; + struct cb_compound_res *res = NULL; + struct cb_argop *argop; + struct cb_resop *resop; + XDR *xdr = (XDR*)req->xdr; + nfs41_cb_session *session = NULL; + bool_t cachethis = FALSE; + uint32_t i, status = NFS4_OK; + + dprintf(CBSLVL, "--> handle_cb_compound()\n"); + + /* decode the arguments */ + if (!proc_cb_compound_args(xdr, &args)) { + status = NFS4ERR_BADXDR; + eprintf("failed to decode compound arguments\n"); + } + + /* allocate the compound results */ + res = calloc(1, sizeof(struct cb_compound_res)); + if (res == NULL) { + status = NFS4ERR_SERVERFAULT; + goto out; + } + res->status = status; + StringCchCopyA(res->tag.str, CB_COMPOUND_MAX_TAG, g_server_tag); + res->tag.str[CB_COMPOUND_MAX_TAG-1] = 0; + res->tag.len = (uint32_t)strlen(res->tag.str); + res->resarray = calloc(args.argarray_count, sizeof(struct cb_resop)); + if (res->resarray == NULL) { + res->status = NFS4ERR_SERVERFAULT; + goto out; + } + + dprintf(CBSLVL, "CB_COMPOUND('%s', %u)\n", args.tag.str, args.argarray_count); + if (args.minorversion != 1) { + res->status = NFS4ERR_MINOR_VERS_MISMATCH; //XXXXX + eprintf("args.minorversion %u != 1\n", args.minorversion); + goto out; + } + + /* handle each operation in the compound */ + for (i = 0; i < args.argarray_count && res->status == NFS4_OK; i++) { + argop = &args.argarray[i]; + resop = &res->resarray[i]; + resop->opnum = argop->opnum; + res->resarray_count++; + + /* 20.9.3: The error NFS4ERR_SEQUENCE_POS MUST be returned + * when CB_SEQUENCE is found in any position in a CB_COMPOUND + * beyond the first. If any other operation is in the first + * position of CB_COMPOUND, NFS4ERR_OP_NOT_IN_SESSION MUST + * be returned. + */ + if (i == 0 && argop->opnum != OP_CB_SEQUENCE) { + res->status = resop->res.status = NFS4ERR_OP_NOT_IN_SESSION; + break; + } + if (i != 0 && argop->opnum == OP_CB_SEQUENCE) { + res->status = resop->res.status = NFS4ERR_SEQUENCE_POS; + break; + } + if (status == NFS4ERR_RETRY_UNCACHED_REP) { + res->status = resop->res.status = status; + break; + } + + switch (argop->opnum) { + case OP_CB_LAYOUTRECALL: + dprintf(1, "OP_CB_LAYOUTRECALL\n"); + res->status = handle_cb_layoutrecall(rpc_clnt, + &argop->args.layoutrecall, &resop->res.layoutrecall); + break; + case OP_CB_RECALL_SLOT: + dprintf(1, "OP_CB_RECALL_SLOT\n"); + res->status = handle_cb_recall_slot(rpc_clnt, + &argop->args.recall_slot, &resop->res.recall_slot); + break; + case OP_CB_SEQUENCE: + dprintf(1, "OP_CB_SEQUENCE\n"); + status = handle_cb_sequence(rpc_clnt, &argop->args.sequence, + &resop->res.sequence, &session, &cachethis); + + if (status == NFS4ERR_SEQ_FALSE_RETRY) { + /* replace the current results with the cached response */ + status = replay_cache_read(session, &args, &res); + if (status) res->status = status; + goto out; + } + + if (status == NFS4_OK) + res->status = resop->res.sequence.status; + break; + case OP_CB_GETATTR: + dprintf(1, "OP_CB_GETATTR\n"); + res->status = handle_cb_getattr(rpc_clnt, + &argop->args.getattr, &resop->res.getattr); + break; + case OP_CB_RECALL: + dprintf(1, "OP_CB_RECALL\n"); + res->status = handle_cb_recall(rpc_clnt, + &argop->args.recall, &resop->res.recall); + break; + case OP_CB_NOTIFY: + dprintf(1, "OP_CB_NOTIFY\n"); + res->status = NFS4ERR_NOTSUPP; + break; + case OP_CB_PUSH_DELEG: + dprintf(1, "OP_CB_PUSH_DELEG\n"); + res->status = NFS4ERR_NOTSUPP; + break; + case OP_CB_RECALL_ANY: + dprintf(1, "OP_CB_RECALL_ANY\n"); + res->status = NFS4ERR_NOTSUPP; + break; + case OP_CB_RECALLABLE_OBJ_AVAIL: + dprintf(1, "OP_CB_RECALLABLE_OBJ_AVAIL\n"); + res->status = NFS4ERR_NOTSUPP; + break; + case OP_CB_WANTS_CANCELLED: + dprintf(1, "OP_CB_WANTS_CANCELLED\n"); + res->status = NFS4ERR_NOTSUPP; + break; + case OP_CB_NOTIFY_LOCK: + dprintf(1, "OP_CB_NOTIFY_LOCK\n"); + res->status = NFS4ERR_NOTSUPP; + break; + case OP_CB_NOTIFY_DEVICEID: + dprintf(1, "OP_CB_NOTIFY_DEVICEID\n"); + res->status = NFS4_OK; + break; + case OP_CB_ILLEGAL: + dprintf(1, "OP_CB_ILLEGAL\n"); + res->status = NFS4ERR_NOTSUPP; + break; + default: + eprintf("operation %u not supported\n", argop->opnum); + res->status = NFS4ERR_NOTSUPP; + break; + } + } + + /* always attempt to cache the reply */ + if (session) + replay_cache_write(session, &args, res, cachethis); +out: + /* free the arguments */ + xdr->x_op = XDR_FREE; + proc_cb_compound_args(xdr, &args); + + *reply = res; + dprintf(CBSLVL, "<-- handle_cb_compound() returning %s (%u results)\n", + nfs_error_string(res ? res->status : status), + res ? res->resarray_count : 0); +} + +#ifdef __REACTOS__ +int nfs41_handle_callback(void *rpc_clnt, void *cb, void * dummy) +{ + struct cb_compound_res **reply = dummy; +#else +int nfs41_handle_callback(void *rpc_clnt, void *cb, struct cb_compound_res **reply) +{ +#endif + nfs41_rpc_clnt *rpc = (nfs41_rpc_clnt *)rpc_clnt; + cb_req *request = (cb_req *)cb; + uint32_t status = 0; + + dprintf(1, "nfs41_handle_callback: received call\n"); + if (request->rq_prog != NFS41_RPC_CBPROGRAM) { + eprintf("invalid rpc program %u\n", request->rq_prog); + status = 2; + goto out; + } + + switch (request->rq_proc) { + case CB_NULL: + dprintf(1, "CB_NULL\n"); + break; + + case CB_COMPOUND: + dprintf(1, "CB_COMPOUND\n"); + handle_cb_compound(rpc, request, reply); + break; + + default: + dprintf(1, "invalid rpc procedure %u\n", request->rq_proc); + status = 3; + goto out; + } +out: + return status; +} diff --git a/reactos/base/services/nfsd/callback_xdr.c b/reactos/base/services/nfsd/callback_xdr.c new file mode 100644 index 00000000000..945aa0b43df --- /dev/null +++ b/reactos/base/services/nfsd/callback_xdr.c @@ -0,0 +1,659 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include "nfs41_callback.h" +#include "nfs41_ops.h" +#include "util.h" +#include "daemon_debug.h" + + +#define CBXLVL 2 /* dprintf level for callback xdr logging */ +#ifdef __REACTOS__ +#define CBX_ERR(msg) dprintf((CBXLVL), "%s: failed at %s\n", __FUNCTION__, msg) +#else +#define CBX_ERR(msg) dprintf((CBXLVL), __FUNCTION__ ": failed at " msg "\n") +#endif + +/* common types */ +bool_t xdr_bitmap4(XDR *xdr, bitmap4 *bitmap); +bool_t xdr_fattr4(XDR *xdr, fattr4 *fattr); + +static bool_t common_stateid(XDR *xdr, stateid4 *stateid) +{ + return xdr_u_int32_t(xdr, &stateid->seqid) + && xdr_opaque(xdr, (char*)stateid->other, NFS4_STATEID_OTHER); +} + +static bool_t common_fh(XDR *xdr, nfs41_fh *fh) +{ + return xdr_u_int32_t(xdr, &fh->len) + && fh->len <= NFS4_FHSIZE + && xdr_opaque(xdr, (char*)fh->fh, fh->len); +} + +static bool_t common_fsid(XDR *xdr, nfs41_fsid *fsid) +{ + return xdr_u_int64_t(xdr, &fsid->major) + && xdr_u_int64_t(xdr, &fsid->minor); +} + +static bool_t common_notify4(XDR *xdr, struct notify4 *notify) +{ + return xdr_bitmap4(xdr, ¬ify->mask) + && xdr_bytes(xdr, ¬ify->list, ¬ify->len, NFS4_OPAQUE_LIMIT); +} + +/* OP_CB_LAYOUTRECALL */ +static bool_t op_cb_layoutrecall_file(XDR *xdr, struct cb_recall_file *args) +{ + bool_t result; + + result = common_fh(xdr, &args->fh); + if (!result) { CBX_ERR("layoutrecall_file.fh"); goto out; } + + result = xdr_u_int64_t(xdr, &args->offset); + if (!result) { CBX_ERR("layoutrecall_file.offset"); goto out; } + + result = xdr_u_int64_t(xdr, &args->length); + if (!result) { CBX_ERR("layoutrecall_file.length"); goto out; } + + result = common_stateid(xdr, &args->stateid); + if (!result) { CBX_ERR("layoutrecall_file.stateid"); goto out; } +out: + return result; +} + +static bool_t op_cb_layoutrecall_fsid(XDR *xdr, union cb_recall_file_args *args) +{ + bool_t result; + + result = common_fsid(xdr, &args->fsid); + if (!result) { CBX_ERR("layoutrecall_fsid.fsid"); goto out; } +out: + return result; +} + +static const struct xdr_discrim cb_layoutrecall_discrim[] = { + { PNFS_RETURN_FILE, (xdrproc_t)op_cb_layoutrecall_file }, + { PNFS_RETURN_FSID, (xdrproc_t)op_cb_layoutrecall_fsid }, + { PNFS_RETURN_ALL, (xdrproc_t)xdr_void }, + { 0, NULL_xdrproc_t } +}; + +static bool_t op_cb_layoutrecall_args(XDR *xdr, struct cb_layoutrecall_args *args) +{ + bool_t result; + + result = xdr_enum(xdr, (enum_t*)&args->type); + if (!result) { CBX_ERR("layoutrecall_args.type"); goto out; } + + result = xdr_enum(xdr, (enum_t*)&args->iomode); + if (!result) { CBX_ERR("layoutrecall_args.iomode"); goto out; } + + result = xdr_bool(xdr, &args->changed); + if (!result) { CBX_ERR("layoutrecall_args.changed"); goto out; } + + result = xdr_union(xdr, (enum_t*)&args->recall.type, + (char*)&args->recall.args, cb_layoutrecall_discrim, NULL_xdrproc_t); + if (!result) { CBX_ERR("layoutrecall_args.recall"); goto out; } +out: + return result; +} + +static bool_t op_cb_layoutrecall_res(XDR *xdr, struct cb_layoutrecall_res *res) +{ + bool_t result; + + result = xdr_enum(xdr, &res->status); + if (!result) { CBX_ERR("layoutrecall_res.status"); goto out; } +out: + return result; +} + + +/* OP_CB_RECALL_SLOT */ +static bool_t op_cb_recall_slot_args(XDR *xdr, struct cb_recall_slot_args *res) +{ + bool_t result; + + result = xdr_u_int32_t(xdr, &res->target_highest_slotid); + if (!result) { CBX_ERR("recall_slot.target_highest_slotid"); goto out; } +out: + return result; +} + +static bool_t op_cb_recall_slot_res(XDR *xdr, struct cb_recall_slot_res *res) +{ + bool_t result; + + result = xdr_enum(xdr, &res->status); + if (!result) { CBX_ERR("recall_slot.status"); goto out; } +out: + return result; +} + + +/* OP_CB_SEQUENCE */ +static bool_t op_cb_sequence_ref(XDR *xdr, struct cb_sequence_ref *args) +{ + bool_t result; + + result = xdr_u_int32_t(xdr, &args->sequenceid); + if (!result) { CBX_ERR("sequence_ref.sequenceid"); goto out; } + + result = xdr_u_int32_t(xdr, &args->slotid); + if (!result) { CBX_ERR("sequence_ref.slotid"); goto out; } +out: + return result; +} + +static bool_t op_cb_sequence_ref_list(XDR *xdr, struct cb_sequence_ref_list *args) +{ + bool_t result; + + result = xdr_opaque(xdr, args->sessionid, NFS4_SESSIONID_SIZE); + if (!result) { CBX_ERR("sequence_ref_list.sessionid"); goto out; } + + result = xdr_array(xdr, (char**)&args->calls, &args->call_count, + 64, sizeof(struct cb_sequence_ref), (xdrproc_t)op_cb_sequence_ref); + if (!result) { CBX_ERR("sequence_ref_list.calls"); goto out; } +out: + return result; +} + +static bool_t op_cb_sequence_args(XDR *xdr, struct cb_sequence_args *args) +{ + bool_t result; + + result = xdr_opaque(xdr, args->sessionid, NFS4_SESSIONID_SIZE); + if (!result) { CBX_ERR("sequence_args.sessionid"); goto out; } + + result = xdr_u_int32_t(xdr, &args->sequenceid); + if (!result) { CBX_ERR("sequence_args.sequenceid"); goto out; } + + result = xdr_u_int32_t(xdr, &args->slotid); + if (!result) { CBX_ERR("sequence_args.slotid"); goto out; } + + result = xdr_u_int32_t(xdr, &args->highest_slotid); + if (!result) { CBX_ERR("sequence_args.highest_slotid"); goto out; } + + result = xdr_bool(xdr, &args->cachethis); + if (!result) { CBX_ERR("sequence_args.cachethis"); goto out; } + + result = xdr_array(xdr, (char**)&args->ref_lists, + &args->ref_list_count, 64, sizeof(struct cb_sequence_ref_list), + (xdrproc_t)op_cb_sequence_ref_list); + if (!result) { CBX_ERR("sequence_args.ref_lists"); goto out; } +out: + return result; +} + +static bool_t op_cb_sequence_res_ok(XDR *xdr, struct cb_sequence_res_ok *res) +{ + bool_t result; + + result = xdr_opaque(xdr, res->sessionid, NFS4_SESSIONID_SIZE); + if (!result) { CBX_ERR("sequence_res.sessionid"); goto out; } + + result = xdr_u_int32_t(xdr, &res->sequenceid); + if (!result) { CBX_ERR("sequence_res.sequenceid"); goto out; } + + result = xdr_u_int32_t(xdr, &res->slotid); + if (!result) { CBX_ERR("sequence_res.slotid"); goto out; } + + result = xdr_u_int32_t(xdr, &res->highest_slotid); + if (!result) { CBX_ERR("sequence_res.highest_slotid"); goto out; } + + result = xdr_u_int32_t(xdr, &res->target_highest_slotid); + if (!result) { CBX_ERR("sequence_res.target_highest_slotid"); goto out; } +out: + return result; +} + +static const struct xdr_discrim cb_sequence_res_discrim[] = { + { NFS4_OK, (xdrproc_t)op_cb_sequence_res_ok }, + { 0, NULL_xdrproc_t } +}; + +static bool_t op_cb_sequence_res(XDR *xdr, struct cb_sequence_res *res) +{ + bool_t result; + + result = xdr_union(xdr, &res->status, (char*)&res->ok, + cb_sequence_res_discrim, (xdrproc_t)xdr_void); + if (!result) { CBX_ERR("seq:argop.args"); goto out; } +out: + return result; +} + +/* OP_CB_GETATTR */ +static bool_t op_cb_getattr_args(XDR *xdr, struct cb_getattr_args *args) +{ + bool_t result; + + result = common_fh(xdr, &args->fh); + if (!result) { CBX_ERR("getattr.fh"); goto out; } + + result = xdr_bitmap4(xdr, &args->attr_request); + if (!result) { CBX_ERR("getattr.attr_request"); goto out; } +out: + return result; +} + +static bool_t info_to_fattr4(nfs41_file_info *info, fattr4 *fattr) +{ + XDR fattr_xdr; + bool_t result = TRUE; + + /* encode nfs41_file_info into fattr4 */ + xdrmem_create(&fattr_xdr, (char*)fattr->attr_vals, + NFS4_OPAQUE_LIMIT, XDR_ENCODE); + + /* The only attributes that the server can reliably + * query via CB_GETATTR are size and change. */ + if (bitmap_isset(&info->attrmask, 0, FATTR4_WORD0_CHANGE)) { + result = xdr_u_hyper(&fattr_xdr, &info->change); + if (!result) { CBX_ERR("getattr.info.change"); goto out; } + bitmap_set(&fattr->attrmask, 0, FATTR4_WORD0_CHANGE); + } + if (bitmap_isset(&info->attrmask, 0, FATTR4_WORD0_SIZE)) { + result = xdr_u_hyper(&fattr_xdr, &info->size); + if (!result) { CBX_ERR("getattr.info.size"); goto out; } + bitmap_set(&fattr->attrmask, 0, FATTR4_WORD0_SIZE); + } + fattr->attr_vals_len = xdr_getpos(&fattr_xdr); +out: + return result; +} + +static bool_t op_cb_getattr_res(XDR *xdr, struct cb_getattr_res *res) +{ + bool_t result; + + result = xdr_enum(xdr, &res->status); + if (!result) { CBX_ERR("getattr.status"); goto out; } + + if (res->status == NFS4_OK) { + fattr4 fattr = { 0 }; + + result = info_to_fattr4(&res->info, &fattr); + if (!result) { goto out; } + + result = xdr_fattr4(xdr, &fattr); + if (!result) { CBX_ERR("getattr.obj_attributes"); goto out; } + } +out: + return result; +} + +/* OP_CB_RECALL */ +static bool_t op_cb_recall_args(XDR *xdr, struct cb_recall_args *args) +{ + bool_t result; + + result = common_stateid(xdr, &args->stateid); + if (!result) { CBX_ERR("recall.stateid"); goto out; } + + result = xdr_bool(xdr, &args->truncate); + if (!result) { CBX_ERR("recall.truncate"); goto out; } + + result = common_fh(xdr, &args->fh); + if (!result) { CBX_ERR("recall.fh"); goto out; } +out: + return result; +} + +static bool_t op_cb_recall_res(XDR *xdr, struct cb_recall_res *res) +{ + bool_t result; + + result = xdr_enum(xdr, &res->status); + if (!result) { CBX_ERR("recall.status"); goto out; } +out: + return result; +} + +/* OP_CB_NOTIFY */ +static bool_t op_cb_notify_args(XDR *xdr, struct cb_notify_args *res) +{ + bool_t result; + + result = xdr_u_int32_t(xdr, &res->target_highest_slotid); + if (!result) { CBX_ERR("notify.target_highest_slotid"); goto out; } +out: + return result; +} + +static bool_t op_cb_notify_res(XDR *xdr, struct cb_notify_res *res) +{ + bool_t result; + + result = xdr_enum(xdr, &res->status); + if (!result) { CBX_ERR("notify.status"); goto out; } +out: + return result; +} + +/* OP_CB_PUSH_DELEG */ +static bool_t op_cb_push_deleg_args(XDR *xdr, struct cb_push_deleg_args *res) +{ + bool_t result; + + result = xdr_u_int32_t(xdr, &res->target_highest_slotid); + if (!result) { CBX_ERR("push_deleg.target_highest_slotid"); goto out; } +out: + return result; +} + +static bool_t op_cb_push_deleg_res(XDR *xdr, struct cb_push_deleg_res *res) +{ + bool_t result; + + result = xdr_enum(xdr, &res->status); + if (!result) { CBX_ERR("push_deleg.status"); goto out; } +out: + return result; +} + +/* OP_CB_RECALL_ANY */ +static bool_t op_cb_recall_any_args(XDR *xdr, struct cb_recall_any_args *res) +{ + bool_t result; + + result = xdr_u_int32_t(xdr, &res->target_highest_slotid); + if (!result) { CBX_ERR("recall_any.target_highest_slotid"); goto out; } +out: + return result; +} + +static bool_t op_cb_recall_any_res(XDR *xdr, struct cb_recall_any_res *res) +{ + bool_t result; + + result = xdr_enum(xdr, &res->status); + if (!result) { CBX_ERR("recall_any.status"); goto out; } +out: + return result; +} + +/* OP_CB_RECALLABLE_OBJ_AVAIL */ +static bool_t op_cb_recallable_obj_avail_args(XDR *xdr, struct cb_recallable_obj_avail_args *res) +{ + bool_t result; + + result = xdr_u_int32_t(xdr, &res->target_highest_slotid); + if (!result) { CBX_ERR("recallable_obj_avail.target_highest_slotid"); goto out; } +out: + return result; +} + +static bool_t op_cb_recallable_obj_avail_res(XDR *xdr, struct cb_recallable_obj_avail_res *res) +{ + bool_t result; + + result = xdr_enum(xdr, &res->status); + if (!result) { CBX_ERR("recallable_obj_avail.status"); goto out; } +out: + return result; +} + +/* OP_CB_WANTS_CANCELLED */ +static bool_t op_cb_wants_cancelled_args(XDR *xdr, struct cb_wants_cancelled_args *res) +{ + bool_t result; + + result = xdr_u_int32_t(xdr, &res->target_highest_slotid); + if (!result) { CBX_ERR("wants_cancelled.target_highest_slotid"); goto out; } +out: + return result; +} + +static bool_t op_cb_wants_cancelled_res(XDR *xdr, struct cb_wants_cancelled_res *res) +{ + bool_t result; + + result = xdr_enum(xdr, &res->status); + if (!result) { CBX_ERR("wants_cancelled.status"); goto out; } +out: + return result; +} + +/* OP_CB_NOTIFY_LOCK */ +static bool_t op_cb_notify_lock_args(XDR *xdr, struct cb_notify_lock_args *res) +{ + bool_t result; + + result = xdr_u_int32_t(xdr, &res->target_highest_slotid); + if (!result) { CBX_ERR("notify_lock.target_highest_slotid"); goto out; } +out: + return result; +} + +static bool_t op_cb_notify_lock_res(XDR *xdr, struct cb_notify_lock_res *res) +{ + bool_t result; + + result = xdr_enum(xdr, &res->status); + if (!result) { CBX_ERR("notify_lock.status"); goto out; } +out: + return result; +} + +/* OP_CB_NOTIFY_DEVICEID */ +static bool_t cb_notify_deviceid_change(XDR *xdr, struct notify_deviceid4 *change) +{ + bool_t result; + + result = xdr_u_int32_t(xdr, (uint32_t*)&change->layouttype); + if (!result) { CBX_ERR("notify_deviceid.change.layouttype"); goto out; } + + result = xdr_opaque(xdr, (char*)change->deviceid, PNFS_DEVICEID_SIZE); + if (!result) { CBX_ERR("notify_deviceid.change.deviceid"); goto out; } + + result = xdr_bool(xdr, &change->immediate); + if (!result) { CBX_ERR("notify_deviceid.change.immediate"); goto out; } +out: + return result; +} + +static bool_t cb_notify_deviceid_delete(XDR *xdr, struct notify_deviceid4 *change) +{ + bool_t result; + + result = xdr_u_int32_t(xdr, (uint32_t*)&change->layouttype); + if (!result) { CBX_ERR("notify_deviceid.delete.layouttype"); goto out; } + + result = xdr_opaque(xdr, (char*)change->deviceid, PNFS_DEVICEID_SIZE); + if (!result) { CBX_ERR("notify_deviceid.delete.deviceid"); goto out; } +out: + return result; +} + +static bool_t op_cb_notify_deviceid_args(XDR *xdr, struct cb_notify_deviceid_args *args) +{ + XDR notify_xdr; + uint32_t i, j, c; + bool_t result; + + /* decode the generic notify4 list */ + result = xdr_array(xdr, (char**)&args->notify_list, + &args->notify_count, CB_COMPOUND_MAX_OPERATIONS, + sizeof(struct notify4), (xdrproc_t)common_notify4); + if (!result) { CBX_ERR("notify_deviceid.notify_list"); goto out; } + + switch (xdr->x_op) { + case XDR_FREE: + free(args->change_list); + case XDR_ENCODE: + return TRUE; + } + + /* count the number of device changes */ + args->change_count = 0; + for (i = 0; i < args->notify_count; i++) + args->change_count += args->notify_list[i].mask.count; + + args->change_list = calloc(args->change_count, sizeof(struct notify_deviceid4)); + if (args->change_list == NULL) + return FALSE; + + c = 0; + for (i = 0; i < args->notify_count; i++) { + struct notify4 *notify = &args->notify_list[i]; + + /* decode the device notifications out of the opaque buffer */ + xdrmem_create(¬ify_xdr, notify->list, notify->len, XDR_DECODE); + + for (j = 0; j < notify->mask.count; j++) { + struct notify_deviceid4 *change = &args->change_list[c++]; + change->type = notify->mask.arr[j]; + + switch (change->type) { + case NOTIFY_DEVICEID4_CHANGE: + result = cb_notify_deviceid_change(¬ify_xdr, change); + if (!result) { CBX_ERR("notify_deviceid.change"); goto out; } + break; + case NOTIFY_DEVICEID4_DELETE: + result = cb_notify_deviceid_delete(¬ify_xdr, change); + if (!result) { CBX_ERR("notify_deviceid.delete"); goto out; } + break; + } + } + } +out: + return result; +} + +static bool_t op_cb_notify_deviceid_res(XDR *xdr, struct cb_notify_deviceid_res *res) +{ + bool_t result; + + result = xdr_enum(xdr, &res->status); + if (!result) { CBX_ERR("notify_deviceid.status"); goto out; } +out: + return result; +} + +/* CB_COMPOUND */ +static bool_t cb_compound_tag(XDR *xdr, struct cb_compound_tag *args) +{ + return xdr_u_int32_t(xdr, &args->len) + && args->len <= CB_COMPOUND_MAX_TAG + && xdr_opaque(xdr, args->str, args->len); +} + +static const struct xdr_discrim cb_argop_discrim[] = { + { OP_CB_LAYOUTRECALL, (xdrproc_t)op_cb_layoutrecall_args }, + { OP_CB_RECALL_SLOT, (xdrproc_t)op_cb_recall_slot_args }, + { OP_CB_SEQUENCE, (xdrproc_t)op_cb_sequence_args }, + { OP_CB_GETATTR, (xdrproc_t)op_cb_getattr_args }, + { OP_CB_RECALL, (xdrproc_t)op_cb_recall_args }, + { OP_CB_NOTIFY, (xdrproc_t)op_cb_notify_args }, + { OP_CB_PUSH_DELEG, (xdrproc_t)op_cb_push_deleg_args }, + { OP_CB_RECALL_ANY, (xdrproc_t)op_cb_recall_any_args }, + { OP_CB_RECALLABLE_OBJ_AVAIL, (xdrproc_t)op_cb_recallable_obj_avail_args }, + { OP_CB_WANTS_CANCELLED, (xdrproc_t)op_cb_wants_cancelled_args }, + { OP_CB_NOTIFY_LOCK, (xdrproc_t)op_cb_notify_lock_args }, + { OP_CB_NOTIFY_DEVICEID, (xdrproc_t)op_cb_notify_deviceid_args }, + { OP_CB_ILLEGAL, NULL_xdrproc_t }, +}; + +static bool_t cb_compound_argop(XDR *xdr, struct cb_argop *args) +{ + bool_t result; + + result = xdr_union(xdr, &args->opnum, (char*)&args->args, + cb_argop_discrim, NULL_xdrproc_t); + if (!result) { CBX_ERR("cmb:argop.args"); goto out; } +out: + return result; +} + +bool_t proc_cb_compound_args(XDR *xdr, struct cb_compound_args *args) +{ + bool_t result; + + result = cb_compound_tag(xdr, &args->tag); + if (!result) { CBX_ERR("compound.tag"); goto out; } + + result = xdr_u_int32_t(xdr, &args->minorversion); + if (!result) { CBX_ERR("compound.minorversion"); goto out; } + + /* "superfluous in NFSv4.1 and MUST be ignored by the client" */ + result = xdr_u_int32_t(xdr, &args->callback_ident); + if (!result) { CBX_ERR("compound.callback_ident"); goto out; } + + result = xdr_array(xdr, (char**)&args->argarray, + &args->argarray_count, CB_COMPOUND_MAX_OPERATIONS, + sizeof(struct cb_argop), (xdrproc_t)cb_compound_argop); + if (!result) { CBX_ERR("compound.argarray"); goto out; } +out: + return result; +} + +static const struct xdr_discrim cb_resop_discrim[] = { + { OP_CB_LAYOUTRECALL, (xdrproc_t)op_cb_layoutrecall_res }, + { OP_CB_RECALL_SLOT, (xdrproc_t)op_cb_recall_slot_res }, + { OP_CB_SEQUENCE, (xdrproc_t)op_cb_sequence_res }, + { OP_CB_GETATTR, (xdrproc_t)op_cb_getattr_res }, + { OP_CB_RECALL, (xdrproc_t)op_cb_recall_res }, + { OP_CB_NOTIFY, (xdrproc_t)op_cb_notify_res }, + { OP_CB_PUSH_DELEG, (xdrproc_t)op_cb_push_deleg_res }, + { OP_CB_RECALL_ANY, (xdrproc_t)op_cb_recall_any_res }, + { OP_CB_RECALLABLE_OBJ_AVAIL, (xdrproc_t)op_cb_recallable_obj_avail_res }, + { OP_CB_WANTS_CANCELLED, (xdrproc_t)op_cb_wants_cancelled_res }, + { OP_CB_NOTIFY_LOCK, (xdrproc_t)op_cb_notify_lock_res }, + { OP_CB_NOTIFY_DEVICEID, (xdrproc_t)op_cb_notify_deviceid_res }, + { OP_CB_ILLEGAL, NULL_xdrproc_t }, +}; + +static bool_t cb_compound_resop(XDR *xdr, struct cb_resop *res) +{ + /* save xdr encode/decode status to see which operation failed */ + res->xdr_ok = xdr_union(xdr, &res->opnum, (char*)&res->res, + cb_resop_discrim, NULL_xdrproc_t); + if (!res->xdr_ok) { CBX_ERR("resop.res"); goto out; } +out: + return res->xdr_ok; +} + +bool_t proc_cb_compound_res(XDR *xdr, struct cb_compound_res *res) +{ + bool_t result; + + if (res == NULL) + return TRUE; + + result = xdr_enum(xdr, &res->status); + if (!result) { CBX_ERR("compound_res.status"); goto out; } + + result = cb_compound_tag(xdr, &res->tag); + if (!result) { CBX_ERR("compound_res.tag"); goto out; } + + result = xdr_array(xdr, (char**)&res->resarray, + &res->resarray_count, CB_COMPOUND_MAX_OPERATIONS, + sizeof(struct cb_resop), (xdrproc_t)cb_compound_resop); + if (!result) { CBX_ERR("compound_res.resarray"); goto out; } +out: + if (xdr->x_op == XDR_FREE) + free(res); + return result; +} diff --git a/reactos/base/services/nfsd/daemon_debug.c b/reactos/base/services/nfsd/daemon_debug.c new file mode 100644 index 00000000000..661c627b8af --- /dev/null +++ b/reactos/base/services/nfsd/daemon_debug.c @@ -0,0 +1,678 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include +#include + +#include "daemon_debug.h" +#include "from_kernel.h" +#include "nfs41_driver.h" +#include "nfs41_ops.h" +#include "service.h" +#include "rpc/rpc.h" +#include "rpc/auth_sspi.h" + +static int g_debug_level = DEFAULT_DEBUG_LEVEL; + +void set_debug_level(int level) { g_debug_level = level; } + +FILE *dlog_file, *elog_file; + +#ifndef STANDALONE_NFSD +void open_log_files() +{ + const char dfile[] = "nfsddbg.log"; + const char efile[] = "nfsderr.log"; + const char mode[] = "w"; + if (g_debug_level > 0) { + dlog_file = fopen(dfile, mode); + if (dlog_file == NULL) { + ReportStatusToSCMgr(SERVICE_STOPPED, GetLastError(), 0); + exit (GetLastError()); + } + } + elog_file = fopen(efile, mode); + if (elog_file == NULL) { + ReportStatusToSCMgr(SERVICE_STOPPED, GetLastError(), 0); + exit (GetLastError()); + } +} + +void close_log_files() +{ + if (dlog_file) fclose(dlog_file); + if (elog_file) fclose(elog_file); +} +#else +void open_log_files() +{ + dlog_file = stdout; + elog_file = stderr; +} +#endif + +void dprintf(int level, LPCSTR format, ...) +{ + if (level <= g_debug_level) { + va_list args; + va_start(args, format); + fprintf(dlog_file, "%04x: ", GetCurrentThreadId()); + vfprintf(dlog_file, format, args); +#ifndef STANDALONE_NFSD + fflush(dlog_file); +#endif + va_end(args); + } +} + +void eprintf(LPCSTR format, ...) +{ + va_list args; + va_start(args, format); + fprintf(elog_file, "%04x: ", GetCurrentThreadId()); + vfprintf(elog_file, format, args); +#ifndef STANDALONE_NFSD + fflush(elog_file); +#endif + va_end(args); +} + +void print_hexbuf(int level, unsigned char *title, unsigned char *buf, int len) +{ + int j, k; + if (level > g_debug_level) return; + fprintf(dlog_file, "%s", title); + for(j = 0, k = 0; j < len; j++, k++) { + fprintf(dlog_file, "%02x '%c' ", buf[j], isascii(buf[j])? buf[j]:' '); + if (((k+1) % 10 == 0 && k > 0)) { + fprintf(dlog_file, "\n"); + } + } + fprintf(dlog_file, "\n"); +} + +void print_hexbuf_no_asci(int level, unsigned char *title, unsigned char *buf, int len) +{ + int j, k; + if (level > g_debug_level) return; + fprintf(dlog_file, "%s", title); + for(j = 0, k = 0; j < len; j++, k++) { + fprintf(dlog_file, "%02x ", buf[j]); + if (((k+1) % 10 == 0 && k > 0)) { + fprintf(dlog_file, "\n"); + } + } + fprintf(dlog_file, "\n"); +} + +void print_create_attributes(int level, DWORD create_opts) { + if (level > g_debug_level) return; + fprintf(dlog_file, "create attributes: "); + if (create_opts & FILE_DIRECTORY_FILE) + fprintf(dlog_file, "DIRECTORY_FILE "); + if (create_opts & FILE_NON_DIRECTORY_FILE) + fprintf(dlog_file, "NON_DIRECTORY_FILE "); + if (create_opts & FILE_WRITE_THROUGH) + fprintf(dlog_file, "WRITE_THROUGH "); + if (create_opts & FILE_SEQUENTIAL_ONLY) + fprintf(dlog_file, "SEQUENTIAL_ONLY "); + if (create_opts & FILE_RANDOM_ACCESS) + fprintf(dlog_file, "RANDOM_ACCESS "); + if (create_opts & FILE_NO_INTERMEDIATE_BUFFERING) + fprintf(dlog_file, "NO_INTERMEDIATE_BUFFERING "); + if (create_opts & FILE_SYNCHRONOUS_IO_ALERT) + fprintf(dlog_file, "SYNCHRONOUS_IO_ALERT "); + if (create_opts & FILE_SYNCHRONOUS_IO_NONALERT) + fprintf(dlog_file, "SYNCHRONOUS_IO_NONALERT "); + if (create_opts & FILE_CREATE_TREE_CONNECTION) + fprintf(dlog_file, "CREATE_TREE_CONNECTION "); + if (create_opts & FILE_COMPLETE_IF_OPLOCKED) + fprintf(dlog_file, "COMPLETE_IF_OPLOCKED "); + if (create_opts & FILE_NO_EA_KNOWLEDGE) + fprintf(dlog_file, "NO_EA_KNOWLEDGE "); + if (create_opts & FILE_OPEN_REPARSE_POINT) + fprintf(dlog_file, "OPEN_REPARSE_POINT "); + if (create_opts & FILE_DELETE_ON_CLOSE) + fprintf(dlog_file, "DELETE_ON_CLOSE "); + if (create_opts & FILE_OPEN_BY_FILE_ID) + fprintf(dlog_file, "OPEN_BY_FILE_ID "); + if (create_opts & FILE_OPEN_FOR_BACKUP_INTENT) + fprintf(dlog_file, "OPEN_FOR_BACKUP_INTENT "); + if (create_opts & FILE_RESERVE_OPFILTER) + fprintf(dlog_file, "RESERVE_OPFILTER"); + fprintf(dlog_file, "\n"); +} + +void print_disposition(int level, DWORD disposition) { + if (level > g_debug_level) return; + fprintf(dlog_file, "userland disposition = "); + if (disposition == FILE_SUPERSEDE) + fprintf(dlog_file, "FILE_SUPERSEDE\n"); + else if (disposition == FILE_CREATE) + fprintf(dlog_file, "FILE_CREATE\n"); + else if (disposition == FILE_OPEN) + fprintf(dlog_file, "FILE_OPEN\n"); + else if (disposition == FILE_OPEN_IF) + fprintf(dlog_file, "FILE_OPEN_IF\n"); + else if (disposition == FILE_OVERWRITE) + fprintf(dlog_file, "FILE_OVERWRITE\n"); + else if (disposition == FILE_OVERWRITE_IF) + fprintf(dlog_file, "FILE_OVERWRITE_IF\n"); +} + +void print_access_mask(int level, DWORD access_mask) { + if (level > g_debug_level) return; + fprintf(dlog_file, "access mask: "); + if (access_mask & FILE_READ_DATA) + fprintf(dlog_file, "READ "); + if (access_mask & STANDARD_RIGHTS_READ) + fprintf(dlog_file, "READ_ACL "); + if (access_mask & FILE_READ_ATTRIBUTES) + fprintf(dlog_file, "READ_ATTR "); + if (access_mask & FILE_READ_EA) + fprintf(dlog_file, "READ_EA "); + if (access_mask & FILE_WRITE_DATA) + fprintf(dlog_file, "WRITE "); + if (access_mask & STANDARD_RIGHTS_WRITE) + fprintf(dlog_file, "WRITE_ACL "); + if (access_mask & FILE_WRITE_ATTRIBUTES) + fprintf(dlog_file, "WRITE_ATTR "); + if (access_mask & FILE_WRITE_EA) + fprintf(dlog_file, "WRITE_EA "); + if (access_mask & FILE_APPEND_DATA) + fprintf(dlog_file, "APPEND "); + if (access_mask & FILE_EXECUTE) + fprintf(dlog_file, "EXECUTE "); + if (access_mask & FILE_LIST_DIRECTORY) + fprintf(dlog_file, "LIST "); + if (access_mask & FILE_TRAVERSE) + fprintf(dlog_file, "TRAVERSE "); + if (access_mask & SYNCHRONIZE) + fprintf(dlog_file, "SYNC "); + if (access_mask & FILE_DELETE_CHILD) + fprintf(dlog_file, "DELETE_CHILD"); + fprintf(dlog_file, "\n"); +} + +void print_share_mode(int level, DWORD mode) +{ + if (level > g_debug_level) return; + fprintf(dlog_file, "share mode: "); + if (mode & FILE_SHARE_READ) + fprintf(dlog_file, "READ "); + if (mode & FILE_SHARE_WRITE) + fprintf(dlog_file, "WRITE "); + if (mode & FILE_SHARE_DELETE) + fprintf(dlog_file, "DELETE"); + fprintf(dlog_file, "\n"); +} + +void print_file_id_both_dir_info(int level, FILE_ID_BOTH_DIR_INFO *pboth_dir_info) +{ + if (level > g_debug_level) return; + fprintf(dlog_file, "FILE_ID_BOTH_DIR_INFO %p %d\n", + pboth_dir_info, sizeof(unsigned char *)); + fprintf(dlog_file, "\tNextEntryOffset=%ld %d %d\n", + pboth_dir_info->NextEntryOffset, + sizeof(pboth_dir_info->NextEntryOffset), sizeof(DWORD)); + fprintf(dlog_file, "\tFileIndex=%ld %d\n", pboth_dir_info->FileIndex, + sizeof(pboth_dir_info->FileIndex)); + fprintf(dlog_file, "\tCreationTime=0x%x %d\n", + pboth_dir_info->CreationTime.QuadPart, + sizeof(pboth_dir_info->CreationTime)); + fprintf(dlog_file, "\tLastAccessTime=0x%x %d\n", + pboth_dir_info->LastAccessTime.QuadPart, + sizeof(pboth_dir_info->LastAccessTime)); + fprintf(dlog_file, "\tLastWriteTime=0x%x %d\n", + pboth_dir_info->LastWriteTime.QuadPart, + sizeof(pboth_dir_info->LastWriteTime)); + fprintf(dlog_file, "\tChangeTime=0x%x %d\n", + pboth_dir_info->ChangeTime.QuadPart, + sizeof(pboth_dir_info->ChangeTime)); + fprintf(dlog_file, "\tEndOfFile=0x%x %d\n", + pboth_dir_info->EndOfFile.QuadPart, + sizeof(pboth_dir_info->EndOfFile)); + fprintf(dlog_file, "\tAllocationSize=0x%x %d\n", + pboth_dir_info->AllocationSize.QuadPart, + sizeof(pboth_dir_info->AllocationSize)); + fprintf(dlog_file, "\tFileAttributes=%ld %d\n", + pboth_dir_info->FileAttributes, + sizeof(pboth_dir_info->FileAttributes)); + fprintf(dlog_file, "\tFileNameLength=%ld %d\n", + pboth_dir_info->FileNameLength, + sizeof(pboth_dir_info->FileNameLength)); + fprintf(dlog_file, "\tEaSize=%ld %d\n", + pboth_dir_info->EaSize, sizeof(pboth_dir_info->EaSize)); + fprintf(dlog_file, "\tShortNameLength=%d %d\n", + pboth_dir_info->ShortNameLength, + sizeof(pboth_dir_info->ShortNameLength)); + fprintf(dlog_file, "\tShortName='%S' %d\n", pboth_dir_info->ShortName, + sizeof(pboth_dir_info->ShortName)); + fprintf(dlog_file, "\tFileId=0x%x %d\n", pboth_dir_info->FileId.QuadPart, + sizeof(pboth_dir_info->FileId)); + fprintf(dlog_file, "\tFileName='%S' %p\n", pboth_dir_info->FileName, + pboth_dir_info->FileName); +} + +void print_opcode(int level, DWORD opcode) +{ + dprintf(level, (LPCSTR)opcode2string(opcode)); +} + +const char* opcode2string(DWORD opcode) +{ + switch(opcode) { + case NFS41_SHUTDOWN: return "NFS41_SHUTDOWN"; + case NFS41_MOUNT: return "NFS41_MOUNT"; + case NFS41_UNMOUNT: return "NFS41_UNMOUNT"; + case NFS41_OPEN: return "NFS41_OPEN"; + case NFS41_CLOSE: return "NFS41_CLOSE"; + case NFS41_READ: return "NFS41_READ"; + case NFS41_WRITE: return "NFS41_WRITE"; + case NFS41_LOCK: return "NFS41_LOCK"; + case NFS41_UNLOCK: return "NFS41_UNLOCK"; + case NFS41_DIR_QUERY: return "NFS41_DIR_QUERY"; + case NFS41_FILE_QUERY: return "NFS41_FILE_QUERY"; + case NFS41_FILE_SET: return "NFS41_FILE_SET"; + case NFS41_EA_SET: return "NFS41_EA_SET"; + case NFS41_EA_GET: return "NFS41_EA_GET"; + case NFS41_SYMLINK: return "NFS41_SYMLINK"; + case NFS41_VOLUME_QUERY: return "NFS41_VOLUME_QUERY"; + case NFS41_ACL_QUERY: return "NFS41_ACL_QUERY"; + case NFS41_ACL_SET: return "NFS41_ACL_SET"; + default: return "UNKNOWN"; + } +} + +const char* nfs_opnum_to_string(int opnum) +{ + switch (opnum) + { + case OP_ACCESS: return "ACCESS"; + case OP_CLOSE: return "CLOSE"; + case OP_COMMIT: return "COMMIT"; + case OP_CREATE: return "CREATE"; + case OP_DELEGPURGE: return "DELEGPURGE"; + case OP_DELEGRETURN: return "DELEGRETURN"; + case OP_GETATTR: return "GETATTR"; + case OP_GETFH: return "GETFH"; + case OP_LINK: return "LINK"; + case OP_LOCK: return "LOCK"; + case OP_LOCKT: return "LOCKT"; + case OP_LOCKU: return "LOCKU"; + case OP_LOOKUP: return "LOOKUP"; + case OP_LOOKUPP: return "LOOKUPP"; + case OP_NVERIFY: return "NVERIFY"; + case OP_OPEN: return "OPEN"; + case OP_OPENATTR: return "OPENATTR"; + case OP_OPEN_CONFIRM: return "OPEN_CONFIRM"; + case OP_OPEN_DOWNGRADE: return "OPEN_DOWNGRADE"; + case OP_PUTFH: return "PUTFH"; + case OP_PUTPUBFH: return "PUTPUBFH"; + case OP_PUTROOTFH: return "PUTROOTFH"; + case OP_READ: return "READ"; + case OP_READDIR: return "READDIR"; + case OP_READLINK: return "READLINK"; + case OP_REMOVE: return "REMOVE"; + case OP_RENAME: return "RENAME"; + case OP_RENEW: return "RENEW"; + case OP_RESTOREFH: return "RESTOREFH"; + case OP_SAVEFH: return "SAVEFH"; + case OP_SECINFO: return "SECINFO"; + case OP_SETATTR: return "SETATTR"; + case OP_SETCLIENTID: return "SETCLIENTID"; + case OP_SETCLIENTID_CONFIRM: return "SETCLIENTID_CONFIRM"; + case OP_VERIFY: return "VERIFY"; + case OP_WRITE: return "WRITE"; + case OP_RELEASE_LOCKOWNER: return "RELEASE_LOCKOWNER"; + case OP_BACKCHANNEL_CTL: return "BACKCHANNEL_CTL"; + case OP_BIND_CONN_TO_SESSION: return "BIND_CONN_TO_SESSION"; + case OP_EXCHANGE_ID: return "EXCHANGE_ID"; + case OP_CREATE_SESSION: return "CREATE_SESSION"; + case OP_DESTROY_SESSION: return "DESTROY_SESSION"; + case OP_FREE_STATEID: return "FREE_STATEID"; + case OP_GET_DIR_DELEGATION: return "GET_DIR_DELEGATION"; + case OP_GETDEVICEINFO: return "GETDEVICEINFO"; + case OP_GETDEVICELIST: return "GETDEVICELIST"; + case OP_LAYOUTCOMMIT: return "LAYOUTCOMMIT"; + case OP_LAYOUTGET: return "LAYOUTGET"; + case OP_LAYOUTRETURN: return "LAYOUTRETURN"; + case OP_SECINFO_NO_NAME: return "SECINFO_NO_NAME"; + case OP_SEQUENCE: return "SEQUENCE"; + case OP_SET_SSV: return "SET_SSV"; + case OP_TEST_STATEID: return "TEST_STATEID"; + case OP_WANT_DELEGATION: return "WANT_DELEGATION"; + case OP_DESTROY_CLIENTID: return "DESTROY_CLIENTID"; + case OP_RECLAIM_COMPLETE: return "RECLAIM_COMPLETE"; + case OP_ILLEGAL: return "ILLEGAL"; + default: return "invalid nfs opnum"; + } +} + +const char* nfs_error_string(int status) +{ + switch (status) + { + case NFS4_OK: return "NFS4_OK"; + case NFS4ERR_PERM: return "NFS4ERR_PERM"; + case NFS4ERR_NOENT: return "NFS4ERR_NOENT"; + case NFS4ERR_IO: return "NFS4ERR_IO"; + case NFS4ERR_NXIO: return "NFS4ERR_NXIO"; + case NFS4ERR_ACCESS: return "NFS4ERR_ACCESS"; + case NFS4ERR_EXIST: return "NFS4ERR_EXIST"; + case NFS4ERR_XDEV: return "NFS4ERR_XDEV"; + case NFS4ERR_NOTDIR: return "NFS4ERR_NOTDIR"; + case NFS4ERR_ISDIR: return "NFS4ERR_ISDIR"; + case NFS4ERR_INVAL: return "NFS4ERR_INVAL"; + case NFS4ERR_FBIG: return "NFS4ERR_FBIG"; + case NFS4ERR_NOSPC: return "NFS4ERR_NOSPC"; + case NFS4ERR_ROFS: return "NFS4ERR_ROFS"; + case NFS4ERR_MLINK: return "NFS4ERR_MLINK"; + case NFS4ERR_NAMETOOLONG: return "NFS4ERR_NAMETOOLONG"; + case NFS4ERR_NOTEMPTY: return "NFS4ERR_NOTEMPTY"; + case NFS4ERR_DQUOT: return "NFS4ERR_DQUOT"; + case NFS4ERR_STALE: return "NFS4ERR_STALE"; + case NFS4ERR_BADHANDLE: return "NFS4ERR_BADHANDLE"; + case NFS4ERR_BAD_COOKIE: return "NFS4ERR_BAD_COOKIE"; + case NFS4ERR_NOTSUPP: return "NFS4ERR_NOTSUPP"; + case NFS4ERR_TOOSMALL: return "NFS4ERR_TOOSMALL"; + case NFS4ERR_SERVERFAULT: return "NFS4ERR_SERVERFAULT"; + case NFS4ERR_BADTYPE: return "NFS4ERR_BADTYPE"; + case NFS4ERR_DELAY: return "NFS4ERR_DELAY"; + case NFS4ERR_SAME: return "NFS4ERR_SAME"; + case NFS4ERR_DENIED: return "NFS4ERR_DENIED"; + case NFS4ERR_EXPIRED: return "NFS4ERR_EXPIRED"; + case NFS4ERR_LOCKED: return "NFS4ERR_LOCKED"; + case NFS4ERR_GRACE: return "NFS4ERR_GRACE"; + case NFS4ERR_FHEXPIRED: return "NFS4ERR_FHEXPIRED"; + case NFS4ERR_SHARE_DENIED: return "NFS4ERR_SHARE_DENIED"; + case NFS4ERR_WRONGSEC: return "NFS4ERR_WRONGSEC"; + case NFS4ERR_CLID_INUSE: return "NFS4ERR_CLID_INUSE"; + case NFS4ERR_RESOURCE: return "NFS4ERR_RESOURCE"; + case NFS4ERR_MOVED: return "NFS4ERR_MOVED"; + case NFS4ERR_NOFILEHANDLE: return "NFS4ERR_NOFILEHANDLE"; + case NFS4ERR_MINOR_VERS_MISMATCH: return "NFS4ERR_MINOR_VERS_MISMATCH"; + case NFS4ERR_STALE_CLIENTID: return "NFS4ERR_STALE_CLIENTID"; + case NFS4ERR_STALE_STATEID: return "NFS4ERR_STALE_STATEID"; + case NFS4ERR_OLD_STATEID: return "NFS4ERR_OLD_STATEID"; + case NFS4ERR_BAD_STATEID: return "NFS4ERR_BAD_STATEID"; + case NFS4ERR_BAD_SEQID: return "NFS4ERR_BAD_SEQID"; + case NFS4ERR_NOT_SAME: return "NFS4ERR_NOT_SAME"; + case NFS4ERR_LOCK_RANGE: return "NFS4ERR_LOCK_RANGE"; + case NFS4ERR_SYMLINK: return "NFS4ERR_SYMLINK"; + case NFS4ERR_RESTOREFH: return "NFS4ERR_RESTOREFH"; + case NFS4ERR_LEASE_MOVED: return "NFS4ERR_LEASE_MOVED"; + case NFS4ERR_ATTRNOTSUPP: return "NFS4ERR_ATTRNOTSUPP"; + case NFS4ERR_NO_GRACE: return "NFS4ERR_NO_GRACE"; + case NFS4ERR_RECLAIM_BAD: return "NFS4ERR_RECLAIM_BAD"; + case NFS4ERR_RECLAIM_CONFLICT: return "NFS4ERR_RECLAIM_CONFLICT"; + case NFS4ERR_BADXDR: return "NFS4ERR_BADXDR"; + case NFS4ERR_LOCKS_HELD: return "NFS4ERR_LOCKS_HELD"; + case NFS4ERR_OPENMODE: return "NFS4ERR_OPENMODE"; + case NFS4ERR_BADOWNER: return "NFS4ERR_BADOWNER"; + case NFS4ERR_BADCHAR: return "NFS4ERR_BADCHAR"; + case NFS4ERR_BADNAME: return "NFS4ERR_BADNAME"; + case NFS4ERR_BAD_RANGE: return "NFS4ERR_BAD_RANGE"; + case NFS4ERR_LOCK_NOTSUPP: return "NFS4ERR_LOCK_NOTSUPP"; + case NFS4ERR_OP_ILLEGAL: return "NFS4ERR_OP_ILLEGAL"; + case NFS4ERR_DEADLOCK: return "NFS4ERR_DEADLOCK"; + case NFS4ERR_FILE_OPEN: return "NFS4ERR_FILE_OPEN"; + case NFS4ERR_ADMIN_REVOKED: return "NFS4ERR_ADMIN_REVOKED"; + case NFS4ERR_CB_PATH_DOWN: return "NFS4ERR_CB_PATH_DOWN"; + case NFS4ERR_BADIOMODE: return "NFS4ERR_BADIOMODE"; + case NFS4ERR_BADLAYOUT: return "NFS4ERR_BADLAYOUT"; + case NFS4ERR_BAD_SESSION_DIGEST: return "NFS4ERR_BAD_SESSION_DIGEST"; + case NFS4ERR_BADSESSION: return "NFS4ERR_BADSESSION"; + case NFS4ERR_BADSLOT: return "NFS4ERR_BADSLOT"; + case NFS4ERR_COMPLETE_ALREADY: return "NFS4ERR_COMPLETE_ALREADY"; + case NFS4ERR_CONN_NOT_BOUND_TO_SESSION: return "NFS4ERR_CONN_NOT_BOUND_TO_SESSION"; + case NFS4ERR_DELEG_ALREADY_WANTED: return "NFS4ERR_DELEG_ALREADY_WANTED"; + case NFS4ERR_BACK_CHAN_BUSY: return "NFS4ERR_BACK_CHAN_BUSY"; + case NFS4ERR_LAYOUTTRYLATER: return "NFS4ERR_LAYOUTTRYLATER"; + case NFS4ERR_LAYOUTUNAVAILABLE: return "NFS4ERR_LAYOUTUNAVAILABLE"; + case NFS4ERR_NOMATCHING_LAYOUT: return "NFS4ERR_NOMATCHING_LAYOUT"; + case NFS4ERR_RECALLCONFLICT: return "NFS4ERR_RECALLCONFLICT"; + case NFS4ERR_UNKNOWN_LAYOUTTYPE: return "NFS4ERR_UNKNOWN_LAYOUTTYPE"; + case NFS4ERR_SEQ_MISORDERED: return "NFS4ERR_SEQ_MISORDERED"; + case NFS4ERR_SEQUENCE_POS: return "NFS4ERR_SEQUENCE_POS"; + case NFS4ERR_REQ_TOO_BIG: return "NFS4ERR_REQ_TOO_BIG"; + case NFS4ERR_REP_TOO_BIG: return "NFS4ERR_REP_TOO_BIG"; + case NFS4ERR_REP_TOO_BIG_TO_CACHE: return "NFS4ERR_REP_TOO_BIG_TO_CACHE"; + case NFS4ERR_RETRY_UNCACHED_REP: return "NFS4ERR_RETRY_UNCACHED_REP"; + case NFS4ERR_UNSAFE_COMPOUND: return "NFS4ERR_UNSAFE_COMPOUND"; + case NFS4ERR_TOO_MANY_OPS: return "NFS4ERR_TOO_MANY_OPS"; + case NFS4ERR_OP_NOT_IN_SESSION: return "NFS4ERR_OP_NOT_IN_SESSION"; + case NFS4ERR_HASH_ALG_UNSUPP: return "NFS4ERR_HASH_ALG_UNSUPP"; + case NFS4ERR_CLIENTID_BUSY: return "NFS4ERR_CLIENTID_BUSY"; + case NFS4ERR_PNFS_IO_HOLE: return "NFS4ERR_PNFS_IO_HOLE"; + case NFS4ERR_SEQ_FALSE_RETRY: return "NFS4ERR_SEQ_FALSE_RETRY"; + case NFS4ERR_BAD_HIGH_SLOT: return "NFS4ERR_BAD_HIGH_SLOT"; + case NFS4ERR_DEADSESSION: return "NFS4ERR_DEADSESSION"; + case NFS4ERR_ENCR_ALG_UNSUPP: return "NFS4ERR_ENCR_ALG_UNSUPP"; + case NFS4ERR_PNFS_NO_LAYOUT: return "NFS4ERR_PNFS_NO_LAYOUT"; + case NFS4ERR_NOT_ONLY_OP: return "NFS4ERR_NOT_ONLY_OP"; + case NFS4ERR_WRONG_CRED: return "NFS4ERR_WRONG_CRED"; + case NFS4ERR_WRONG_TYPE: return "NFS4ERR_WRONG_TYPE"; + case NFS4ERR_DIRDELEG_UNAVAIL: return "NFS4ERR_DIRDELEG_UNAVAIL"; + case NFS4ERR_REJECT_DELEG: return "NFS4ERR_REJECT_DELEG"; + case NFS4ERR_RETURNCONFLICT: return "NFS4ERR_RETURNCONFLICT"; + case NFS4ERR_DELEG_REVOKED: return "NFS4ERR_DELEG_REVOKED"; + default: return "invalid nfs error code"; + } +} + +const char* rpc_error_string(int status) +{ + switch (status) + { + case RPC_CANTENCODEARGS: return "RPC_CANTENCODEARGS"; + case RPC_CANTDECODERES: return "RPC_CANTDECODERES"; + case RPC_CANTSEND: return "RPC_CANTSEND"; + case RPC_CANTRECV: return "RPC_CANTRECV"; + case RPC_TIMEDOUT: return "RPC_TIMEDOUT"; + case RPC_INTR: return "RPC_INTR"; + case RPC_UDERROR: return "RPC_UDERROR"; + case RPC_VERSMISMATCH: return "RPC_VERSMISMATCH"; + case RPC_AUTHERROR: return "RPC_AUTHERROR"; + case RPC_PROGUNAVAIL: return "RPC_PROGUNAVAIL"; + case RPC_PROGVERSMISMATCH: return "RPC_PROGVERSMISMATCH"; + case RPC_PROCUNAVAIL: return "RPC_PROCUNAVAIL"; + case RPC_CANTDECODEARGS: return "RPC_CANTDECODEARGS"; + case RPC_SYSTEMERROR: return "RPC_SYSTEMERROR"; + default: return "invalid rpc error code"; + } +} + +const char* gssauth_string(int type) { + switch(type) { + case RPCSEC_SSPI_SVC_NONE: return "RPCSEC_SSPI_SVC_NONE"; + case RPCSEC_SSPI_SVC_INTEGRITY: return "RPCSEC_SSPI_SVC_INTEGRITY"; + case RPCSEC_SSPI_SVC_PRIVACY: return "RPCSEC_SSPI_SVC_PRIVACY"; + default: return "invalid gss auth type"; + } +} + +void print_condwait_status(int level, int status) +{ + if (level > g_debug_level) return; + switch(status) { + case WAIT_ABANDONED: fprintf(dlog_file, "WAIT_ABANDONED\n"); break; + case WAIT_OBJECT_0: fprintf(dlog_file, "WAIT_OBJECT_0\n"); break; + case WAIT_TIMEOUT: fprintf(dlog_file, "WAIT_TIMEOUT\n"); break; + case WAIT_FAILED: fprintf(dlog_file, "WAIT_FAILED %d\n", GetLastError()); + default: fprintf(dlog_file, "unknown status =%d\n", status); + } +} + +void print_sr_status_flags(int level, int flags) +{ + if (level > g_debug_level) return; + fprintf(dlog_file, "%04x: sr_status_flags: ", GetCurrentThreadId()); + if (flags & SEQ4_STATUS_CB_PATH_DOWN) + fprintf(dlog_file, "SEQ4_STATUS_CB_PATH_DOWN "); + if (flags & SEQ4_STATUS_CB_GSS_CONTEXTS_EXPIRING) + fprintf(dlog_file, "SEQ4_STATUS_CB_GSS_CONTEXTS_EXPIRING "); + if (flags & SEQ4_STATUS_CB_GSS_CONTEXTS_EXPIRED) + fprintf(dlog_file, "SEQ4_STATUS_CB_GSS_CONTEXTS_EXPIRED "); + if (flags & SEQ4_STATUS_EXPIRED_ALL_STATE_REVOKED) + fprintf(dlog_file, "SEQ4_STATUS_EXPIRED_ALL_STATE_REVOKED "); + if (flags & SEQ4_STATUS_EXPIRED_SOME_STATE_REVOKED) + fprintf(dlog_file, "SEQ4_STATUS_EXPIRED_SOME_STATE_REVOKED "); + if (flags & SEQ4_STATUS_ADMIN_STATE_REVOKED) + fprintf(dlog_file, "SEQ4_STATUS_ADMIN_STATE_REVOKED "); + if (flags & SEQ4_STATUS_RECALLABLE_STATE_REVOKED) + fprintf(dlog_file, "SEQ4_STATUS_RECALLABLE_STATE_REVOKED "); + if (flags & SEQ4_STATUS_LEASE_MOVED) + fprintf(dlog_file, "SEQ4_STATUS_LEASE_MOVED "); + if (flags & SEQ4_STATUS_RESTART_RECLAIM_NEEDED) + fprintf(dlog_file, "SEQ4_STATUS_RESTART_RECLAIM_NEEDED "); + if (flags & SEQ4_STATUS_CB_PATH_DOWN_SESSION) + fprintf(dlog_file, "SEQ4_STATUS_CB_PATH_DOWN_SESSION "); + if (flags & SEQ4_STATUS_BACKCHANNEL_FAULT) + fprintf(dlog_file, "SEQ4_STATUS_BACKCHANNEL_FAULT "); + if (flags & SEQ4_STATUS_DEVID_CHANGED) + fprintf(dlog_file, "SEQ4_STATUS_DEVID_CHANGED "); + if (flags & SEQ4_STATUS_DEVID_DELETED) + fprintf(dlog_file, "SEQ4_STATUS_DEVID_DELETED "); + fprintf(dlog_file, "\n"); +} + +const char* secflavorop2name(DWORD sec_flavor) +{ + switch(sec_flavor) { + case RPCSEC_AUTH_SYS: return "AUTH_SYS"; + case RPCSEC_AUTHGSS_KRB5: return "AUTHGSS_KRB5"; + case RPCSEC_AUTHGSS_KRB5I: return "AUTHGSS_KRB5I"; + case RPCSEC_AUTHGSS_KRB5P: return "AUTHGSS_KRB5P"; + } + + return "UNKNOWN FLAVOR"; +} + +void print_windows_access_mask(int on, ACCESS_MASK m) +{ + if (!on) return; + dprintf(1, "--> print_windows_access_mask: %x\n", m); + if (m & GENERIC_READ) + dprintf(1, "\tGENERIC_READ\n"); + if (m & GENERIC_WRITE) + dprintf(1, "\tGENERIC_WRITE\n"); + if (m & GENERIC_EXECUTE) + dprintf(1, "\tGENERIC_EXECUTE\n"); + if (m & GENERIC_ALL) + dprintf(1, "\tGENERIC_ALL\n"); + if (m & MAXIMUM_ALLOWED) + dprintf(1, "\tMAXIMUM_ALLOWED\n"); + if (m & ACCESS_SYSTEM_SECURITY) + dprintf(1, "\tACCESS_SYSTEM_SECURITY\n"); + if ((m & SPECIFIC_RIGHTS_ALL) == SPECIFIC_RIGHTS_ALL) + dprintf(1, "\tSPECIFIC_RIGHTS_ALL\n"); + if ((m & STANDARD_RIGHTS_ALL) == STANDARD_RIGHTS_ALL) + dprintf(1, "\tSTANDARD_RIGHTS_ALL\n"); + if ((m & STANDARD_RIGHTS_REQUIRED) == STANDARD_RIGHTS_REQUIRED) + dprintf(1, "\tSTANDARD_RIGHTS_REQUIRED\n"); + if (m & SYNCHRONIZE) + dprintf(1, "\tSYNCHRONIZE\n"); + if (m & WRITE_OWNER) + dprintf(1, "\tWRITE_OWNER\n"); + if (m & WRITE_DAC) + dprintf(1, "\tWRITE_DAC\n"); + if (m & READ_CONTROL) + dprintf(1, "\tREAD_CONTROL\n"); + if (m & DELETE) + dprintf(1, "\tDELETE\n"); + if (m & FILE_READ_DATA) + dprintf(1, "\tFILE_READ_DATA\n"); + if (m & FILE_LIST_DIRECTORY) + dprintf(1, "\tFILE_LIST_DIRECTORY\n"); + if (m & FILE_WRITE_DATA) + dprintf(1, "\tFILE_WRITE_DATA\n"); + if (m & FILE_ADD_FILE) + dprintf(1, "\tFILE_ADD_FILE\n"); + if (m & FILE_APPEND_DATA) + dprintf(1, "\tFILE_APPEND_DATA\n"); + if (m & FILE_ADD_SUBDIRECTORY) + dprintf(1, "\tFILE_ADD_SUBDIRECTORY\n"); + if (m & FILE_CREATE_PIPE_INSTANCE) + dprintf(1, "\tFILE_CREATE_PIPE_INSTANCE\n"); + if (m & FILE_READ_EA) + dprintf(1, "\tFILE_READ_EA\n"); + if (m & FILE_WRITE_EA) + dprintf(1, "\tFILE_WRITE_EA\n"); + if (m & FILE_EXECUTE) + dprintf(1, "\tFILE_EXECUTE\n"); + if (m & FILE_TRAVERSE) + dprintf(1, "\tFILE_TRAVERSE\n"); + if (m & FILE_DELETE_CHILD) + dprintf(1, "\tFILE_DELETE_CHILD\n"); + if (m & FILE_READ_ATTRIBUTES) + dprintf(1, "\tFILE_READ_ATTRIBUTES\n"); + if (m & FILE_WRITE_ATTRIBUTES) + dprintf(1, "\tFILE_WRITE_ATTRIBUTES\n"); + if ((m & FILE_ALL_ACCESS) == FILE_ALL_ACCESS) + dprintf(1, "\tFILE_ALL_ACCESS\n"); + if ((m & FILE_GENERIC_READ) == FILE_GENERIC_READ) + dprintf(1, "\tFILE_GENERIC_READ\n"); + if ((m & FILE_GENERIC_WRITE) == FILE_GENERIC_WRITE) + dprintf(1, "\tFILE_GENERIC_WRITE\n"); + if ((m & FILE_GENERIC_EXECUTE) == FILE_GENERIC_EXECUTE) + dprintf(1, "\tFILE_GENERIC_EXECUTE\n"); +} + +void print_nfs_access_mask(int on, int m) +{ + if (!on) return; + dprintf(1, "--> print_nfs_access_mask: %x\n", m); + if (m & ACE4_READ_DATA) + dprintf(1, "\tACE4_READ_DATA\n"); + if (m & ACE4_LIST_DIRECTORY) + dprintf(1, "\tACE4_LIST_DIRECTORY\n"); + if (m & ACE4_WRITE_DATA) + dprintf(1, "\tACE4_WRITE_DATA\n"); + if (m & ACE4_ADD_FILE) + dprintf(1, "\tACE4_ADD_FILE\n"); + if (m & ACE4_APPEND_DATA) + dprintf(1, "\tACE4_APPEND_DATA\n"); + if (m & ACE4_ADD_SUBDIRECTORY) + dprintf(1, "\tACE4_ADD_SUBDIRECTORY\n"); + if (m & ACE4_READ_NAMED_ATTRS) + dprintf(1, "\tACE4_READ_NAMED_ATTRS\n"); + if (m & ACE4_WRITE_NAMED_ATTRS) + dprintf(1, "\tACE4_WRITE_NAMED_ATTRS\n"); + if (m & ACE4_EXECUTE) + dprintf(1, "\tACE4_EXECUTE\n"); + if (m & ACE4_DELETE_CHILD) + dprintf(1, "\tACE4_DELETE_CHILD\n"); + if (m & ACE4_READ_ATTRIBUTES) + dprintf(1, "\tACE4_READ_ATTRIBUTES\n"); + if (m & ACE4_WRITE_ATTRIBUTES) + dprintf(1, "\tACE4_WRITE_ATTRIBUTES\n"); + if (m & ACE4_DELETE) + dprintf(1, "\tACE4_DELETE\n"); + if (m & ACE4_READ_ACL) + dprintf(1, "\tACE4_READ_ACL\n"); + if (m & ACE4_WRITE_ACL) + dprintf(1, "\tACE4_WRITE_ACL\n"); + if (m & ACE4_WRITE_OWNER) + dprintf(1, "\tACE4_WRITE_OWNER\n"); + if (m & ACE4_SYNCHRONIZE) + dprintf(1, "\tACE4_SYNCHRONIZE\n"); +} diff --git a/reactos/base/services/nfsd/daemon_debug.h b/reactos/base/services/nfsd/daemon_debug.h new file mode 100644 index 00000000000..5b4f247419f --- /dev/null +++ b/reactos/base/services/nfsd/daemon_debug.h @@ -0,0 +1,78 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#ifndef _DAEMON_DEBUG_ +#define _DAEMON_DEBUG_ + +#ifdef _DEBUG +/* use visual studio's debug heap */ +# define _CRTDBG_MAP_ALLOC +# include +# include +#else +# include +#endif + +#define DEFAULT_DEBUG_LEVEL 1 + + +/* daemon_debug.h */ +void set_debug_level(int level); +void dprintf(int level, LPCSTR format, ...); +void eprintf(LPCSTR format, ...); + +void print_windows_access_mask(int on, ACCESS_MASK m); +void print_nfs_access_mask(int on, int m); +void print_hexbuf_no_asci(int on, unsigned char *title, unsigned char *buf, int len); +void print_hexbuf(int level, unsigned char *title, unsigned char *buf, int len); +void print_create_attributes(int level, DWORD create_opts); +void print_disposition(int level, DWORD disposition); +void print_access_mask(int level, DWORD access_mask); +void print_share_mode(int level, DWORD mode); +void print_file_id_both_dir_info(int level, FILE_ID_BOTH_DIR_INFO *p); +void print_opcode(int level, DWORD opcode); +const char* opcode2string(DWORD opcode); +const char* nfs_opnum_to_string(int opnum); +const char* nfs_error_string(int status); +const char* rpc_error_string(int status); +const char* gssauth_string(int type); +void print_condwait_status(int level, int status); +void print_sr_status_flags(int level, int flags); +void open_log_files(); +void close_log_files(); +const char* secflavorop2name(DWORD sec_flavor); + +/* pnfs_debug.c */ +enum pnfs_status; +enum pnfs_layout_type; +enum pnfs_iomode; +struct __pnfs_file_layout; +struct __pnfs_file_device; + +const char* pnfs_error_string(enum pnfs_status status); +const char* pnfs_layout_type_string(enum pnfs_layout_type type); +const char* pnfs_iomode_string(enum pnfs_iomode iomode); + +void dprint_deviceid(int level, const char *title, const unsigned char *deviceid); +void dprint_layout(int level, const struct __pnfs_file_layout *layout); +void dprint_device(int level, const struct __pnfs_file_device *device); + +#endif diff --git a/reactos/base/services/nfsd/delegation.c b/reactos/base/services/nfsd/delegation.c new file mode 100644 index 00000000000..994134fe817 --- /dev/null +++ b/reactos/base/services/nfsd/delegation.c @@ -0,0 +1,941 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include "delegation.h" +#include "nfs41_ops.h" +#include "name_cache.h" +#include "util.h" +#include "daemon_debug.h" + +#include +#include "nfs41_driver.h" /* for making downcall to invalidate cache */ +#include "util.h" + +#define DGLVL 2 /* dprintf level for delegation logging */ + + +/* allocation and reference counting */ +static int delegation_create( + IN const nfs41_path_fh *parent, + IN const nfs41_path_fh *file, + IN const open_delegation4 *delegation, + OUT nfs41_delegation_state **deleg_out) +{ + nfs41_delegation_state *state; + int status = NO_ERROR; + + state = calloc(1, sizeof(nfs41_delegation_state)); + if (state == NULL) { + status = GetLastError(); + goto out; + } + + memcpy(&state->state, delegation, sizeof(open_delegation4)); + + abs_path_copy(&state->path, file->path); + path_fh_init(&state->file, &state->path); + fh_copy(&state->file.fh, &file->fh); + path_fh_init(&state->parent, &state->path); + last_component(state->path.path, state->file.name.name, + &state->parent.name); + fh_copy(&state->parent.fh, &parent->fh); + + list_init(&state->client_entry); + state->status = DELEGATION_GRANTED; + InitializeSRWLock(&state->lock); + InitializeConditionVariable(&state->cond); + state->ref_count = 1; + *deleg_out = state; +out: + return status; +} + +void nfs41_delegation_ref( + IN nfs41_delegation_state *state) +{ + const LONG count = InterlockedIncrement(&state->ref_count); + dprintf(DGLVL, "nfs41_delegation_ref(%s) count %d\n", + state->path.path, count); +} + +void nfs41_delegation_deref( + IN nfs41_delegation_state *state) +{ + const LONG count = InterlockedDecrement(&state->ref_count); + dprintf(DGLVL, "nfs41_delegation_deref(%s) count %d\n", + state->path.path, count); + if (count == 0) + free(state); +} + +#define open_entry(pos) list_container(pos, nfs41_open_state, client_entry) + +static void delegation_remove( + IN nfs41_client *client, + IN nfs41_delegation_state *deleg) +{ + struct list_entry *entry; + + /* remove from the client's list */ + EnterCriticalSection(&client->state.lock); + list_remove(&deleg->client_entry); + + /* remove from each associated open */ + list_for_each(entry, &client->state.opens) { + nfs41_open_state *open = open_entry(entry); + AcquireSRWLockExclusive(&open->lock); + if (open->delegation.state == deleg) { + /* drop the delegation reference */ + nfs41_delegation_deref(open->delegation.state); + open->delegation.state = NULL; + } + ReleaseSRWLockExclusive(&open->lock); + } + LeaveCriticalSection(&client->state.lock); + + /* signal threads waiting on delegreturn */ + AcquireSRWLockExclusive(&deleg->lock); + deleg->status = DELEGATION_RETURNED; + WakeAllConditionVariable(&deleg->cond); + ReleaseSRWLockExclusive(&deleg->lock); + + /* release the client's reference */ + nfs41_delegation_deref(deleg); +} + + +/* delegation return */ +#define lock_entry(pos) list_container(pos, nfs41_lock_state, open_entry) + +static bool_t has_delegated_locks( + IN nfs41_open_state *open) +{ + struct list_entry *entry; + list_for_each(entry, &open->locks.list) { + if (lock_entry(entry)->delegated) + return TRUE; + } + return FALSE; +} + +static int open_deleg_cmp(const struct list_entry *entry, const void *value) +{ + nfs41_open_state *open = open_entry(entry); + int result = -1; + + /* open must match the delegation and have state to reclaim */ + AcquireSRWLockShared(&open->lock); + if (open->delegation.state != value) goto out; + if (open->do_close && !has_delegated_locks(open)) goto out; + result = 0; +out: + ReleaseSRWLockShared(&open->lock); + return result; +} + +/* find the first open that needs recovery */ +static nfs41_open_state* deleg_open_find( + IN struct client_state *state, + IN const nfs41_delegation_state *deleg) +{ + struct list_entry *entry; + nfs41_open_state *open = NULL; + + EnterCriticalSection(&state->lock); + entry = list_search(&state->opens, deleg, open_deleg_cmp); + if (entry) { + open = open_entry(entry); + nfs41_open_state_ref(open); /* return a reference */ + } + LeaveCriticalSection(&state->lock); + return open; +} + +/* find the first lock that needs recovery */ +static bool_t deleg_lock_find( + IN nfs41_open_state *open, + OUT nfs41_lock_state *lock_out) +{ + struct list_entry *entry; + bool_t found = FALSE; + + AcquireSRWLockShared(&open->lock); + list_for_each(entry, &open->locks.list) { + nfs41_lock_state *lock = lock_entry(entry); + if (lock->delegated) { + /* copy offset, length, type */ + lock_out->offset = lock->offset; + lock_out->length = lock->length; + lock_out->exclusive = lock->exclusive; + lock_out->id = lock->id; + found = TRUE; + break; + } + } + ReleaseSRWLockShared(&open->lock); + return found; +} + +/* find the matching lock by id, and reset lock.delegated */ +static void deleg_lock_update( + IN nfs41_open_state *open, + IN const nfs41_lock_state *source) +{ + struct list_entry *entry; + + AcquireSRWLockExclusive(&open->lock); + list_for_each(entry, &open->locks.list) { + nfs41_lock_state *lock = lock_entry(entry); + if (lock->id == source->id) { + lock->delegated = FALSE; + break; + } + } + ReleaseSRWLockExclusive(&open->lock); +} + +static int delegation_flush_locks( + IN nfs41_open_state *open, + IN bool_t try_recovery) +{ + stateid_arg stateid; + nfs41_lock_state lock; + int status = NFS4_OK; + + stateid.open = open; + stateid.delegation = NULL; + + /* get the starting open/lock stateid */ + AcquireSRWLockShared(&open->lock); + if (open->locks.stateid.seqid) { + memcpy(&stateid.stateid, &open->locks.stateid, sizeof(stateid4)); + stateid.type = STATEID_LOCK; + } else { + memcpy(&stateid.stateid, &open->stateid, sizeof(stateid4)); + stateid.type = STATEID_OPEN; + } + ReleaseSRWLockShared(&open->lock); + + /* send LOCK requests for each delegated lock range */ + while (deleg_lock_find(open, &lock)) { + status = nfs41_lock(open->session, &open->file, + &open->owner, lock.exclusive ? WRITE_LT : READ_LT, + lock.offset, lock.length, FALSE, try_recovery, &stateid); + if (status) + break; + deleg_lock_update(open, &lock); + } + + /* save the updated lock stateid */ + if (stateid.type == STATEID_LOCK) { + AcquireSRWLockExclusive(&open->lock); + if (open->locks.stateid.seqid == 0) { + /* if it's a new lock stateid, copy it in */ + memcpy(&open->locks.stateid, &stateid.stateid, sizeof(stateid4)); + } else if (stateid.stateid.seqid > open->locks.stateid.seqid) { + /* update the seqid if it's more recent */ + open->locks.stateid.seqid = stateid.stateid.seqid; + } + ReleaseSRWLockExclusive(&open->lock); + } + return status; +} + +#pragma warning (disable : 4706) /* assignment within conditional expression */ + +static int delegation_return( + IN nfs41_client *client, + IN nfs41_delegation_state *deleg, + IN bool_t truncate, + IN bool_t try_recovery) +{ + stateid_arg stateid; + nfs41_open_state *open; + int status; + + if (deleg->srv_open) { + /* make an upcall to the kernel: invalide data cache */ + HANDLE pipe; + unsigned char inbuf[sizeof(HANDLE)], *buffer = inbuf; + DWORD inbuf_len = sizeof(HANDLE), outbuf_len, dstatus; + uint32_t length; + dprintf(1, "delegation_return: making a downcall for srv_open=%x\n", + deleg->srv_open); + pipe = CreateFile(NFS41_USER_DEVICE_NAME_A, GENERIC_READ|GENERIC_WRITE, + FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if (pipe == INVALID_HANDLE_VALUE) { + eprintf("delegation_return: Unable to open downcall pipe %d\n", + GetLastError()); + goto out_downcall; + } + length = inbuf_len; + safe_write(&buffer, &length, &deleg->srv_open, sizeof(HANDLE)); + + dstatus = DeviceIoControl(pipe, IOCTL_NFS41_INVALCACHE, inbuf, inbuf_len, + NULL, 0, (LPDWORD)&outbuf_len, NULL); + if (!dstatus) + eprintf("IOCTL_NFS41_INVALCACHE failed %d\n", GetLastError()); + CloseHandle(pipe); + } +out_downcall: + + /* recover opens and locks associated with the delegation */ + while (open = deleg_open_find(&client->state, deleg)) { + status = nfs41_delegation_to_open(open, try_recovery); + if (status == NFS4_OK) + status = delegation_flush_locks(open, try_recovery); + nfs41_open_state_deref(open); + + if (status) + break; + } + + /* return the delegation */ + stateid.type = STATEID_DELEG_FILE; + stateid.open = NULL; + stateid.delegation = deleg; + AcquireSRWLockShared(&deleg->lock); + memcpy(&stateid.stateid, &deleg->state.stateid, sizeof(stateid4)); + ReleaseSRWLockShared(&deleg->lock); + + status = nfs41_delegreturn(client->session, + &deleg->file, &stateid, try_recovery); + if (status == NFS4ERR_BADSESSION) + goto out; + + delegation_remove(client, deleg); +out: + return status; +} + +/* open delegation */ +int nfs41_delegation_granted( + IN nfs41_session *session, + IN nfs41_path_fh *parent, + IN nfs41_path_fh *file, + IN open_delegation4 *delegation, + IN bool_t try_recovery, + OUT nfs41_delegation_state **deleg_out) +{ + stateid_arg stateid; + nfs41_client *client = session->client; + nfs41_delegation_state *state; + int status = NO_ERROR; + + if (delegation->type != OPEN_DELEGATE_READ && + delegation->type != OPEN_DELEGATE_WRITE) + goto out; + + if (delegation->recalled) { + status = NFS4ERR_DELEG_REVOKED; + goto out_return; + } + + /* allocate the delegation state */ + status = delegation_create(parent, file, delegation, &state); + if (status) + goto out_return; + + /* register the delegation with the client */ + EnterCriticalSection(&client->state.lock); + /* XXX: check for duplicates by fh and stateid? */ + list_add_tail(&client->state.delegations, &state->client_entry); + LeaveCriticalSection(&client->state.lock); + + nfs41_delegation_ref(state); /* return a reference */ + *deleg_out = state; +out: + return status; + +out_return: /* return the delegation on failure */ + memcpy(&stateid.stateid, &delegation->stateid, sizeof(stateid4)); + stateid.type = STATEID_DELEG_FILE; + stateid.open = NULL; + stateid.delegation = NULL; + nfs41_delegreturn(session, file, &stateid, try_recovery); + goto out; +} + +#define deleg_entry(pos) list_container(pos, nfs41_delegation_state, client_entry) + +static int deleg_file_cmp(const struct list_entry *entry, const void *value) +{ + const nfs41_fh *lhs = &deleg_entry(entry)->file.fh; + const nfs41_fh *rhs = (const nfs41_fh*)value; + if (lhs->superblock != rhs->superblock) return -1; + if (lhs->fileid != rhs->fileid) return -1; + return 0; +} + +static bool_t delegation_compatible( + IN enum open_delegation_type4 type, + IN uint32_t create, + IN uint32_t access, + IN uint32_t deny) +{ + switch (type) { + case OPEN_DELEGATE_WRITE: + /* An OPEN_DELEGATE_WRITE delegation allows the client to handle, + * on its own, all opens. */ + return TRUE; + + case OPEN_DELEGATE_READ: + /* An OPEN_DELEGATE_READ delegation allows a client to handle, + * on its own, requests to open a file for reading that do not + * deny OPEN4_SHARE_ACCESS_READ access to others. */ + if (create == OPEN4_CREATE) + return FALSE; + if (access & OPEN4_SHARE_ACCESS_WRITE || deny & OPEN4_SHARE_DENY_READ) + return FALSE; + return TRUE; + + default: + return FALSE; + } +} + +static int delegation_find( + IN nfs41_client *client, + IN const void *value, + IN list_compare_fn cmp, + OUT nfs41_delegation_state **deleg_out) +{ + struct list_entry *entry; + int status = NFS4ERR_BADHANDLE; + + EnterCriticalSection(&client->state.lock); + entry = list_search(&client->state.delegations, value, cmp); + if (entry) { + /* return a reference to the delegation */ + *deleg_out = deleg_entry(entry); + nfs41_delegation_ref(*deleg_out); + + /* move to the 'most recently used' end of the list */ + list_remove(entry); + list_add_tail(&client->state.delegations, entry); + status = NFS4_OK; + } + LeaveCriticalSection(&client->state.lock); + return status; +} + +static int delegation_truncate( + IN nfs41_delegation_state *deleg, + IN nfs41_client *client, + IN stateid_arg *stateid, + IN nfs41_file_info *info) +{ + nfs41_superblock *superblock = deleg->file.fh.superblock; + + /* use SETATTR to truncate the file */ + info->attrmask.arr[1] |= FATTR4_WORD1_TIME_CREATE | + FATTR4_WORD1_TIME_MODIFY_SET; + + get_nfs_time(&info->time_create); + get_nfs_time(&info->time_modify); + info->time_delta = &superblock->time_delta; + + /* mask out unsupported attributes */ + nfs41_superblock_supported_attrs(superblock, &info->attrmask); + + return nfs41_setattr(client->session, &deleg->file, stateid, info); +} + +int nfs41_delegate_open( + IN nfs41_open_state *state, + IN uint32_t create, + IN OPTIONAL nfs41_file_info *createattrs, + OUT nfs41_file_info *info) +{ + nfs41_client *client = state->session->client; + nfs41_path_fh *file = &state->file; + uint32_t access = state->share_access; + uint32_t deny = state->share_deny; + nfs41_delegation_state *deleg; + stateid_arg stateid; + int status; + + /* search for a delegation with this filehandle */ + status = delegation_find(client, &file->fh, deleg_file_cmp, &deleg); + if (status) + goto out; + + AcquireSRWLockExclusive(&deleg->lock); + if (deleg->status != DELEGATION_GRANTED) { + /* the delegation is being returned, wait for it to finish */ + while (deleg->status != DELEGATION_RETURNED) + SleepConditionVariableSRW(&deleg->cond, &deleg->lock, INFINITE, 0); + status = NFS4ERR_BADHANDLE; + } + else if (!delegation_compatible(deleg->state.type, create, access, deny)) { +#ifdef DELEGATION_RETURN_ON_CONFLICT + /* this open will conflict, start the delegation return */ + deleg->status = DELEGATION_RETURNING; + status = NFS4ERR_DELEG_REVOKED; +#else + status = NFS4ERR_BADHANDLE; +#endif + } else if (create == OPEN4_CREATE) { + /* copy the stateid for SETATTR */ + stateid.open = NULL; + stateid.delegation = deleg; + stateid.type = STATEID_DELEG_FILE; + memcpy(&stateid.stateid, &deleg->state.stateid, sizeof(stateid4)); + } + if (!status) { + dprintf(1, "nfs41_delegate_open: updating srv_open from %x to %x\n", + deleg->srv_open, state->srv_open); + deleg->srv_open = state->srv_open; + } + ReleaseSRWLockExclusive(&deleg->lock); + + if (status == NFS4ERR_DELEG_REVOKED) + goto out_return; + if (status) + goto out_deleg; + + if (create == OPEN4_CREATE) { + memcpy(info, createattrs, sizeof(nfs41_file_info)); + + /* write delegations allow us to simulate OPEN4_CREATE with SETATTR */ + status = delegation_truncate(deleg, client, &stateid, info); + if (status) + goto out_deleg; + } + + /* TODO: check access against deleg->state.permissions or send ACCESS */ + + state->delegation.state = deleg; + status = NFS4_OK; +out: + return status; + +out_return: + delegation_return(client, deleg, create == OPEN4_CREATE, TRUE); + +out_deleg: + nfs41_delegation_deref(deleg); + goto out; +} + +int nfs41_delegation_to_open( + IN nfs41_open_state *open, + IN bool_t try_recovery) +{ + open_delegation4 ignore; + open_claim4 claim; + stateid4 open_stateid = { 0 }; + stateid_arg deleg_stateid; + int status = NFS4_OK; + + AcquireSRWLockExclusive(&open->lock); + if (open->delegation.state == NULL) /* no delegation to reclaim */ + goto out_unlock; + + if (open->do_close) /* already have an open stateid */ + goto out_unlock; + + /* if another thread is reclaiming the open stateid, + * wait for it to finish before returning success */ + if (open->delegation.reclaim) { + do { + SleepConditionVariableSRW(&open->delegation.cond, &open->lock, + INFINITE, 0); + } while (open->delegation.reclaim); + if (open->do_close) + goto out_unlock; + } + open->delegation.reclaim = 1; + + AcquireSRWLockShared(&open->delegation.state->lock); + deleg_stateid.open = open; + deleg_stateid.delegation = NULL; + deleg_stateid.type = STATEID_DELEG_FILE; + memcpy(&deleg_stateid.stateid, &open->delegation.state->state.stateid, + sizeof(stateid4)); + ReleaseSRWLockShared(&open->delegation.state->lock); + + ReleaseSRWLockExclusive(&open->lock); + + /* send OPEN with CLAIM_DELEGATE_CUR */ + claim.claim = CLAIM_DELEGATE_CUR; + claim.u.deleg_cur.delegate_stateid = &deleg_stateid; + claim.u.deleg_cur.name = &open->file.name; + + status = nfs41_open(open->session, &open->parent, &open->file, + &open->owner, &claim, open->share_access, open->share_deny, + OPEN4_NOCREATE, 0, NULL, try_recovery, &open_stateid, &ignore, NULL); + + AcquireSRWLockExclusive(&open->lock); + if (status == NFS4_OK) { + /* save the new open stateid */ + memcpy(&open->stateid, &open_stateid, sizeof(stateid4)); + open->do_close = 1; + } else if (open->do_close && (status == NFS4ERR_BAD_STATEID || + status == NFS4ERR_STALE_STATEID || status == NFS4ERR_EXPIRED)) { + /* something triggered client state recovery, and the open stateid + * has already been reclaimed; see recover_stateid_delegation() */ + status = NFS4_OK; + } + open->delegation.reclaim = 0; + + /* signal anyone waiting on the open stateid */ + WakeAllConditionVariable(&open->delegation.cond); +out_unlock: + ReleaseSRWLockExclusive(&open->lock); + if (status) + eprintf("nfs41_delegation_to_open(%p) failed with %s\n", + open, nfs_error_string(status)); + return status; +} + +void nfs41_delegation_remove_srvopen( + IN nfs41_session *session, + IN nfs41_path_fh *file) +{ + nfs41_delegation_state *deleg = NULL; + + /* find a delegation for this file */ + if (delegation_find(session->client, &file->fh, deleg_file_cmp, &deleg)) + return; + dprintf(1, "nfs41_delegation_remove_srvopen: removing reference to " + "srv_open=%x\n", deleg->srv_open); + AcquireSRWLockExclusive(&deleg->lock); + deleg->srv_open = NULL; + ReleaseSRWLockExclusive(&deleg->lock); + nfs41_delegation_deref(deleg); +} + +/* synchronous delegation return */ +#ifdef DELEGATION_RETURN_ON_CONFLICT +int nfs41_delegation_return( + IN nfs41_session *session, + IN nfs41_path_fh *file, +#ifndef __REACTOS__ + IN enum open_delegation_type4 access, +#else + IN int access, +#endif + IN bool_t truncate) +{ + nfs41_client *client = session->client; + nfs41_delegation_state *deleg; + int status; + + /* find a delegation for this file */ + status = delegation_find(client, &file->fh, deleg_file_cmp, &deleg); + if (status) + goto out; + + AcquireSRWLockExclusive(&deleg->lock); + if (deleg->status == DELEGATION_GRANTED) { + /* return unless delegation is write and access is read */ + if (deleg->state.type != OPEN_DELEGATE_WRITE + || access != OPEN_DELEGATE_READ) { + deleg->status = DELEGATION_RETURNING; + status = NFS4ERR_DELEG_REVOKED; + } + } else { + /* the delegation is being returned, wait for it to finish */ + while (deleg->status == DELEGATION_RETURNING) + SleepConditionVariableSRW(&deleg->cond, &deleg->lock, INFINITE, 0); + status = NFS4ERR_BADHANDLE; + } + ReleaseSRWLockExclusive(&deleg->lock); + + if (status == NFS4ERR_DELEG_REVOKED) { + delegation_return(client, deleg, truncate, TRUE); + status = NFS4_OK; + } + + nfs41_delegation_deref(deleg); +out: + return status; +} +#endif + + +/* asynchronous delegation recall */ +struct recall_thread_args { + nfs41_client *client; + nfs41_delegation_state *delegation; + bool_t truncate; +}; + +static unsigned int WINAPI delegation_recall_thread(void *args) +{ + struct recall_thread_args *recall = (struct recall_thread_args*)args; + + delegation_return(recall->client, recall->delegation, recall->truncate, TRUE); + + /* clean up thread arguments */ + nfs41_delegation_deref(recall->delegation); + nfs41_root_deref(recall->client->root); + free(recall); + return 0; +} + +static int deleg_stateid_cmp(const struct list_entry *entry, const void *value) +{ + const stateid4 *lhs = &deleg_entry(entry)->state.stateid; + const stateid4 *rhs = (const stateid4*)value; + return memcmp(lhs->other, rhs->other, NFS4_STATEID_OTHER); +} + +int nfs41_delegation_recall( + IN nfs41_client *client, + IN nfs41_fh *fh, + IN const stateid4 *stateid, + IN bool_t truncate) +{ + nfs41_delegation_state *deleg; + struct recall_thread_args *args; + int status; + + dprintf(2, "--> nfs41_delegation_recall()\n"); + + /* search for the delegation by stateid instead of filehandle; + * deleg_file_cmp() relies on a proper superblock and fileid, + * which we don't get with CB_RECALL */ + status = delegation_find(client, stateid, deleg_stateid_cmp, &deleg); + if (status) + goto out; + + AcquireSRWLockExclusive(&deleg->lock); + if (deleg->state.recalled) { + /* return BADHANDLE if we've already responded to CB_RECALL */ + status = NFS4ERR_BADHANDLE; + } else { + deleg->state.recalled = 1; + + if (deleg->status == DELEGATION_GRANTED) { + /* start the delegation return */ + deleg->status = DELEGATION_RETURNING; + status = NFS4ERR_DELEG_REVOKED; + } /* else return NFS4_OK */ + } + ReleaseSRWLockExclusive(&deleg->lock); + + if (status != NFS4ERR_DELEG_REVOKED) + goto out_deleg; + + /* allocate thread arguments */ + args = calloc(1, sizeof(struct recall_thread_args)); + if (args == NULL) { + status = NFS4ERR_SERVERFAULT; + eprintf("nfs41_delegation_recall() failed to allocate arguments\n"); + goto out_deleg; + } + + /* hold a reference on the root */ + nfs41_root_ref(client->root); + args->client = client; + args->delegation = deleg; + args->truncate = truncate; + + /* the callback thread can't make rpc calls, so spawn a separate thread */ + if (_beginthreadex(NULL, 0, delegation_recall_thread, args, 0, NULL) == 0) { + status = NFS4ERR_SERVERFAULT; + eprintf("nfs41_delegation_recall() failed to start thread\n"); + goto out_args; + } + status = NFS4_OK; +out: + dprintf(DGLVL, "<-- nfs41_delegation_recall() returning %s\n", + nfs_error_string(status)); + return status; + +out_args: + free(args); + nfs41_root_deref(client->root); +out_deleg: + nfs41_delegation_deref(deleg); + goto out; +} + + +static int deleg_fh_cmp(const struct list_entry *entry, const void *value) +{ + const nfs41_fh *lhs = &deleg_entry(entry)->file.fh; + const nfs41_fh *rhs = (const nfs41_fh*)value; + if (lhs->len != rhs->len) return -1; + return memcmp(lhs->fh, rhs->fh, lhs->len); +} + +int nfs41_delegation_getattr( + IN nfs41_client *client, + IN const nfs41_fh *fh, + IN const bitmap4 *attr_request, + OUT nfs41_file_info *info) +{ + nfs41_delegation_state *deleg; + uint64_t fileid; + int status; + + dprintf(2, "--> nfs41_delegation_getattr()\n"); + + /* search for a delegation on this file handle */ + status = delegation_find(client, fh, deleg_fh_cmp, &deleg); + if (status) + goto out; + + AcquireSRWLockShared(&deleg->lock); + fileid = deleg->file.fh.fileid; + if (deleg->status != DELEGATION_GRANTED || + deleg->state.type != OPEN_DELEGATE_WRITE) { + status = NFS4ERR_BADHANDLE; + } + ReleaseSRWLockShared(&deleg->lock); + if (status) + goto out_deleg; + + ZeroMemory(info, sizeof(nfs41_file_info)); + + /* find attributes for the given fileid */ + status = nfs41_attr_cache_lookup( + client_name_cache(client), fileid, info); + if (status) { + status = NFS4ERR_BADHANDLE; + goto out_deleg; + } +out_deleg: + nfs41_delegation_deref(deleg); +out: + dprintf(DGLVL, "<-- nfs41_delegation_getattr() returning %s\n", + nfs_error_string(status)); + return status; +} + + +void nfs41_client_delegation_free( + IN nfs41_client *client) +{ + struct list_entry *entry, *tmp; + + EnterCriticalSection(&client->state.lock); + list_for_each_tmp (entry, tmp, &client->state.delegations) { + list_remove(entry); + nfs41_delegation_deref(deleg_entry(entry)); + } + LeaveCriticalSection(&client->state.lock); +} + + +static int delegation_recovery_status( + IN nfs41_delegation_state *deleg) +{ + int status = NFS4_OK; + + AcquireSRWLockExclusive(&deleg->lock); + if (deleg->status == DELEGATION_GRANTED) { + if (deleg->revoked) { + deleg->status = DELEGATION_RETURNED; + status = NFS4ERR_BADHANDLE; + } else if (deleg->state.recalled) { + deleg->status = DELEGATION_RETURNING; + status = NFS4ERR_DELEG_REVOKED; + } + } + ReleaseSRWLockExclusive(&deleg->lock); + return status; +} + +int nfs41_client_delegation_recovery( + IN nfs41_client *client) +{ + struct list_entry *entry, *tmp; + nfs41_delegation_state *deleg; + int status = NFS4_OK; + + list_for_each_tmp(entry, tmp, &client->state.delegations) { + deleg = list_container(entry, nfs41_delegation_state, client_entry); + + status = delegation_recovery_status(deleg); + switch (status) { + case NFS4ERR_DELEG_REVOKED: + /* the delegation was reclaimed, but flagged as recalled; + * return it with try_recovery=FALSE */ + status = delegation_return(client, deleg, FALSE, FALSE); + break; + + case NFS4ERR_BADHANDLE: + /* reclaim failed, so we have no delegation state on the server; + * 'forget' the delegation without trying to return it */ + delegation_remove(client, deleg); + status = NFS4_OK; + break; + } + + if (status == NFS4ERR_BADSESSION) + goto out; + } + + /* use DELEGPURGE to indicate that we're done reclaiming delegations */ + status = nfs41_delegpurge(client->session); + + /* support for DELEGPURGE is optional; ignore any errors but BADSESSION */ + if (status != NFS4ERR_BADSESSION) + status = NFS4_OK; +out: + return status; +} + + +int nfs41_client_delegation_return_lru( + IN nfs41_client *client) +{ + struct list_entry *entry; + nfs41_delegation_state *state = NULL; + int status = NFS4ERR_BADHANDLE; + + /* starting from the least recently opened, find and return + * the first delegation that's not 'in use' (currently open) */ + + /* TODO: use a more robust algorithm, taking into account: + * -number of total opens + * -time since last operation on an associated open, or + * -number of operations/second over last n seconds */ + EnterCriticalSection(&client->state.lock); + list_for_each(entry, &client->state.delegations) { + state = deleg_entry(entry); + + /* skip if it's currently in use for an open; note that ref_count + * can't go from 1 to 2 without holding client->state.lock */ + if (state->ref_count > 1) + continue; + + AcquireSRWLockExclusive(&state->lock); + if (state->status == DELEGATION_GRANTED) { + /* start returning the delegation */ + state->status = DELEGATION_RETURNING; + status = NFS4ERR_DELEG_REVOKED; + } + ReleaseSRWLockExclusive(&state->lock); + + if (status == NFS4ERR_DELEG_REVOKED) + break; + } + LeaveCriticalSection(&client->state.lock); + + if (status == NFS4ERR_DELEG_REVOKED) + status = delegation_return(client, state, FALSE, TRUE); + return status; +} diff --git a/reactos/base/services/nfsd/delegation.h b/reactos/base/services/nfsd/delegation.h new file mode 100644 index 00000000000..d71be17bf2c --- /dev/null +++ b/reactos/base/services/nfsd/delegation.h @@ -0,0 +1,113 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#ifndef DELEGATION_H +#define DELEGATION_H + +#include "nfs41.h" + + +/* option to avoid conflicts by returning the delegation */ +#define DELEGATION_RETURN_ON_CONFLICT + + +/* reference counting and cleanup */ +void nfs41_delegation_ref( + IN nfs41_delegation_state *state); + +void nfs41_delegation_deref( + IN nfs41_delegation_state *state); + +void nfs41_client_delegation_free( + IN nfs41_client *client); + + +/* open delegation */ +int nfs41_delegation_granted( + IN nfs41_session *session, + IN nfs41_path_fh *parent, + IN nfs41_path_fh *file, + IN open_delegation4 *delegation, + IN bool_t try_recovery, + OUT nfs41_delegation_state **deleg_out); + +int nfs41_delegate_open( + IN nfs41_open_state *state, + IN uint32_t create, + IN OPTIONAL nfs41_file_info *createattrs, + OUT nfs41_file_info *info); + +int nfs41_delegation_to_open( + IN nfs41_open_state *open, + IN bool_t try_recovery); + +void nfs41_delegation_remove_srvopen( + IN nfs41_session *session, + IN nfs41_path_fh *file); + +/* synchronous delegation return */ +#ifdef DELEGATION_RETURN_ON_CONFLICT +int nfs41_delegation_return( + IN nfs41_session *session, + IN nfs41_path_fh *file, +#ifndef __REACTOS__ + IN enum open_delegation_type4 access, +#else + IN int access, +#endif + IN bool_t truncate); +#else +static int nfs41_delegation_return( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN enum open_delegation_type4 access, + IN bool_t truncate) +{ + return NFS4_OK; +} +#endif + + +/* asynchronous delegation recall */ +int nfs41_delegation_recall( + IN nfs41_client *client, + IN nfs41_fh *fh, + IN const stateid4 *stateid, + IN bool_t truncate); + +int nfs41_delegation_getattr( + IN nfs41_client *client, + IN const nfs41_fh *fh, + IN const bitmap4 *attr_request, + OUT nfs41_file_info *info); + + +/* after client state recovery, return any 'recalled' delegations; + * must be called under the client's state lock */ +int nfs41_client_delegation_recovery( + IN nfs41_client *client); + +/* attempt to return the least recently used delegation; + * fails with NFS4ERR_BADHANDLE if all delegations are in use */ +int nfs41_client_delegation_return_lru( + IN nfs41_client *client); + +#endif /* DELEGATION_H */ diff --git a/reactos/base/services/nfsd/ea.c b/reactos/base/services/nfsd/ea.c new file mode 100644 index 00000000000..0a83e357f77 --- /dev/null +++ b/reactos/base/services/nfsd/ea.c @@ -0,0 +1,693 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include +#include +#include + +#include "from_kernel.h" +#include "nfs41_ops.h" +#include "delegation.h" +#include "upcall.h" +#include "daemon_debug.h" + + +#define EALVL 2 /* dprintf level for extended attribute logging */ + + +static int set_ea_value( + IN nfs41_session *session, + IN nfs41_path_fh *parent, + IN state_owner4 *owner, + IN PFILE_FULL_EA_INFORMATION ea) +{ + nfs41_path_fh file = { 0 }; + nfs41_file_info createattrs; + open_claim4 claim; + stateid_arg stateid; + open_delegation4 delegation = { 0 }; + nfs41_write_verf verf; + uint32_t bytes_written; + int status; + + /* don't allow values larger than NFS4_EASIZE */ + if (ea->EaValueLength > NFS4_EASIZE) { + eprintf("trying to write extended attribute value of size %d, " + "max allowed %d\n", ea->EaValueLength, NFS4_EASIZE); + status = NFS4ERR_FBIG; + goto out; + } + /* remove the file on empty value */ + if (ea->EaValueLength == 0) { + nfs41_component name; + name.name = ea->EaName; + name.len = ea->EaNameLength; + nfs41_remove(session, parent, &name, 0); + status = NFS4_OK; + goto out; + } + + claim.claim = CLAIM_NULL; + claim.u.null.filename = &file.name; + file.name.name = ea->EaName; + file.name.len = ea->EaNameLength; + + createattrs.attrmask.count = 2; + createattrs.attrmask.arr[0] = FATTR4_WORD0_SIZE; + createattrs.attrmask.arr[1] = FATTR4_WORD1_MODE; + createattrs.size = 0; + createattrs.mode = 0664; + + status = nfs41_open(session, parent, &file, owner, &claim, + OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_NO_DELEG, + OPEN4_SHARE_DENY_BOTH, OPEN4_CREATE, UNCHECKED4, + &createattrs, TRUE, &stateid.stateid, &delegation, NULL); + if (status) { + eprintf("nfs41_open() failed with %s\n", nfs_error_string(status)); + goto out; + } + + status = nfs41_write(session, &file, &stateid, + (unsigned char*)ea->EaName + ea->EaNameLength + 1, + ea->EaValueLength, 0, FILE_SYNC4, &bytes_written, + &verf, NULL); + if (status) { + eprintf("nfs41_write() failed with %s\n", nfs_error_string(status)); + goto out_close; + } + +out_close: + nfs41_close(session, &file, &stateid); +out: + return status; +} + +static int is_cygwin_ea( + PFILE_FULL_EA_INFORMATION ea) +{ + return (strncmp("NfsV3Attributes", ea->EaName, ea->EaNameLength) == 0 + && sizeof("NfsV3Attributes")-1 == ea->EaNameLength) + || (strncmp("NfsActOnLink", ea->EaName, ea->EaNameLength) == 0 + && sizeof("NfsActOnLink")-1 == ea->EaNameLength) + || (strncmp("NfsSymlinkTargetName", ea->EaName, ea->EaNameLength) == 0 + && sizeof("NfsSymlinkTargetName")-1 == ea->EaNameLength); +} + +#define NEXT_ENTRY(ea) ((PBYTE)(ea) + (ea)->NextEntryOffset) + +int nfs41_ea_set( + IN nfs41_open_state *state, + IN PFILE_FULL_EA_INFORMATION ea) +{ + nfs41_path_fh attrdir = { 0 }; + int status; + + status = nfs41_rpc_openattr(state->session, &state->file, TRUE, &attrdir.fh); + if (status) { + eprintf("nfs41_rpc_openattr() failed with error %s\n", + nfs_error_string(status)); + goto out; + } + + while (status == NFS4_OK) { + if (!is_cygwin_ea(ea)) + status = set_ea_value(state->session, &attrdir, &state->owner, ea); + + if (ea->NextEntryOffset == 0) + break; + ea = (PFILE_FULL_EA_INFORMATION)NEXT_ENTRY(ea); + } +out: + return status; +} + + +/* NFS41_EA_SET */ +static int parse_setexattr(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall) +{ + int status; + setexattr_upcall_args *args = &upcall->args.setexattr; + + status = get_name(&buffer, &length, &args->path); + if (status) goto out; + status = safe_read(&buffer, &length, &args->mode, sizeof(args->mode)); + if (status) goto out; + status = safe_read(&buffer, &length, &args->buf_len, sizeof(args->buf_len)); + if (status) goto out; + args->buf = buffer; + + dprintf(1, "parsing NFS41_EA_SET: mode=%o\n", args->mode); +out: + return status; +} + +static int handle_setexattr(nfs41_upcall *upcall) +{ + int status; + setexattr_upcall_args *args = &upcall->args.setexattr; + nfs41_open_state *state = upcall->state_ref; + PFILE_FULL_EA_INFORMATION ea = + (PFILE_FULL_EA_INFORMATION)args->buf; + + /* break read delegations before SETATTR */ + nfs41_delegation_return(state->session, &state->file, + OPEN_DELEGATE_READ, FALSE); + + if (strncmp("NfsV3Attributes", ea->EaName, ea->EaNameLength) == 0 + && sizeof("NfsV3Attributes")-1 == ea->EaNameLength) { + nfs41_file_info info; + stateid_arg stateid; + + nfs41_open_stateid_arg(state, &stateid); + + info.mode = args->mode; + info.attrmask.arr[0] = 0; + info.attrmask.arr[1] = FATTR4_WORD1_MODE; + info.attrmask.count = 2; + + status = nfs41_setattr(state->session, &state->file, &stateid, &info); + if (status) { + dprintf(1, "nfs41_setattr() failed with error %s.\n", + nfs_error_string(status)); + goto out; + } + + args->ctime = info.change; + goto out; + } + + status = nfs41_ea_set(state, ea); +out: + return nfs_to_windows_error(status, ERROR_NOT_SUPPORTED); +} + +static int marshall_setexattr(unsigned char *buffer, uint32_t *length, nfs41_upcall *upcall) +{ + setexattr_upcall_args *args = &upcall->args.setexattr; + return safe_write(&buffer, length, &args->ctime, sizeof(args->ctime)); +} + + +/* NFS41_EA_GET */ +static int parse_getexattr(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall) +{ + int status; + getexattr_upcall_args *args = &upcall->args.getexattr; + + status = get_name(&buffer, &length, &args->path); + if (status) goto out; + status = safe_read(&buffer, &length, &args->eaindex, sizeof(args->eaindex)); + if (status) goto out; + status = safe_read(&buffer, &length, &args->restart, sizeof(args->restart)); + if (status) goto out; + status = safe_read(&buffer, &length, &args->single, sizeof(args->single)); + if (status) goto out; + status = safe_read(&buffer, &length, &args->buf_len, sizeof(args->buf_len)); + if (status) goto out; + status = safe_read(&buffer, &length, &args->ealist_len, sizeof(args->ealist_len)); + if (status) goto out; + args->ealist = args->ealist_len ? buffer : NULL; + + dprintf(1, "parsing NFS41_EA_GET: buf_len=%d Index %d Restart %d " + "Single %d\n", args->buf_len,args->eaindex, args->restart, args->single); +out: + return status; +} + +#define READDIR_LEN_INITIAL 8192 +#define READDIR_LEN_MIN 2048 + +/* call readdir repeatedly to get a complete list of entries */ +static int read_entire_dir( + IN nfs41_session *session, + IN nfs41_path_fh *eadir, + OUT unsigned char **buffer_out, + OUT uint32_t *length_out) +{ + nfs41_readdir_cookie cookie = { 0 }; + bitmap4 attr_request; + nfs41_readdir_entry *last_entry; + unsigned char *buffer; + uint32_t buffer_len, len, total_len; + bool_t eof; + int status = NO_ERROR; + + attr_request.count = 0; /* don't request attributes */ + + /* allocate the buffer for readdir entries */ + buffer_len = READDIR_LEN_INITIAL; + buffer = calloc(1, buffer_len); + if (buffer == NULL) { + status = GetLastError(); + goto out; + } + + last_entry = NULL; + total_len = 0; + eof = FALSE; + + while (!eof) { + len = buffer_len - total_len; + if (len < READDIR_LEN_MIN) { + const ptrdiff_t diff = (unsigned char*)last_entry - buffer; + /* realloc the buffer to fit more entries */ + unsigned char *tmp = realloc(buffer, buffer_len * 2); + if (tmp == NULL) { + status = GetLastError(); + goto out_free; + } + + if (last_entry) /* fix last_entry pointer */ + last_entry = (nfs41_readdir_entry*)(tmp + diff); + buffer = tmp; + buffer_len *= 2; + len = buffer_len - total_len; + } + + /* fetch the next group of entries */ + status = nfs41_readdir(session, eadir, &attr_request, + &cookie, buffer + total_len, &len, &eof); + if (status) + goto out_free; + + if (last_entry == NULL) { + /* initialize last_entry to the front of the list */ + last_entry = (nfs41_readdir_entry*)(buffer + total_len); + } else if (len) { + /* link the previous list to the new one */ + last_entry->next_entry_offset = (uint32_t)FIELD_OFFSET( + nfs41_readdir_entry, name) + last_entry->name_len; + } + + /* find the new last entry */ + while (last_entry->next_entry_offset) { + last_entry = (nfs41_readdir_entry*)((char*)last_entry + + last_entry->next_entry_offset); + } + + cookie.cookie = last_entry->cookie; + total_len += len; + } + + *buffer_out = buffer; + *length_out = total_len; +out: + return status; + +out_free: + free(buffer); + goto out; +} + +#define ALIGNED_EASIZE(len) (align4(sizeof(FILE_GET_EA_INFORMATION) + len)) + +static uint32_t calculate_ea_list_length( + IN const unsigned char *position, + IN uint32_t remaining) +{ + const nfs41_readdir_entry *entry; + uint32_t length = 0; + + while (remaining) { + entry = (const nfs41_readdir_entry*)position; + length += ALIGNED_EASIZE(entry->name_len); + + if (!entry->next_entry_offset) + break; + + position += entry->next_entry_offset; + remaining -= entry->next_entry_offset; + } + return length; +} + +static void populate_ea_list( + IN const unsigned char *position, + OUT PFILE_GET_EA_INFORMATION ea_list) +{ + const nfs41_readdir_entry *entry; + PFILE_GET_EA_INFORMATION ea = ea_list, prev = NULL; + + for (;;) { + entry = (const nfs41_readdir_entry*)position; + StringCchCopyA(ea->EaName, entry->name_len, entry->name); + ea->EaNameLength = (UCHAR)entry->name_len - 1; + + if (!entry->next_entry_offset) { + ea->NextEntryOffset = 0; + break; + } + + prev = ea; + ea->NextEntryOffset = ALIGNED_EASIZE(ea->EaNameLength); + ea = (PFILE_GET_EA_INFORMATION)NEXT_ENTRY(ea); + position += entry->next_entry_offset; + } +} + +static int get_ea_list( + IN OUT nfs41_open_state *state, + IN nfs41_path_fh *eadir, + OUT PFILE_GET_EA_INFORMATION *ealist_out, + OUT uint32_t *eaindex_out) +{ + unsigned char *entry_list; + PFILE_GET_EA_INFORMATION ea_list; + uint32_t entry_len, ea_size; + int status = NO_ERROR; + + EnterCriticalSection(&state->ea.lock); + + if (state->ea.list != INVALID_HANDLE_VALUE) { + /* use cached ea names */ + *ealist_out = state->ea.list; + *eaindex_out = state->ea.index; + goto out; + } + + /* read the entire directory into a nfs41_readdir_entry buffer */ + status = read_entire_dir(state->session, eadir, &entry_list, &entry_len); + if (status) + goto out; + + ea_size = calculate_ea_list_length(entry_list, entry_len); + if (ea_size == 0) { + *ealist_out = state->ea.list = NULL; + goto out_free; + } + ea_list = calloc(1, ea_size); + if (ea_list == NULL) { + status = GetLastError(); + goto out_free; + } + + populate_ea_list(entry_list, ea_list); + + *ealist_out = state->ea.list = ea_list; + *eaindex_out = state->ea.index; +out_free: + free(entry_list); /* allocated by read_entire_dir() */ +out: + LeaveCriticalSection(&state->ea.lock); + return status; +} + +static int get_ea_value( + IN nfs41_session *session, + IN nfs41_path_fh *parent, + IN state_owner4 *owner, + OUT PFILE_FULL_EA_INFORMATION ea, + IN uint32_t length, + OUT uint32_t *needed) +{ + nfs41_path_fh file = { 0 }; + open_claim4 claim; + stateid_arg stateid; + open_delegation4 delegation = { 0 }; + nfs41_file_info info; + unsigned char *buffer; + uint32_t diff, bytes_read; + bool_t eof; + int status; + + if (parent->fh.len == 0) /* no named attribute directory */ + goto out_empty; + + claim.claim = CLAIM_NULL; + claim.u.null.filename = &file.name; + file.name.name = ea->EaName; + file.name.len = ea->EaNameLength; + + status = nfs41_open(session, parent, &file, owner, &claim, + OPEN4_SHARE_ACCESS_READ | OPEN4_SHARE_ACCESS_WANT_NO_DELEG, + OPEN4_SHARE_DENY_WRITE, OPEN4_NOCREATE, UNCHECKED4, NULL, TRUE, + &stateid.stateid, &delegation, &info); + if (status) { + eprintf("nfs41_open() failed with %s\n", nfs_error_string(status)); + if (status == NFS4ERR_NOENT) + goto out_empty; + goto out; + } + + if (info.size > NFS4_EASIZE) { + status = NFS4ERR_FBIG; + eprintf("EA value for '%s' longer than maximum %u " + "(%llu bytes), returning %s\n", ea->EaName, NFS4_EASIZE, + info.size, nfs_error_string(status)); + goto out_close; + } + + buffer = (unsigned char*)ea->EaName + ea->EaNameLength + 1; + diff = (uint32_t)(buffer - (unsigned char*)ea); + + /* make sure we have room for the value */ + if (length < diff + info.size) { + *needed = (uint32_t)(sizeof(FILE_FULL_EA_INFORMATION) + + ea->EaNameLength + info.size); + status = NFS4ERR_TOOSMALL; + goto out_close; + } + + /* read directly into the ea buffer */ + status = nfs41_read(session, &file, &stateid, + 0, length - diff, buffer, &bytes_read, &eof); + if (status) { + eprintf("nfs41_read() failed with %s\n", nfs_error_string(status)); + goto out_close; + } + if (!eof) { + *needed = (uint32_t)(sizeof(FILE_FULL_EA_INFORMATION) + + ea->EaNameLength + NFS4_EASIZE); + status = NFS4ERR_TOOSMALL; + goto out_close; + } + + ea->EaValueLength = (USHORT)bytes_read; + +out_close: + nfs41_close(session, &file, &stateid); +out: + return status; + +out_empty: /* return an empty value */ + ea->EaValueLength = 0; + status = NFS4_OK; + goto out; +} + +static int empty_ea_error( + IN uint32_t index, + IN BOOLEAN restart) +{ + /* choose an error value depending on the arguments */ + if (index) + return ERROR_INVALID_EA_HANDLE; + + if (!restart) + return ERROR_NO_MORE_FILES; /* -> STATUS_NO_MORE_EAS */ + + return ERROR_FILE_NOT_FOUND; /* -> STATUS_NO_EAS_ON_FILE */ +} + +static int overflow_error( + IN OUT getexattr_upcall_args *args, + IN PFILE_FULL_EA_INFORMATION prev, + IN uint32_t needed) +{ + if (prev) { + /* unlink the overflowing entry, but copy the entries that fit */ + prev->NextEntryOffset = 0; + args->overflow = ERROR_BUFFER_OVERFLOW; + } else { + /* no entries fit; return only the length needed */ + args->buf_len = needed; + args->overflow = ERROR_INSUFFICIENT_BUFFER; + } + + /* in either case, the upcall must return NO_ERROR so we + * can copy this information down to the driver */ + return NO_ERROR; +} + +static int handle_getexattr(nfs41_upcall *upcall) +{ + getexattr_upcall_args *args = &upcall->args.getexattr; + PFILE_GET_EA_INFORMATION query = (PFILE_GET_EA_INFORMATION)args->ealist; + PFILE_FULL_EA_INFORMATION ea, prev = NULL; + nfs41_open_state *state = upcall->state_ref; + nfs41_path_fh parent = { 0 }; + uint32_t remaining, needed, index = 0; + int status; + + status = nfs41_rpc_openattr(state->session, &state->file, FALSE, &parent.fh); + if (status == NFS4ERR_NOENT) { /* no named attribute directory */ + dprintf(EALVL, "no named attribute directory for '%s'\n", args->path); + if (query == NULL) { + status = empty_ea_error(args->eaindex, args->restart); + goto out; + } + } else if (status) { + eprintf("nfs41_rpc_openattr() failed with %s\n", + nfs_error_string(status)); + status = nfs_to_windows_error(status, ERROR_EAS_NOT_SUPPORTED); + goto out; + } + + if (query == NULL) { + /* if no names are queried, use READDIR to list them all */ + uint32_t i; + status = get_ea_list(state, &parent, &query, &index); + if (status) + goto out; + + if (query == NULL) { /* the file has no EAs */ + dprintf(EALVL, "empty named attribute directory for '%s'\n", + args->path); + status = empty_ea_error(args->eaindex, args->restart); + goto out; + } + + if (args->eaindex) + index = args->eaindex - 1; /* convert to zero-based index */ + else if (args->restart) + index = 0; + + /* advance the list to the specified index */ + for (i = 0; i < index; i++) { + if (query->NextEntryOffset == 0) { + if (args->eaindex) + status = ERROR_INVALID_EA_HANDLE; + else + status = ERROR_NO_MORE_FILES; /* STATUS_NO_MORE_EAS */ + goto out; + } + query = (PFILE_GET_EA_INFORMATION)NEXT_ENTRY(query); + } + } + + /* returned ea information can't exceed the downcall buffer size */ + if (args->buf_len > UPCALL_BUF_SIZE - 2 * sizeof(uint32_t)) + args->buf_len = UPCALL_BUF_SIZE - 2 * sizeof(uint32_t); + + args->buf = malloc(args->buf_len); + if (args->buf == NULL) { + status = GetLastError(); + goto out; + } + + ea = (PFILE_FULL_EA_INFORMATION)args->buf; + remaining = args->buf_len; + + for (;;) { + /* make sure we have room for at least the name */ + needed = sizeof(FILE_FULL_EA_INFORMATION) + query->EaNameLength; + if (needed > remaining) { + status = overflow_error(args, prev, needed + NFS4_EASIZE); + goto out; + } + + ea->EaNameLength = query->EaNameLength; + StringCchCopy(ea->EaName, ea->EaNameLength + 1, query->EaName); + ea->Flags = 0; + + /* read the value from file */ + status = get_ea_value(state->session, &parent, + &state->owner, ea, remaining, &needed); + if (status == NFS4ERR_TOOSMALL) { + status = overflow_error(args, prev, needed); + goto out; + } + if (status) { + status = nfs_to_windows_error(status, ERROR_EA_FILE_CORRUPT); + goto out_free; + } + + needed = align4(FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + + ea->EaNameLength + 1 + ea->EaValueLength); + + if (remaining < needed) { + /* align4 may push NextEntryOffset past our buffer, but we + * were still able to fit the ea value. set remaining = 0 + * so we'll fail on the next ea (if any) */ + remaining = 0; + } else + remaining -= needed; + + index++; + if (query->NextEntryOffset == 0 || args->single) + break; + + prev = ea; + ea->NextEntryOffset = needed; + ea = (PFILE_FULL_EA_INFORMATION)NEXT_ENTRY(ea); + query = (PFILE_GET_EA_INFORMATION)NEXT_ENTRY(query); + } + + ea->NextEntryOffset = 0; + args->buf_len -= remaining; +out: + if (args->ealist == NULL) { /* update the ea index */ + EnterCriticalSection(&state->ea.lock); + state->ea.index = index; + if (status == NO_ERROR && !args->overflow && !args->single) { + /* listing was completed, free the cache */ + free(state->ea.list); + state->ea.list = INVALID_HANDLE_VALUE; + } + LeaveCriticalSection(&state->ea.lock); + } + return status; + +out_free: + free(args->buf); + goto out; +} + +static int marshall_getexattr(unsigned char *buffer, uint32_t *length, nfs41_upcall *upcall) +{ + int status = NO_ERROR; + getexattr_upcall_args *args = &upcall->args.getexattr; + + status = safe_write(&buffer, length, &args->overflow, sizeof(args->overflow)); + if (status) goto out; + status = safe_write(&buffer, length, &args->buf_len, sizeof(args->buf_len)); + if (status) goto out; + if (args->overflow == ERROR_INSUFFICIENT_BUFFER) + goto out; + status = safe_write(&buffer, length, args->buf, args->buf_len); + if (status) goto out; +out: + free(args->buf); + return status; +} + + +const nfs41_upcall_op nfs41_op_setexattr = { + parse_setexattr, + handle_setexattr, + marshall_setexattr +}; + +const nfs41_upcall_op nfs41_op_getexattr = { + parse_getexattr, + handle_getexattr, + marshall_getexattr +}; diff --git a/reactos/base/services/nfsd/from_kernel.h b/reactos/base/services/nfsd/from_kernel.h new file mode 100644 index 00000000000..8525647825b --- /dev/null +++ b/reactos/base/services/nfsd/from_kernel.h @@ -0,0 +1,280 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#ifndef _NFS41_DAEMON_ +#define _NFS41_DAEMON_ + +#define FILE_DIRECTORY_FILE 0x00000001 +#define FILE_WRITE_THROUGH 0x00000002 +#define FILE_SEQUENTIAL_ONLY 0x00000004 +#define FILE_NO_INTERMEDIATE_BUFFERING 0x00000008 + +#define FILE_SYNCHRONOUS_IO_ALERT 0x00000010 +#define FILE_SYNCHRONOUS_IO_NONALERT 0x00000020 +#define FILE_NON_DIRECTORY_FILE 0x00000040 +#define FILE_CREATE_TREE_CONNECTION 0x00000080 + +#define FILE_COMPLETE_IF_OPLOCKED 0x00000100 +#define FILE_NO_EA_KNOWLEDGE 0x00000200 +#define FILE_OPEN_REMOTE_INSTANCE 0x00000400 +#define FILE_RANDOM_ACCESS 0x00000800 + +#define FILE_DELETE_ON_CLOSE 0x00001000 +#define FILE_OPEN_BY_FILE_ID 0x00002000 +#define FILE_OPEN_FOR_BACKUP_INTENT 0x00004000 +#define FILE_NO_COMPRESSION 0x00008000 + +#define FILE_RESERVE_OPFILTER 0x00100000 +#define FILE_OPEN_REPARSE_POINT 0x00200000 +#define FILE_OPEN_NO_RECALL 0x00400000 +#define FILE_OPEN_FOR_FREE_SPACE_QUERY 0x00800000 + +#define FILE_COPY_STRUCTURED_STORAGE 0x00000041 +#define FILE_STRUCTURED_STORAGE 0x00000441 + +#define FILE_SUPERSEDE 0x00000000 +#define FILE_OPEN 0x00000001 +#define FILE_CREATE 0x00000002 +#define FILE_OPEN_IF 0x00000003 +#define FILE_OVERWRITE 0x00000004 +#define FILE_OVERWRITE_IF 0x00000005 +#define FILE_MAXIMUM_DISPOSITION 0x00000005 + +typedef enum _FILE_INFORMATION_CLASS { + FileDirectoryInformation = 1, + FileFullDirectoryInformation, // 2 + FileBothDirectoryInformation, // 3 + FileBasicInformation, // 4 + FileStandardInformation, // 5 + FileInternalInformation, // 6 + FileEaInformation, // 7 + FileAccessInformation, // 8 + FileNameInformation, // 9 + FileRenameInformation, // 10 + FileLinkInformation, // 11 + FileNamesInformation, // 12 + FileDispositionInformation, // 13 + FilePositionInformation, // 14 + FileFullEaInformation, // 15 + FileModeInformation, // 16 + FileAlignmentInformation, // 17 + FileAllInformation, // 18 + FileAllocationInformation, // 19 + FileEndOfFileInformation, // 20 + FileAlternateNameInformation, // 21 + FileStreamInformation, // 22 + FilePipeInformation, // 23 + FilePipeLocalInformation, // 24 + FilePipeRemoteInformation, // 25 + FileMailslotQueryInformation, // 26 + FileMailslotSetInformation, // 27 + FileCompressionInformation, // 28 + FileObjectIdInformation, // 29 + FileCompletionInformation, // 30 + FileMoveClusterInformation, // 31 + FileQuotaInformation, // 32 + FileReparsePointInformation, // 33 + FileNetworkOpenInformation, // 34 + FileAttributeTagInformation, // 35 + FileTrackingInformation, // 36 + FileIdBothDirectoryInformation, // 37 + FileIdFullDirectoryInformation, // 38 + FileValidDataLengthInformation, // 39 + FileShortNameInformation, // 40 + FileIoCompletionNotificationInformation, // 41 + FileIoStatusBlockRangeInformation, // 42 + FileIoPriorityHintInformation, // 43 + FileSfioReserveInformation, // 44 + FileSfioVolumeInformation, // 45 + FileHardLinkInformation, // 46 + FileProcessIdsUsingFileInformation, // 47 + FileNormalizedNameInformation, // 48 + FileNetworkPhysicalNameInformation, // 49 + FileIdGlobalTxDirectoryInformation, // 50 + FileMaximumInformation +} FILE_INFORMATION_CLASS, *PFILE_INFORMATION_CLASS; + + +/* kernel structures for QueryDirectory results */ +typedef struct _FILE_NAMES_INFORMATION { + ULONG NextEntryOffset; + ULONG FileIndex; + ULONG FileNameLength; + WCHAR FileName[1]; +} FILE_NAMES_INFORMATION, *PFILE_NAMES_INFORMATION; + +typedef struct _FILE_DIRECTORY_INFO { + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + WCHAR FileName[1]; +} FILE_DIRECTORY_INFO, *PFILE_DIRECTORY_INFO; + +typedef struct _FILE_BOTH_DIR_INFORMATION { + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + ULONG EaSize; + CCHAR ShortNameLength; + WCHAR ShortName[12]; + WCHAR FileName[1]; +} FILE_BOTH_DIR_INFORMATION, *PFILE_BOTH_DIR_INFORMATION; + +typedef struct _FILE_FULL_DIR_INFO { + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + ULONG EaSize; + WCHAR FileName[1]; +} FILE_FULL_DIR_INFO, *PFILE_FULL_DIR_INFO; + +typedef struct _FILE_ID_FULL_DIR_INFO { + ULONG NextEntryOffset; + ULONG FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + ULONG FileAttributes; + ULONG FileNameLength; + ULONG EaSize; + LARGE_INTEGER FileId; + WCHAR FileName[1]; +} FILE_ID_FULL_DIR_INFO, *PFILE_ID_FULL_DIR_INFO; + +typedef struct _FILE_LINK_INFORMATION { + BOOLEAN ReplaceIfExists; + HANDLE RootDirectory; + ULONG FileNameLength; + WCHAR FileName[1]; +} FILE_LINK_INFORMATION, *PFILE_LINK_INFORMATION; + +typedef struct _FILE_FULL_EA_INFORMATION { + ULONG NextEntryOffset; + UCHAR Flags; + UCHAR EaNameLength; + USHORT EaValueLength; + CHAR EaName[1]; +} FILE_FULL_EA_INFORMATION, *PFILE_FULL_EA_INFORMATION; + +typedef struct _FILE_GET_EA_INFORMATION { + ULONG NextEntryOffset; + UCHAR EaNameLength; + CHAR EaName[1]; +} FILE_GET_EA_INFORMATION, *PFILE_GET_EA_INFORMATION; + +typedef struct _FILE_NETWORK_OPEN_INFORMATION { + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER AllocationSize; + LARGE_INTEGER EndOfFile; + ULONG FileAttributes; +} FILE_NETWORK_OPEN_INFORMATION, *PFILE_NETWORK_OPEN_INFORMATION; + +/* wdm.h */ +typedef enum _FSINFOCLASS { + FileFsVolumeInformation = 1, + FileFsLabelInformation, // 2 + FileFsSizeInformation, // 3 + FileFsDeviceInformation, // 4 + FileFsAttributeInformation, // 5 + FileFsControlInformation, // 6 + FileFsFullSizeInformation, // 7 + FileFsObjectIdInformation, // 8 + FileFsDriverPathInformation, // 9 + FileFsVolumeFlagsInformation,// 10 + FileFsMaximumInformation +} FS_INFORMATION_CLASS, *PFS_INFORMATION_CLASS; + +/* ntifs.h */ +#define FILE_CASE_SENSITIVE_SEARCH 0x00000001 +#define FILE_CASE_PRESERVED_NAMES 0x00000002 +#define FILE_UNICODE_ON_DISK 0x00000004 +#define FILE_PERSISTENT_ACLS 0x00000008 +#define FILE_FILE_COMPRESSION 0x00000010 +#define FILE_VOLUME_QUOTAS 0x00000020 +#define FILE_SUPPORTS_SPARSE_FILES 0x00000040 +#define FILE_SUPPORTS_REPARSE_POINTS 0x00000080 +#define FILE_SUPPORTS_REMOTE_STORAGE 0x00000100 +#define FILE_VOLUME_IS_COMPRESSED 0x00008000 +#define FILE_SUPPORTS_OBJECT_IDS 0x00010000 +#define FILE_SUPPORTS_ENCRYPTION 0x00020000 +#define FILE_NAMED_STREAMS 0x00040000 +#define FILE_READ_ONLY_VOLUME 0x00080000 +#define FILE_SEQUENTIAL_WRITE_ONCE 0x00100000 +#define FILE_SUPPORTS_TRANSACTIONS 0x00200000 +#define FILE_SUPPORTS_HARD_LINKS 0x00400000 +#define FILE_SUPPORTS_EXTENDED_ATTRIBUTES 0x00800000 +#define FILE_SUPPORTS_OPEN_BY_FILE_ID 0x01000000 +#define FILE_SUPPORTS_USN_JOURNAL 0x02000000 + +typedef struct _FILE_FS_ATTRIBUTE_INFORMATION { + ULONG FileSystemAttributes; + LONG MaximumComponentNameLength; + ULONG FileSystemNameLength; + WCHAR FileSystemName[1]; +} FILE_FS_ATTRIBUTE_INFORMATION, *PFILE_FS_ATTRIBUTE_INFORMATION; + +/* ntddk.h */ +typedef struct _FILE_FS_SIZE_INFORMATION { + LARGE_INTEGER TotalAllocationUnits; + LARGE_INTEGER AvailableAllocationUnits; + ULONG SectorsPerAllocationUnit; + ULONG BytesPerSector; +} FILE_FS_SIZE_INFORMATION, *PFILE_FS_SIZE_INFORMATION; + +typedef struct _FILE_FS_FULL_SIZE_INFORMATION { + LARGE_INTEGER TotalAllocationUnits; + LARGE_INTEGER CallerAvailableAllocationUnits; + LARGE_INTEGER ActualAvailableAllocationUnits; + ULONG SectorsPerAllocationUnit; + ULONG BytesPerSector; +} FILE_FS_FULL_SIZE_INFORMATION, *PFILE_FS_FULL_SIZE_INFORMATION; + +typedef struct _FILE_INTERNAL_INFORMATION { + LARGE_INTEGER IndexNumber; +} FILE_INTERNAL_INFORMATION, *PFILE_INTERNAL_INFORMATION; +#endif diff --git a/reactos/base/services/nfsd/getattr.c b/reactos/base/services/nfsd/getattr.c new file mode 100644 index 00000000000..6559d40c3f4 --- /dev/null +++ b/reactos/base/services/nfsd/getattr.c @@ -0,0 +1,184 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include +#include +#include + +#include "nfs41_ops.h" +#include "name_cache.h" +#include "upcall.h" +#include "daemon_debug.h" + + +int nfs41_cached_getattr( + IN nfs41_session *session, + IN nfs41_path_fh *file, + OUT nfs41_file_info *info) +{ + int status; + + /* first look for cached attributes */ + status = nfs41_attr_cache_lookup(session_name_cache(session), + file->fh.fileid, info); + + if (status) { + /* fetch attributes from the server */ + bitmap4 attr_request; + nfs41_superblock_getattr_mask(file->fh.superblock, &attr_request); + + status = nfs41_getattr(session, file, &attr_request, info); + if (status) { + eprintf("nfs41_getattr() failed with %s\n", + nfs_error_string(status)); + status = nfs_to_windows_error(status, ERROR_BAD_NET_RESP); + } + } + return status; +} + +/* NFS41_FILE_QUERY */ +static int parse_getattr(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall) +{ + int status; + getattr_upcall_args *args = &upcall->args.getattr; + + status = safe_read(&buffer, &length, &args->query_class, sizeof(args->query_class)); + if (status) goto out; + status = safe_read(&buffer, &length, &args->buf_len, sizeof(args->buf_len)); + if (status) goto out; + + dprintf(1, "parsing NFS41_FILE_QUERY: info_class=%d buf_len=%d file=%.*s\n", + args->query_class, args->buf_len, upcall->state_ref->path.len, + upcall->state_ref->path.path); +out: + return status; +} + +static int handle_getattr(nfs41_upcall *upcall) +{ + int status; + getattr_upcall_args *args = &upcall->args.getattr; + nfs41_open_state *state = upcall->state_ref; + nfs41_file_info info = { 0 }; + + status = nfs41_cached_getattr(state->session, &state->file, &info); + if (status) { + eprintf("nfs41_cached_getattr() failed with %d\n", status); + goto out; + } + + if (info.type == NF4LNK) { + nfs41_file_info target_info; + int target_status = nfs41_symlink_follow(upcall->root_ref, + state->session, &state->file, &target_info); + if (target_status == NO_ERROR && target_info.type == NF4DIR) + info.symlink_dir = TRUE; + } + + switch (args->query_class) { + case FileBasicInformation: + nfs_to_basic_info(&info, &args->basic_info); + args->ctime = info.change; + break; + case FileStandardInformation: + nfs_to_standard_info(&info, &args->std_info); + break; + case FileAttributeTagInformation: + args->tag_info.FileAttributes = nfs_file_info_to_attributes(&info); + args->tag_info.ReparseTag = info.type == NF4LNK ? + IO_REPARSE_TAG_SYMLINK : 0; + break; + case FileInternalInformation: + args->intr_info.IndexNumber.QuadPart = info.fileid; + break; + case FileNetworkOpenInformation: + nfs_to_network_openinfo(&info, &args->network_info); + break; + default: + eprintf("unhandled file query class %d\n", args->query_class); + status = ERROR_INVALID_PARAMETER; + break; + } +out: + return status; +} + +static int marshall_getattr(unsigned char *buffer, uint32_t *length, nfs41_upcall *upcall) +{ + int status; + getattr_upcall_args *args = &upcall->args.getattr; + uint32_t info_len; + + switch (args->query_class) { + case FileBasicInformation: + info_len = sizeof(args->basic_info); + status = safe_write(&buffer, length, &info_len, sizeof(info_len)); + if (status) goto out; + status = safe_write(&buffer, length, &args->basic_info, info_len); + if (status) goto out; + break; + case FileStandardInformation: + info_len = sizeof(args->std_info); + status = safe_write(&buffer, length, &info_len, sizeof(info_len)); + if (status) goto out; + status = safe_write(&buffer, length, &args->std_info, info_len); + if (status) goto out; + break; + case FileAttributeTagInformation: + info_len = sizeof(args->tag_info); + status = safe_write(&buffer, length, &info_len, sizeof(info_len)); + if (status) goto out; + status = safe_write(&buffer, length, &args->tag_info, info_len); + if (status) goto out; + break; + case FileInternalInformation: + info_len = sizeof(args->intr_info); + status = safe_write(&buffer, length, &info_len, sizeof(info_len)); + if (status) goto out; + status = safe_write(&buffer, length, &args->intr_info, info_len); + if (status) goto out; + break; + case FileNetworkOpenInformation: + info_len = sizeof(args->network_info); + status = safe_write(&buffer, length, &info_len, sizeof(info_len)); + if (status) goto out; + status = safe_write(&buffer, length, &args->network_info, info_len); + if (status) goto out; + break; + default: + eprintf("unknown file query class %d\n", args->query_class); + status = 103; + goto out; + } + status = safe_write(&buffer, length, &args->ctime, sizeof(args->ctime)); + if (status) goto out; + dprintf(1, "NFS41_FILE_QUERY: downcall changattr=%llu\n", args->ctime); +out: + return status; +} + + +const nfs41_upcall_op nfs41_op_getattr = { + parse_getattr, + handle_getattr, + marshall_getattr +}; diff --git a/reactos/base/services/nfsd/idmap.c b/reactos/base/services/nfsd/idmap.c new file mode 100644 index 00000000000..328f887028c --- /dev/null +++ b/reactos/base/services/nfsd/idmap.c @@ -0,0 +1,1063 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include +#include +#include +#include /* for strtoul() */ +#include +#include + +#include "idmap.h" +#include "nfs41_const.h" +#include "list.h" +#include "daemon_debug.h" + + +#define IDLVL 2 /* dprintf level for idmap logging */ + +#define FILTER_LEN 1024 +#define NAME_LEN 32 +#define VAL_LEN 257 + + +enum ldap_class { + CLASS_USER, + CLASS_GROUP, + + NUM_CLASSES +}; + +enum ldap_attr { + ATTR_USER_NAME, + ATTR_GROUP_NAME, + ATTR_PRINCIPAL, + ATTR_UID, + ATTR_GID, + + NUM_ATTRIBUTES +}; + +#define ATTR_FLAG(attr) (1 << (attr)) +#define ATTR_ISSET(mask, attr) (((mask) & ATTR_FLAG(attr)) != 0) + + +/* ldap/cache lookups */ +struct idmap_lookup { + enum ldap_attr attr; + enum ldap_class klass; +#ifdef __REACTOS__ + uint32_t type; +#else + enum config_type type; +#endif + list_compare_fn compare; + const void *value; +}; + + +/* configuration */ +static const char CONFIG_FILENAME[] = "C:\\ReactOS\\System32\\drivers\\etc\\ms-nfs41-idmap.conf"; + +struct idmap_config { + /* ldap server information */ + char hostname[NFS41_HOSTNAME_LEN+1]; + UINT port; + UINT version; + UINT timeout; + + /* ldap schema information */ + char classes[NUM_CLASSES][NAME_LEN]; + char attributes[NUM_ATTRIBUTES][NAME_LEN]; + char base[VAL_LEN]; + + /* caching configuration */ + INT cache_ttl; +}; + + +enum config_type { + TYPE_STR, + TYPE_INT +}; + +struct config_option { + const char *key; + const char *def; + enum config_type type; + size_t offset; + size_t max_len; +}; + +/* helper macros for declaring config_options */ +#define OPT_INT(key,def,field) \ + { key, def, TYPE_INT, FIELD_OFFSET(struct idmap_config, field), 0 } +#define OPT_STR(key,def,field,len) \ + { key, def, TYPE_STR, FIELD_OFFSET(struct idmap_config, field), len } +#define OPT_CLASS(key,def,index) \ + { key, def, TYPE_STR, FIELD_OFFSET(struct idmap_config, classes[index]), NAME_LEN } +#define OPT_ATTR(key,def,index) \ + { key, def, TYPE_STR, FIELD_OFFSET(struct idmap_config, attributes[index]), NAME_LEN } + +/* table of recognized config options, including type and default value */ +static const struct config_option g_options[] = { + /* server information */ + OPT_STR("ldap_hostname", "localhost", hostname, NFS41_HOSTNAME_LEN+1), + OPT_INT("ldap_port", "389", port), + OPT_INT("ldap_version", "3", version), + OPT_INT("ldap_timeout", "0", timeout), + + /* schema information */ + OPT_STR("ldap_base", "cn=localhost", base, VAL_LEN), + OPT_CLASS("ldap_class_users", "user", CLASS_USER), + OPT_CLASS("ldap_class_groups", "group", CLASS_GROUP), + OPT_ATTR("ldap_attr_username", "cn", ATTR_USER_NAME), + OPT_ATTR("ldap_attr_groupname", "cn", ATTR_GROUP_NAME), + OPT_ATTR("ldap_attr_gssAuthName", "gssAuthName", ATTR_PRINCIPAL), + OPT_ATTR("ldap_attr_uidNumber", "uidNumber", ATTR_UID), + OPT_ATTR("ldap_attr_gidNumber", "gidNumber", ATTR_GID), + + /* caching configuration */ + OPT_INT("cache_ttl", "60", cache_ttl), +}; + + +/* parse each line into key-value pairs + * accepts 'key = value' or 'key = "value"', + * ignores whitespace anywhere outside the ""s */ +struct config_pair { + const char *key, *value; + size_t key_len, value_len; +}; + +static int config_parse_pair( + char *line, + struct config_pair *pair) +{ + char *pos = line; + int status = NO_ERROR; + + /* terminate at comment */ + pos = strchr(line, '#'); + if (pos) *pos = 0; + + /* skip whitespace before key */ + pos = line; + while (isspace(*pos)) pos++; + pair->key = pos; + + pos = strchr(pos, '='); + if (pos == NULL) { + eprintf("missing '='\n"); + status = ERROR_INVALID_PARAMETER; + goto out; + } + + /* skip whitespace after key */ + pair->key_len = pos - pair->key; + while (pair->key_len && isspace(pair->key[pair->key_len-1])) + pair->key_len--; + + if (pair->key_len <= 0) { + eprintf("empty key\n"); + status = ERROR_INVALID_PARAMETER; + goto out; + } + + /* skip whitespace after = */ + pos++; + while (isspace(*pos)) pos++; + + if (*pos == 0) { + eprintf("end of line looking for value\n"); + status = ERROR_INVALID_PARAMETER; + goto out; + } + + if (*pos == '\"') { + /* value is between the "s */ + pair->value = pos + 1; + pos = strchr(pair->value, '\"'); + if (pos == NULL) { + eprintf("no matching '\"'\n"); + status = ERROR_INVALID_PARAMETER; + goto out; + } + pair->value_len = pos - pair->value; + } else { + pair->value = pos; + pair->value_len = strlen(pair->value); + + /* skip whitespace after value */ + while (pair->value_len && isspace(pair->value[pair->value_len-1])) + pair->value_len--; + } + + /* on success, null terminate the key and value */ + ((char*)pair->key)[pair->key_len] = 0; + ((char*)pair->value)[pair->value_len] = 0; +out: + return status; +} + +static BOOL parse_uint( + const char *str, + UINT *id_out) +{ + PCHAR endp; + const UINT id = strtoul(str, &endp, 10); + + /* must convert the whole string */ + if ((endp - str) < (ptrdiff_t)strlen(str)) + return FALSE; + + /* result must fit in 32 bits */ + if (id == ULONG_MAX && errno == ERANGE) + return FALSE; + + *id_out = id; + return TRUE; +} + +/* parse default values from g_options[] into idmap_config */ +static int config_defaults( + struct idmap_config *config) +{ + const struct config_option *option; + const int count = ARRAYSIZE(g_options); + char *dst; + int i, status = NO_ERROR; + + for (i = 0; i < count; i++) { + option = &g_options[i]; + dst = (char*)config + option->offset; + + if (option->type == TYPE_INT) { + if (!parse_uint(option->def, (UINT*)dst)) { + status = ERROR_INVALID_PARAMETER; + eprintf("failed to parse default value of %s=\"%s\": " + "expected a number\n", option->key, option->def); + break; + } + } else { + if (FAILED(StringCchCopyA(dst, option->max_len, option->def))) { + status = ERROR_BUFFER_OVERFLOW; + eprintf("failed to parse default value of %s=\"%s\": " + "buffer overflow > %u\n", option->key, option->def, + option->max_len); + break; + } + } + } + return status; +} + +static int config_find_option( + const struct config_pair *pair, + const struct config_option **option) +{ + int i, count = ARRAYSIZE(g_options); + int status = ERROR_NOT_FOUND; + + /* find the config_option by key */ + for (i = 0; i < count; i++) { + if (stricmp(pair->key, g_options[i].key) == 0) { + *option = &g_options[i]; + status = NO_ERROR; + break; + } + } + return status; +} + +static int config_load( + struct idmap_config *config, + const char *filename) +{ + char buffer[1024], *pos; + FILE *file; + struct config_pair pair; + const struct config_option *option; + int line = 0; + int status = NO_ERROR; + + /* open the file */ + file = fopen(filename, "r"); + if (file == NULL) { + eprintf("config_load() failed to open file '%s'\n", filename); + goto out; + } + + /* read each line */ + while (fgets(buffer, sizeof(buffer), file)) { + line++; + + /* skip whitespace */ + pos = buffer; + while (isspace(*pos)) pos++; + + /* skip comments and empty lines */ + if (*pos == '#' || *pos == 0) + continue; + + /* parse line into a key=value pair */ + status = config_parse_pair(buffer, &pair); + if (status) { + eprintf("error on line %d: %s\n", line, buffer); + break; + } + + /* find the config_option by key */ + status = config_find_option(&pair, &option); + if (status) { + eprintf("unrecognized option '%s' on line %d: %s\n", + pair.key, line, buffer); + status = ERROR_INVALID_PARAMETER; + break; + } + + if (option->type == TYPE_INT) { + if (!parse_uint(pair.value, (UINT*)((char*)config + option->offset))) { + status = ERROR_INVALID_PARAMETER; + eprintf("expected a number on line %d: %s=\"%s\"\n", + line, pair.key, pair.value); + break; + } + } else { + if (FAILED(StringCchCopyNA((char*)config + option->offset, + option->max_len, pair.value, pair.value_len))) { + status = ERROR_BUFFER_OVERFLOW; + eprintf("overflow on line %d: %s=\"%s\"\n", + line, pair.key, pair.value); + break; + } + } + } + + fclose(file); +out: + return status; +} + +static int config_init( + struct idmap_config *config) +{ + int status; + + /* load default values */ + status = config_defaults(config); + if (status) { + eprintf("config_defaults() failed with %d\n", status); + goto out; + } + + /* load configuration from file */ + status = config_load(config, CONFIG_FILENAME); + if (status) { + eprintf("config_load('%s') failed with %d\n", CONFIG_FILENAME, status); + goto out; + } +out: + return status; +} + + +/* generic cache */ +typedef struct list_entry* (*entry_alloc_fn)(); +typedef void (*entry_free_fn)(struct list_entry*); +typedef void (*entry_copy_fn)(struct list_entry*, const struct list_entry*); + +struct cache_ops { + entry_alloc_fn entry_alloc; + entry_free_fn entry_free; + entry_copy_fn entry_copy; +}; + +struct idmap_cache { + struct list_entry head; + const struct cache_ops *ops; + SRWLOCK lock; +}; + + +static void cache_init( + struct idmap_cache *cache, + const struct cache_ops *ops) +{ + list_init(&cache->head); + cache->ops = ops; + InitializeSRWLock(&cache->lock); +} + +static void cache_cleanup( + struct idmap_cache *cache) +{ + struct list_entry *entry, *tmp; + list_for_each_tmp(entry, tmp, &cache->head) + cache->ops->entry_free(entry); + list_init(&cache->head); +} + +static int cache_insert( + struct idmap_cache *cache, + const struct idmap_lookup *lookup, + const struct list_entry *src) +{ + struct list_entry *entry; + int status = NO_ERROR; + + AcquireSRWLockExclusive(&cache->lock); + + /* search for an existing match */ + entry = list_search(&cache->head, lookup->value, lookup->compare); + if (entry) { + /* overwrite the existing entry with the new results */ + cache->ops->entry_copy(entry, src); + goto out; + } + + /* initialize a new entry and add it to the list */ + entry = cache->ops->entry_alloc(); + if (entry == NULL) { + status = GetLastError(); + goto out; + } + cache->ops->entry_copy(entry, src); + list_add_head(&cache->head, entry); +out: + ReleaseSRWLockExclusive(&cache->lock); + return status; +} + +static int cache_lookup( + struct idmap_cache *cache, + const struct idmap_lookup *lookup, + struct list_entry *entry_out) +{ + struct list_entry *entry; + int status = ERROR_NOT_FOUND; + + AcquireSRWLockShared(&cache->lock); + + entry = list_search(&cache->head, lookup->value, lookup->compare); + if (entry) { + /* make a copy for use outside of the lock */ + cache->ops->entry_copy(entry_out, entry); + status = NO_ERROR; + } + + ReleaseSRWLockShared(&cache->lock); + return status; +} + + +/* user cache */ +struct idmap_user { + struct list_entry entry; + char username[VAL_LEN]; + char principal[VAL_LEN]; + uid_t uid; + gid_t gid; + time_t last_updated; +}; + +static struct list_entry* user_cache_alloc() +{ + struct idmap_user *user = calloc(1, sizeof(struct idmap_user)); + return user == NULL ? NULL : &user->entry; +} +static void user_cache_free(struct list_entry *entry) +{ + free(list_container(entry, struct idmap_user, entry)); +} +static void user_cache_copy( + struct list_entry *lhs, + const struct list_entry *rhs) +{ + struct idmap_user *dst = list_container(lhs, struct idmap_user, entry); + const struct idmap_user *src = list_container(rhs, const struct idmap_user, entry); + StringCchCopyA(dst->username, VAL_LEN, src->username); + StringCchCopyA(dst->principal, VAL_LEN, src->principal); + dst->uid = src->uid; + dst->gid = src->gid; + dst->last_updated = src->last_updated; +} +static const struct cache_ops user_cache_ops = { + user_cache_alloc, + user_cache_free, + user_cache_copy +}; + + +/* group cache */ +struct idmap_group { + struct list_entry entry; + char name[VAL_LEN]; + gid_t gid; + time_t last_updated; +}; + +static struct list_entry* group_cache_alloc() +{ + struct idmap_group *group = calloc(1, sizeof(struct idmap_group)); + return group == NULL ? NULL : &group->entry; +} +static void group_cache_free(struct list_entry *entry) +{ + free(list_container(entry, struct idmap_group, entry)); +} +static void group_cache_copy( + struct list_entry *lhs, + const struct list_entry *rhs) +{ + struct idmap_group *dst = list_container(lhs, struct idmap_group, entry); + const struct idmap_group *src = list_container(rhs, const struct idmap_group, entry); + StringCchCopyA(dst->name, VAL_LEN, src->name); + dst->gid = src->gid; + dst->last_updated = src->last_updated; +} +static const struct cache_ops group_cache_ops = { + group_cache_alloc, + group_cache_free, + group_cache_copy +}; + + +/* ldap context */ +struct idmap_context { + struct idmap_config config; + struct idmap_cache users; + struct idmap_cache groups; + LDAP *ldap; +}; + + +static int idmap_filter( + struct idmap_config *config, + const struct idmap_lookup *lookup, + char *filter, + size_t filter_len) +{ + UINT_PTR i; + int status = NO_ERROR; + + switch (lookup->type) { + case TYPE_INT: + i = (UINT_PTR)lookup->value; + if (FAILED(StringCchPrintfA(filter, filter_len, + "(&(objectClass=%s)(%s=%u))", + config->classes[lookup->klass], + config->attributes[lookup->attr], (UINT)i))) { + status = ERROR_BUFFER_OVERFLOW; + eprintf("ldap filter buffer overflow: '%s=%u'\n", + config->attributes[lookup->attr], (UINT)i); + } + break; + + case TYPE_STR: + if (FAILED(StringCchPrintfA(filter, filter_len, + "(&(objectClass=%s)(%s=%s))", + config->classes[lookup->klass], + config->attributes[lookup->attr], lookup->value))) { + status = ERROR_BUFFER_OVERFLOW; + eprintf("ldap filter buffer overflow: '%s=%s'\n", + config->attributes[lookup->attr], lookup->value); + } + break; + + default: + status = ERROR_INVALID_PARAMETER; + break; + } + return status; +} + +static int idmap_query_attrs( + struct idmap_context *context, + const struct idmap_lookup *lookup, + const unsigned attributes, + const unsigned optional, + PCHAR *values[], + const int len) +{ + char filter[FILTER_LEN]; + struct idmap_config *config = &context->config; + LDAPMessage *res = NULL, *entry; + int i, status; + + /* format the ldap filter */ + status = idmap_filter(config, lookup, filter, FILTER_LEN); + if (status) + goto out; + + /* send the ldap query */ + status = ldap_search_st(context->ldap, config->base, + LDAP_SCOPE_SUBTREE, filter, NULL, 0, NULL, &res); + if (status) { + eprintf("ldap search for '%s' failed with %d: %s\n", + filter, status, ldap_err2stringA(status)); + status = LdapMapErrorToWin32(status); + goto out; + } + + entry = ldap_first_entry(context->ldap, res); + if (entry == NULL) { + status = LDAP_NO_RESULTS_RETURNED; + eprintf("ldap search for '%s' failed with %d: %s\n", + filter, status, ldap_err2stringA(status)); + status = LdapMapErrorToWin32(status); + goto out; + } + + /* fetch the attributes */ + for (i = 0; i < len; i++) { + if (ATTR_ISSET(attributes, i)) { + values[i] = ldap_get_values(context->ldap, + entry, config->attributes[i]); + + /* fail if required attributes are missing */ + if (values[i] == NULL && !ATTR_ISSET(optional, i)) { + status = LDAP_NO_SUCH_ATTRIBUTE; + eprintf("ldap entry for '%s' missing required " + "attribute '%s', returning %d: %s\n", + filter, config->attributes[i], + status, ldap_err2stringA(status)); + status = LdapMapErrorToWin32(status); + goto out; + } + } + } +out: + if (res) ldap_msgfree(res); + return status; +} + +static int idmap_lookup_user( + struct idmap_context *context, + const struct idmap_lookup *lookup, + struct idmap_user *user) +{ + PCHAR* values[NUM_ATTRIBUTES] = { NULL }; + const unsigned attributes = ATTR_FLAG(ATTR_USER_NAME) + | ATTR_FLAG(ATTR_PRINCIPAL) + | ATTR_FLAG(ATTR_UID) + | ATTR_FLAG(ATTR_GID); + /* principal is optional; we'll cache it if we have it */ + const unsigned optional = ATTR_FLAG(ATTR_PRINCIPAL); + int i, status; + + /* check the user cache for an existing entry */ + status = cache_lookup(&context->users, lookup, &user->entry); + if (status == NO_ERROR) { + /* don't return expired entries; query new attributes + * and overwrite the entry with cache_insert() */ + if (time(NULL) - user->last_updated < context->config.cache_ttl) + goto out; + } + + /* send the query to the ldap server */ + status = idmap_query_attrs(context, lookup, + attributes, optional, values, NUM_ATTRIBUTES); + if (status) + goto out_free_values; + + /* parse attributes */ + if (FAILED(StringCchCopyA(user->username, VAL_LEN, + *values[ATTR_USER_NAME]))) { + eprintf("ldap attribute %s='%s' longer than %u characters\n", + context->config.attributes[ATTR_USER_NAME], + *values[ATTR_USER_NAME], VAL_LEN); + status = ERROR_BUFFER_OVERFLOW; + goto out_free_values; + } + if (FAILED(StringCchCopyA(user->principal, VAL_LEN, + values[ATTR_PRINCIPAL] ? *values[ATTR_PRINCIPAL] : ""))) { + eprintf("ldap attribute %s='%s' longer than %u characters\n", + context->config.attributes[ATTR_PRINCIPAL], + values[ATTR_PRINCIPAL] ? *values[ATTR_PRINCIPAL] : "", VAL_LEN); + status = ERROR_BUFFER_OVERFLOW; + goto out_free_values; + } + if (!parse_uint(*values[ATTR_UID], &user->uid)) { + eprintf("failed to parse ldap attribute %s='%s'\n", + context->config.attributes[ATTR_UID], *values[ATTR_UID]); + status = ERROR_INVALID_PARAMETER; + goto out_free_values; + } + if (!parse_uint(*values[ATTR_GID], &user->gid)) { + eprintf("failed to parse ldap attribute %s='%s'\n", + context->config.attributes[ATTR_GID], *values[ATTR_GID]); + status = ERROR_INVALID_PARAMETER; + goto out_free_values; + } + user->last_updated = time(NULL); + + if (context->config.cache_ttl) { + /* insert the entry into the cache */ + cache_insert(&context->users, lookup, &user->entry); + } +out_free_values: + for (i = 0; i < NUM_ATTRIBUTES; i++) + ldap_value_free(values[i]); +out: + return status; +} + +static int idmap_lookup_group( + struct idmap_context *context, + const struct idmap_lookup *lookup, + struct idmap_group *group) +{ + PCHAR* values[NUM_ATTRIBUTES] = { NULL }; + const unsigned attributes = ATTR_FLAG(ATTR_GROUP_NAME) + | ATTR_FLAG(ATTR_GID); + int i, status; + + /* check the group cache for an existing entry */ + status = cache_lookup(&context->groups, lookup, &group->entry); + if (status == NO_ERROR) { + /* don't return expired entries; query new attributes + * and overwrite the entry with cache_insert() */ + if (time(NULL) - group->last_updated < context->config.cache_ttl) + goto out; + } + + /* send the query to the ldap server */ + status = idmap_query_attrs(context, lookup, + attributes, 0, values, NUM_ATTRIBUTES); + if (status) + goto out_free_values; + + /* parse attributes */ + if (FAILED(StringCchCopyA(group->name, VAL_LEN, + *values[ATTR_GROUP_NAME]))) { + eprintf("ldap attribute %s='%s' longer than %u characters\n", + context->config.attributes[ATTR_GROUP_NAME], + *values[ATTR_GROUP_NAME], VAL_LEN); + status = ERROR_BUFFER_OVERFLOW; + goto out_free_values; + } + if (!parse_uint(*values[ATTR_GID], &group->gid)) { + eprintf("failed to parse ldap attribute %s='%s'\n", + context->config.attributes[ATTR_GID], *values[ATTR_GID]); + status = ERROR_INVALID_PARAMETER; + goto out_free_values; + } + group->last_updated = time(NULL); + + if (context->config.cache_ttl) { + /* insert the entry into the cache */ + cache_insert(&context->groups, lookup, &group->entry); + } +out_free_values: + for (i = 0; i < NUM_ATTRIBUTES; i++) + ldap_value_free(values[i]); +out: + return status; +} + + +/* public idmap interface */ +int nfs41_idmap_create( + struct idmap_context **context_out) +{ + struct idmap_context *context; + int status = NO_ERROR; + + context = calloc(1, sizeof(struct idmap_context)); + if (context == NULL) { + status = GetLastError(); + goto out; + } + + /* initialize the caches */ + cache_init(&context->users, &user_cache_ops); + cache_init(&context->groups, &group_cache_ops); + + /* load ldap configuration from file */ + status = config_init(&context->config); + if (status) { + eprintf("config_init() failed with %d\n", status); + goto out_err_free; + } + + /* initialize ldap and configure options */ + context->ldap = ldap_init(context->config.hostname, context->config.port); + if (context->ldap == NULL) { + status = LdapGetLastError(); + eprintf("ldap_init(%s) failed with %d: %s\n", + context->config.hostname, status, ldap_err2stringA(status)); + status = LdapMapErrorToWin32(status); + goto out_err_free; + } + + status = ldap_set_option(context->ldap, LDAP_OPT_PROTOCOL_VERSION, + (void *)&context->config.version); + if (status != LDAP_SUCCESS) { + eprintf("ldap_set_option(version=%d) failed with %d\n", + context->config.version, status); + status = LdapMapErrorToWin32(status); + goto out_err_free; + } + + if (context->config.timeout) { + status = ldap_set_option(context->ldap, LDAP_OPT_TIMELIMIT, + (void *)&context->config.timeout); + if (status != LDAP_SUCCESS) { + eprintf("ldap_set_option(timeout=%d) failed with %d\n", + context->config.timeout, status); + status = LdapMapErrorToWin32(status); + goto out_err_free; + } + } + + *context_out = context; +out: + return status; + +out_err_free: + nfs41_idmap_free(context); + goto out; +} + +void nfs41_idmap_free( + struct idmap_context *context) +{ + /* clean up the connection */ + if (context->ldap) + ldap_unbind(context->ldap); + + cache_cleanup(&context->users); + cache_cleanup(&context->groups); + free(context); +} + + +/* username -> uid, gid */ +static int username_cmp(const struct list_entry *list, const void *value) +{ + const struct idmap_user *entry = list_container(list, + const struct idmap_user, entry); + const char *username = (const char*)value; + return strcmp(entry->username, username); +} + +int nfs41_idmap_name_to_ids( + struct idmap_context *context, + const char *username, + uid_t *uid_out, + gid_t *gid_out) +{ + struct idmap_lookup lookup = { ATTR_USER_NAME, + CLASS_USER, TYPE_STR, username_cmp }; + struct idmap_user user; + int status; + + if (context == NULL) + return ERROR_FILE_NOT_FOUND; + + dprintf(IDLVL, "--> nfs41_idmap_name_to_ids('%s')\n", username); + + lookup.value = username; + + /* look up the user entry */ + status = idmap_lookup_user(context, &lookup, &user); + if (status) { + dprintf(IDLVL, "<-- nfs41_idmap_name_to_ids('%s') " + "failed with %d\n", username, status); + goto out; + } + + *uid_out = user.uid; + *gid_out = user.gid; + dprintf(IDLVL, "<-- nfs41_idmap_name_to_ids('%s') " + "returning uid=%u, gid=%u\n", username, user.uid, user.gid); +out: + return status; +} + +/* uid -> username */ +static int uid_cmp(const struct list_entry *list, const void *value) +{ + const struct idmap_user *entry = list_container(list, + const struct idmap_user, entry); + const UINT_PTR uid = (const UINT_PTR)value; + return (UINT)uid - entry->uid; +} + +int nfs41_idmap_uid_to_name( + struct idmap_context *context, + uid_t uid, + char *name, + size_t len) +{ + UINT_PTR uidp = uid; /* convert to pointer size to pass as void* */ + struct idmap_lookup lookup = { ATTR_UID, CLASS_USER, TYPE_INT, uid_cmp }; + struct idmap_user user; + int status; + + dprintf(IDLVL, "--> nfs41_idmap_uid_to_name(%u)\n", uid); + + lookup.value = (const void*)uidp; + + /* look up the user entry */ + status = idmap_lookup_user(context, &lookup, &user); + if (status) { + dprintf(IDLVL, "<-- nfs41_idmap_uid_to_name(%u) " + "failed with %d\n", uid, status); + goto out; + } + + if (FAILED(StringCchCopyA(name, len, user.username))) { + status = ERROR_BUFFER_OVERFLOW; + eprintf("username buffer overflow: '%s' > %u\n", + user.username, len); + goto out; + } + + dprintf(IDLVL, "<-- nfs41_idmap_uid_to_name(%u) " + "returning '%s'\n", uid, name); +out: + return status; +} + +/* principal -> uid, gid */ +static int principal_cmp(const struct list_entry *list, const void *value) +{ + const struct idmap_user *entry = list_container(list, + const struct idmap_user, entry); + const char *principal = (const char*)value; + return strcmp(entry->principal, principal); +} + +int nfs41_idmap_principal_to_ids( + struct idmap_context *context, + const char *principal, + uid_t *uid_out, + gid_t *gid_out) +{ + struct idmap_lookup lookup = { ATTR_PRINCIPAL, + CLASS_USER, TYPE_STR, principal_cmp }; + struct idmap_user user; + int status; + + dprintf(IDLVL, "--> nfs41_idmap_principal_to_ids('%s')\n", principal); + + lookup.value = principal; + + /* look up the user entry */ + status = idmap_lookup_user(context, &lookup, &user); + if (status) { + dprintf(IDLVL, "<-- nfs41_idmap_principal_to_ids('%s') " + "failed with %d\n", principal, status); + goto out; + } + + *uid_out = user.uid; + *gid_out = user.gid; + dprintf(IDLVL, "<-- nfs41_idmap_principal_to_ids('%s') " + "returning uid=%u, gid=%u\n", principal, user.uid, user.gid); +out: + return status; +} + +/* group -> gid */ +static int group_cmp(const struct list_entry *list, const void *value) +{ + const struct idmap_group *entry = list_container(list, + const struct idmap_group, entry); + const char *group = (const char*)value; + return strcmp(entry->name, group); +} + +int nfs41_idmap_group_to_gid( + struct idmap_context *context, + const char *name, + gid_t *gid_out) +{ + struct idmap_lookup lookup = { ATTR_GROUP_NAME, + CLASS_GROUP, TYPE_STR, group_cmp }; + struct idmap_group group; + int status; + + dprintf(IDLVL, "--> nfs41_idmap_group_to_gid('%s')\n", name); + + lookup.value = name; + + /* look up the group entry */ + status = idmap_lookup_group(context, &lookup, &group); + if (status) { + dprintf(IDLVL, "<-- nfs41_idmap_group_to_gid('%s') " + "failed with %d\n", name, status); + goto out; + } + + *gid_out = group.gid; + dprintf(IDLVL, "<-- nfs41_idmap_group_to_gid('%s') " + "returning %u\n", name, group.gid); +out: + return status; +} + +/* gid -> group */ +static int gid_cmp(const struct list_entry *list, const void *value) +{ + const struct idmap_group *entry = list_container(list, + const struct idmap_group, entry); + const UINT_PTR gid = (const UINT_PTR)value; + return (UINT)gid - entry->gid; +} + +int nfs41_idmap_gid_to_group( + struct idmap_context *context, + gid_t gid, + char *name, + size_t len) +{ + UINT_PTR gidp = gid; /* convert to pointer size to pass as void* */ + struct idmap_lookup lookup = { ATTR_GID, CLASS_GROUP, TYPE_INT, gid_cmp }; + struct idmap_group group; + int status; + + dprintf(IDLVL, "--> nfs41_idmap_gid_to_group(%u)\n", gid); + + lookup.value = (const void*)gidp; + + /* look up the group entry */ + status = idmap_lookup_group(context, &lookup, &group); + if (status) { + dprintf(IDLVL, "<-- nfs41_idmap_gid_to_group(%u) " + "failed with %d\n", gid, status); + goto out; + } + + if (FAILED(StringCchCopyA(name, len, group.name))) { + status = ERROR_BUFFER_OVERFLOW; + eprintf("group name buffer overflow: '%s' > %u\n", + group.name, len); + goto out; + } + + dprintf(IDLVL, "<-- nfs41_idmap_gid_to_group(%u) " + "returning '%s'\n", gid, name); +out: + return status; +} diff --git a/reactos/base/services/nfsd/idmap.h b/reactos/base/services/nfsd/idmap.h new file mode 100644 index 00000000000..6703bec0626 --- /dev/null +++ b/reactos/base/services/nfsd/idmap.h @@ -0,0 +1,67 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#ifndef IDMAP_H +#define IDMAP_H + +#include "nfs41_types.h" + + +/* idmap.c */ +typedef struct idmap_context nfs41_idmapper; + +int nfs41_idmap_create( + nfs41_idmapper **context_out); + +void nfs41_idmap_free( + nfs41_idmapper *context); + + +int nfs41_idmap_name_to_ids( + nfs41_idmapper *context, + const char *username, + uid_t *uid_out, + gid_t *gid_out); + +int nfs41_idmap_uid_to_name( + nfs41_idmapper *context, + uid_t uid, + char *name_out, + size_t len); + +int nfs41_idmap_principal_to_ids( + nfs41_idmapper *context, + const char *principal, + uid_t *uid_out, + gid_t *gid_out); + +int nfs41_idmap_group_to_gid( + nfs41_idmapper *context, + const char *name, + gid_t *gid_out); + +int nfs41_idmap_gid_to_group( + nfs41_idmapper *context, + gid_t gid, + char *name_out, + size_t len); + +#endif /* !IDMAP_H */ diff --git a/reactos/base/services/nfsd/list.h b/reactos/base/services/nfsd/list.h new file mode 100644 index 00000000000..221daf949b7 --- /dev/null +++ b/reactos/base/services/nfsd/list.h @@ -0,0 +1,114 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#ifndef NFS41_LIST_H +#define NFS41_LIST_H + + +/* doubly-linked list */ +struct list_entry { + struct list_entry *prev; + struct list_entry *next; +}; + + +#define list_container(entry, type, field) \ + ((type*)((const char*)(entry) - (const char*)(&((type*)0)->field))) + +#define list_for_each(entry, head) \ + for (entry = (head)->next; entry != (head); entry = entry->next) + +#define list_for_each_tmp(entry, tmp, head) \ + for (entry = (head)->next, tmp = entry->next; entry != (head); \ + entry = tmp, tmp = entry->next) + +#define list_for_each_reverse(entry, head) \ + for (entry = (head)->prev; entry != (head); entry = entry->prev) + +#define list_for_each_reverse_tmp(entry, tmp, head) \ + for (entry = (head)->next, tmp = entry->next; entry != (head); \ + entry = tmp, tmp = entry->next) + + +static void list_init( + struct list_entry *head) +{ + head->prev = head; + head->next = head; +} + +static int list_empty( + struct list_entry *head) +{ + return head->next == head; +} + +static void list_add( + struct list_entry *entry, + struct list_entry *prev, + struct list_entry *next) +{ + /* assert(prev->next == next && next->prev == prev); */ + entry->prev = prev; + entry->next = next; + prev->next = entry; + next->prev = entry; +} + +static void list_add_head( + struct list_entry *head, + struct list_entry *entry) +{ + list_add(entry, head, head->next); +} + +static void list_add_tail( + struct list_entry *head, + struct list_entry *entry) +{ + list_add(entry, head->prev, head); +} + +static void list_remove( + struct list_entry *entry) +{ + if (!list_empty(entry)) { + entry->next->prev = entry->prev; + entry->prev->next = entry->next; + list_init(entry); + } +} + +typedef int (*list_compare_fn)(const struct list_entry*, const void*); + +static struct list_entry* list_search( + const struct list_entry *head, + const void *value, + list_compare_fn compare) +{ + struct list_entry *entry; + list_for_each(entry, head) + if (compare(entry, value) == 0) + return entry; + return NULL; +} + +#endif /* !NFS41_LIST_H */ diff --git a/reactos/base/services/nfsd/lock.c b/reactos/base/services/nfsd/lock.c new file mode 100644 index 00000000000..9d0c46b20b5 --- /dev/null +++ b/reactos/base/services/nfsd/lock.c @@ -0,0 +1,369 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include +#include + +#include "daemon_debug.h" +#include "delegation.h" +#include "nfs41_ops.h" +#include "upcall.h" +#include "util.h" + + +#define LKLVL 2 /* dprintf level for lock logging */ + + +static void lock_stateid_arg( + IN nfs41_open_state *state, + OUT stateid_arg *arg) +{ + arg->open = state; + arg->delegation = NULL; + + AcquireSRWLockShared(&state->lock); + if (state->locks.stateid.seqid) { + memcpy(&arg->stateid, &state->locks.stateid, sizeof(stateid4)); + arg->type = STATEID_LOCK; + } else if (state->do_close) { + memcpy(&arg->stateid, &state->stateid, sizeof(stateid4)); + arg->type = STATEID_OPEN; + } else { + memset(&arg->stateid, 0, sizeof(stateid4)); + arg->type = STATEID_SPECIAL; + } + ReleaseSRWLockShared(&state->lock); +} + +/* expects the caller to hold an exclusive lock on nfs41_open_state.lock */ +static void lock_stateid_update( + OUT nfs41_open_state *state, + IN const stateid4 *stateid) +{ + if (state->locks.stateid.seqid == 0) { + /* if it's a new lock stateid, copy it in */ + memcpy(&state->locks.stateid, stateid, sizeof(stateid4)); + } else if (stateid->seqid > state->locks.stateid.seqid) { + /* update the seqid if it's more recent */ + state->locks.stateid.seqid = stateid->seqid; + } +} + +static void open_lock_add( + IN nfs41_open_state *open, + IN const stateid_arg *stateid, + IN nfs41_lock_state *lock) +{ + AcquireSRWLockExclusive(&open->lock); + + if (stateid->type == STATEID_LOCK) + lock_stateid_update(open, &stateid->stateid); + + lock->id = open->locks.counter++; + list_add_tail(&open->locks.list, &lock->open_entry); + + ReleaseSRWLockExclusive(&open->lock); +} + +static bool_t open_lock_delegate( + IN nfs41_open_state *open, + IN nfs41_lock_state *lock) +{ + bool_t delegated = FALSE; + + AcquireSRWLockExclusive(&open->lock); + if (open->delegation.state) { + nfs41_delegation_state *deleg = open->delegation.state; + AcquireSRWLockShared(&deleg->lock); + if (deleg->state.type == OPEN_DELEGATE_WRITE + && deleg->status == DELEGATION_GRANTED) { + lock->delegated = 1; + lock->id = open->locks.counter++; + list_add_tail(&open->locks.list, &lock->open_entry); + delegated = TRUE; + } + ReleaseSRWLockShared(&deleg->lock); + } + ReleaseSRWLockExclusive(&open->lock); + + return delegated; +} + +#define lock_entry(pos) list_container(pos, nfs41_lock_state, open_entry) + +static int lock_range_cmp(const struct list_entry *entry, const void *value) +{ + const nfs41_lock_state *lhs = lock_entry(entry); + const nfs41_lock_state *rhs = (const nfs41_lock_state*)value; + if (lhs->offset != rhs->offset) return -1; + if (lhs->length != rhs->length) return -1; + return 0; +} + +static int open_unlock_delegate( + IN nfs41_open_state *open, + IN const nfs41_lock_state *input) +{ + struct list_entry *entry; + int status = ERROR_NOT_LOCKED; + + AcquireSRWLockExclusive(&open->lock); + + /* find lock state that matches this range */ + entry = list_search(&open->locks.list, input, lock_range_cmp); + if (entry) { + nfs41_lock_state *lock = lock_entry(entry); + if (lock->delegated) { + /* if the lock was delegated, remove/free it and return success */ + list_remove(entry); + free(lock); + status = NO_ERROR; + } else + status = ERROR_LOCKED; + } + + ReleaseSRWLockExclusive(&open->lock); + return status; +} + +static void open_unlock_remove( + IN nfs41_open_state *open, + IN const stateid_arg *stateid, + IN const nfs41_lock_state *input) +{ + struct list_entry *entry; + + AcquireSRWLockExclusive(&open->lock); + if (stateid->type == STATEID_LOCK) + lock_stateid_update(open, &stateid->stateid); + + /* find and remove the unlocked range */ + entry = list_search(&open->locks.list, input, lock_range_cmp); + if (entry) { + list_remove(entry); + free(lock_entry(entry)); + } + ReleaseSRWLockExclusive(&open->lock); +} + + +/* NFS41_LOCK */ +static int parse_lock(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall) +{ + int status; + lock_upcall_args *args = &upcall->args.lock; + + status = safe_read(&buffer, &length, &args->offset, sizeof(LONGLONG)); + if (status) goto out; + status = safe_read(&buffer, &length, &args->length, sizeof(LONGLONG)); + if (status) goto out; + status = safe_read(&buffer, &length, &args->exclusive, sizeof(BOOLEAN)); + if (status) goto out; + status = safe_read(&buffer, &length, &args->blocking, sizeof(BOOLEAN)); + if (status) goto out; + + dprintf(1, "parsing NFS41_LOCK: offset=0x%llx length=0x%llx exclusive=%u " + "blocking=%u\n", args->offset, args->length, args->exclusive, + args->blocking); +out: + return status; +} + +static __inline uint32_t get_lock_type(BOOLEAN exclusive, BOOLEAN blocking) +{ + return blocking == 0 + ? ( exclusive == 0 ? READ_LT : WRITE_LT ) + : ( exclusive == 0 ? READW_LT : WRITEW_LT ); +} + +static int handle_lock(nfs41_upcall *upcall) +{ + stateid_arg stateid; + lock_upcall_args *args = &upcall->args.lock; + nfs41_open_state *state = upcall->state_ref; + nfs41_lock_state *lock; + const uint32_t type = get_lock_type(args->exclusive, args->blocking); + int status = NO_ERROR; + + /* 18.10.3. Operation 12: LOCK - Create Lock + * "To lock the file from a specific offset through the end-of-file + * (no matter how long the file actually is) use a length field equal + * to NFS4_UINT64_MAX." */ + if (args->length >= NFS4_UINT64_MAX - args->offset) + args->length = NFS4_UINT64_MAX; + + /* allocate the lock state */ + lock = calloc(1, sizeof(nfs41_lock_state)); + if (lock == NULL) { + status = GetLastError(); + goto out; + } + lock->offset = args->offset; + lock->length = args->length; + lock->exclusive = args->exclusive; + + /* if we hold a write delegation, handle the lock locally */ + if (open_lock_delegate(state, lock)) { + dprintf(LKLVL, "delegated lock { %llu, %llu }\n", + lock->offset, lock->length); + args->acquired = TRUE; /* for cancel_lock() */ + goto out; + } + + /* open_to_lock_owner4 requires an open stateid; if we + * have a delegation, convert it to an open stateid */ + status = nfs41_delegation_to_open(state, TRUE); + if (status) { + status = ERROR_FILE_INVALID; + goto out_free; + } + + EnterCriticalSection(&state->locks.lock); + + lock_stateid_arg(state, &stateid); + + status = nfs41_lock(state->session, &state->file, &state->owner, + type, lock->offset, lock->length, FALSE, TRUE, &stateid); + if (status) { + dprintf(LKLVL, "nfs41_lock failed with %s\n", + nfs_error_string(status)); + status = nfs_to_windows_error(status, ERROR_BAD_NET_RESP); + LeaveCriticalSection(&state->locks.lock); + goto out_free; + } + + /* save lock state with the open */ + open_lock_add(state, &stateid, lock); + LeaveCriticalSection(&state->locks.lock); + + args->acquired = TRUE; /* for cancel_lock() */ +out: + return status; + +out_free: + free(lock); + goto out; +} + +static void cancel_lock(IN nfs41_upcall *upcall) +{ + stateid_arg stateid; + nfs41_lock_state input; + lock_upcall_args *args = &upcall->args.lock; + nfs41_open_state *state = upcall->state_ref; + int status = NO_ERROR; + + dprintf(1, "--> cancel_lock()\n"); + + /* can't do 'if (upcall->status)' here, because a handle_lock() success + * could be overwritten by upcall_marshall() or allocation failure */ + if (!args->acquired) + goto out; + + input.offset = args->offset; + input.length = args->length; + + /* search for the range to unlock, and remove if delegated */ + status = open_unlock_delegate(state, &input); + if (status != ERROR_LOCKED) + goto out; + + EnterCriticalSection(&state->locks.lock); + lock_stateid_arg(state, &stateid); + + status = nfs41_unlock(state->session, &state->file, + args->offset, args->length, &stateid); + + open_unlock_remove(state, &stateid, &input); + LeaveCriticalSection(&state->locks.lock); + + status = nfs_to_windows_error(status, ERROR_BAD_NET_RESP); +out: + dprintf(1, "<-- cancel_lock() returning %d\n", status); +} + + +/* NFS41_UNLOCK */ +static int parse_unlock(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall) +{ + int status; + unlock_upcall_args *args = &upcall->args.unlock; + + status = safe_read(&buffer, &length, &args->count, sizeof(ULONG)); + if (status) goto out; + + args->buf = buffer; + args->buf_len = length; + + dprintf(1, "parsing NFS41_UNLOCK: count=%u\n", args->count); +out: + return status; +} + +static int handle_unlock(nfs41_upcall *upcall) +{ + nfs41_lock_state input; + stateid_arg stateid; + unlock_upcall_args *args = &upcall->args.unlock; + nfs41_open_state *state = upcall->state_ref; + unsigned char *buf = args->buf; + uint32_t buf_len = args->buf_len; + uint32_t i; + int status = NO_ERROR; + + for (i = 0; i < args->count; i++) { + if (safe_read(&buf, &buf_len, &input.offset, sizeof(LONGLONG))) break; + if (safe_read(&buf, &buf_len, &input.length, sizeof(LONGLONG))) break; + + /* do the same translation as LOCK, or the ranges won't match */ + if (input.length >= NFS4_UINT64_MAX - input.offset) + input.length = NFS4_UINT64_MAX; + + /* search for the range to unlock, and remove if delegated */ + status = open_unlock_delegate(state, &input); + if (status != ERROR_LOCKED) + continue; + + EnterCriticalSection(&state->locks.lock); + lock_stateid_arg(state, &stateid); + + status = nfs41_unlock(state->session, &state->file, + input.offset, input.length, &stateid); + + open_unlock_remove(state, &stateid, &input); + LeaveCriticalSection(&state->locks.lock); + + status = nfs_to_windows_error(status, ERROR_BAD_NET_RESP); + } + return status; +} + + +const nfs41_upcall_op nfs41_op_lock = { + parse_lock, + handle_lock, + NULL, + cancel_lock +}; +const nfs41_upcall_op nfs41_op_unlock = { + parse_unlock, + handle_unlock +}; diff --git a/reactos/base/services/nfsd/lookup.c b/reactos/base/services/nfsd/lookup.c new file mode 100644 index 00000000000..1b84bdc37f4 --- /dev/null +++ b/reactos/base/services/nfsd/lookup.c @@ -0,0 +1,508 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include +#include +#include + +#include "nfs41_compound.h" +#include "nfs41_ops.h" +#include "name_cache.h" +#include "util.h" +#include "daemon_debug.h" + + +#define LULVL 2 /* dprintf level for lookup logging */ + + +#define MAX_LOOKUP_COMPONENTS 8 + +/* map NFS4ERR_MOVED to an arbitrary windows error */ +#define ERROR_FILESYSTEM_ABSENT ERROR_DEVICE_REMOVED + +struct lookup_referral { + nfs41_path_fh parent; + nfs41_component name; +}; + + +typedef struct __nfs41_lookup_component_args { + nfs41_sequence_args sequence; + nfs41_putfh_args putfh; + nfs41_lookup_args lookup[MAX_LOOKUP_COMPONENTS]; + nfs41_getattr_args getrootattr; + nfs41_getattr_args getattr[MAX_LOOKUP_COMPONENTS]; + bitmap4 attr_request; +} nfs41_lookup_component_args; + +typedef struct __nfs41_lookup_component_res { + nfs41_sequence_res sequence; + nfs41_putfh_res putfh; + nfs41_lookup_res lookup[MAX_LOOKUP_COMPONENTS]; + nfs41_getfh_res getrootfh; + nfs41_getfh_res getfh[MAX_LOOKUP_COMPONENTS]; + nfs41_path_fh root; + nfs41_path_fh file[MAX_LOOKUP_COMPONENTS]; + nfs41_getattr_res getrootattr; + nfs41_getattr_res getattr[MAX_LOOKUP_COMPONENTS]; + nfs41_file_info rootinfo; + nfs41_file_info info[MAX_LOOKUP_COMPONENTS]; + struct lookup_referral *referral; +} nfs41_lookup_component_res; + + +static void init_component_args( + IN nfs41_lookup_component_args *args, + IN nfs41_lookup_component_res *res, + IN nfs41_abs_path *path, + IN struct lookup_referral *referral) +{ + uint32_t i; + + args->attr_request.count = 2; + args->attr_request.arr[0] = FATTR4_WORD0_TYPE + | FATTR4_WORD0_CHANGE | FATTR4_WORD0_SIZE + | FATTR4_WORD0_FSID | FATTR4_WORD0_FILEID + | FATTR4_WORD0_HIDDEN | FATTR4_WORD0_ARCHIVE; + args->attr_request.arr[1] = FATTR4_WORD1_MODE + | FATTR4_WORD1_NUMLINKS | FATTR4_WORD1_SYSTEM + | FATTR4_WORD1_TIME_ACCESS | FATTR4_WORD1_TIME_CREATE + | FATTR4_WORD1_TIME_MODIFY; + + args->getrootattr.attr_request = &args->attr_request; + res->root.path = path; + res->getrootfh.fh = &res->root.fh; + res->getrootattr.info = &res->rootinfo; + res->getrootattr.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT; + res->referral = referral; + + for (i = 0; i < MAX_LOOKUP_COMPONENTS; i++) { + args->getattr[i].attr_request = &args->attr_request; + res->file[i].path = path; + args->lookup[i].name = &res->file[i].name; + res->getfh[i].fh = &res->file[i].fh; + res->getattr[i].info = &res->info[i]; + res->getattr[i].obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT; + } +} + +static int lookup_rpc( + IN nfs41_session *session, + IN nfs41_path_fh *dir, + IN uint32_t component_count, + IN nfs41_lookup_component_args *args, + OUT nfs41_lookup_component_res *res) +{ + int status; + uint32_t i; + nfs41_compound compound; + nfs_argop4 argops[4+MAX_LOOKUP_COMPONENTS*3]; + nfs_resop4 resops[4+MAX_LOOKUP_COMPONENTS*3]; + + compound_init(&compound, argops, resops, "lookup"); + + compound_add_op(&compound, OP_SEQUENCE, &args->sequence, &res->sequence); + nfs41_session_sequence(&args->sequence, session, 0); + + if (dir == &res->root) { + compound_add_op(&compound, OP_PUTROOTFH, NULL, &res->putfh); + compound_add_op(&compound, OP_GETFH, NULL, &res->getrootfh); + compound_add_op(&compound, OP_GETATTR, &args->getrootattr, + &res->getrootattr); + } else { + args->putfh.file = dir; + compound_add_op(&compound, OP_PUTFH, &args->putfh, &res->putfh); + } + + for (i = 0; i < component_count; i++) { + compound_add_op(&compound, OP_LOOKUP, &args->lookup[i], &res->lookup[i]); + compound_add_op(&compound, OP_GETFH, NULL, &res->getfh[i]); + compound_add_op(&compound, OP_GETATTR, &args->getattr[i], + &res->getattr[i]); + } + + status = compound_encode_send_decode(session, &compound, TRUE); + if (status) + goto out; + + compound_error(status = compound.res.status); +out: + return status; +} + +static int map_lookup_error(int status, bool_t last_component) +{ + switch (status) { + case NFS4ERR_NOENT: + if (last_component) return ERROR_FILE_NOT_FOUND; + else return ERROR_PATH_NOT_FOUND; + case NFS4ERR_SYMLINK: return ERROR_REPARSE; + case NFS4ERR_MOVED: return ERROR_FILESYSTEM_ABSENT; + default: return nfs_to_windows_error(status, ERROR_FILE_NOT_FOUND); + } +} + +static int server_lookup( + IN nfs41_session *session, + IN nfs41_path_fh *dir, + IN const char *path, + IN const char *path_end, + IN uint32_t count, + IN nfs41_lookup_component_args *args, + IN nfs41_lookup_component_res *res, + OUT OPTIONAL nfs41_path_fh **parent_out, + OUT OPTIONAL nfs41_path_fh **target_out, + OUT OPTIONAL nfs41_file_info *info_out) +{ + nfs41_path_fh *file, *parent; + uint32_t i = 0; + int status; + + if (parent_out) *parent_out = NULL; + if (target_out) *target_out = NULL; + + lookup_rpc(session, dir, count, args, res); + + status = res->sequence.sr_status; if (status) goto out; + status = res->putfh.status; if (status) goto out; + status = res->getrootfh.status; if (status) goto out; + status = res->getrootattr.status; if (status) goto out; + + if (dir == &res->root) { + nfs41_component name = { 0 }; + + /* fill in the file handle's fileid and superblock */ + dir->fh.fileid = res->getrootattr.info->fileid; + status = nfs41_superblock_for_fh(session, + &res->getrootattr.info->fsid, NULL, dir); + if (status) + goto out; + + /* get the name of the parent (empty if its the root) */ + last_component(path, count ? args->lookup[0].name->name : path_end, &name); + + /* add the file handle and attributes to the name cache */ + memcpy(&res->getrootattr.info->attrmask, + &res->getrootattr.obj_attributes.attrmask, sizeof(bitmap4)); + nfs41_name_cache_insert(session_name_cache(session), path, &name, + &dir->fh, res->getrootattr.info, NULL, OPEN_DELEGATE_NONE); + } + file = dir; + + if (count == 0) { + if (target_out) + *target_out = dir; + if (info_out) + memcpy(info_out, res->getrootattr.info, sizeof(nfs41_file_info)); + } else if (count == 1) { + if (parent_out) + *parent_out = dir; + } + + for (i = 0; i < count; i++) { + if (res->lookup[i].status == NFS4ERR_SYMLINK) { + /* return the symlink as the parent file */ + last_component(path, args->lookup[i].name->name, &file->name); + if (parent_out) *parent_out = file; + } else if (res->lookup[i].status == NFS4ERR_NOENT) { + /* insert a negative lookup entry */ + nfs41_name_cache_insert(session_name_cache(session), path, + args->lookup[i].name, NULL, NULL, NULL, OPEN_DELEGATE_NONE); + } + status = res->lookup[i].status; if (status) break; + + if (res->getfh[i].status == NFS4ERR_MOVED) { + /* save enough information to follow the referral */ + path_fh_copy(&res->referral->parent, file); + res->referral->name.name = args->lookup[i].name->name; + res->referral->name.len = args->lookup[i].name->len; + } + status = res->getfh[i].status; if (status) break; + status = res->getattr[i].status; if (status) break; + + parent = file; + file = &res->file[i]; + + /* fill in the file handle's fileid and superblock */ + file->fh.fileid = res->getattr[i].info->fileid; + status = nfs41_superblock_for_fh(session, + &res->getattr[i].info->fsid, &parent->fh, file); + if (status) + break; + + /* add the file handle and attributes to the name cache */ + memcpy(&res->getattr[i].info->attrmask, + &res->getattr[i].obj_attributes.attrmask, sizeof(bitmap4)); + nfs41_name_cache_insert(session_name_cache(session), + path, args->lookup[i].name, &res->file[i].fh, + res->getattr[i].info, NULL, OPEN_DELEGATE_NONE); + + if (i == count-1) { + if (target_out) + *target_out = file; + if (info_out) + memcpy(info_out, res->getattr[i].info, sizeof(nfs41_file_info)); + } else if (i == count-2) { + if (parent_out) + *parent_out = file; + } + } +out: + return map_lookup_error(status, i == count-1); +} + +static uint32_t max_lookup_components( + IN const nfs41_session *session) +{ + const uint32_t comps = (session->fore_chan_attrs.ca_maxoperations - 4) / 3; + return min(comps, MAX_LOOKUP_COMPONENTS); +} + +static uint32_t get_component_array( + IN OUT const char **path_pos, + IN const char *path_end, + IN uint32_t max_components, + OUT nfs41_path_fh *components, + OUT uint32_t *component_count) +{ + uint32_t i; + + for (i = 0; i < max_components; i++) { + if (!next_component(*path_pos, path_end, &components[i].name)) + break; + *path_pos = components[i].name.name + components[i].name.len; + } + + *component_count = i; + return i; +} + +static int server_lookup_loop( + IN nfs41_session *session, + IN OPTIONAL nfs41_path_fh *parent_in, + IN nfs41_abs_path *path, + IN const char *path_pos, + IN struct lookup_referral *referral, + OUT OPTIONAL nfs41_path_fh *parent_out, + OUT OPTIONAL nfs41_path_fh *target_out, + OUT OPTIONAL nfs41_file_info *info_out) +{ + nfs41_lookup_component_args args = { 0 }; + nfs41_lookup_component_res res = { 0 }; + nfs41_path_fh *dir, *parent, *target; + const char *path_end; + const uint32_t max_components = max_lookup_components(session); + uint32_t count; + int status = NO_ERROR; + + init_component_args(&args, &res, path, referral); + parent = NULL; + target = NULL; + + path_end = path->path + path->len; + dir = parent_in ? parent_in : &res.root; + + while (get_component_array(&path_pos, path_end, + max_components, res.file, &count)) { + + status = server_lookup(session, dir, path->path, path_end, count, + &args, &res, &parent, &target, info_out); + + if (status == ERROR_REPARSE) { + /* copy the component name of the symlink */ + if (parent_out && parent) { + const ptrdiff_t offset = parent->name.name - path->path; + parent_out->name.name = parent_out->path->path + offset; + parent_out->name.len = parent->name.len; + } + goto out_parent; + } + if (status == ERROR_FILE_NOT_FOUND && is_last_component(path_pos, path_end)) + goto out_parent; + if (status) + goto out; + + dir = target; + } + + if (dir == &res.root && (target_out || info_out)) { + /* didn't get any components, so we just need the root */ + status = server_lookup(session, dir, path->path, path_end, + 0, &args, &res, &parent, &target, info_out); + if (status) + goto out; + } + + if (target_out && target) fh_copy(&target_out->fh, &target->fh); +out_parent: + if (parent_out && parent) fh_copy(&parent_out->fh, &parent->fh); +out: + return status; +} + + +static void referral_locations_free( + IN fs_locations4 *locations) +{ + uint32_t i; + if (locations->locations) { + for (i = 0; i < locations->location_count; i++) + free(locations->locations[i].servers); + free(locations->locations); + } +} + +static int referral_resolve( + IN nfs41_root *root, + IN nfs41_session *session_in, + IN struct lookup_referral *referral, + OUT nfs41_abs_path *path_out, + OUT nfs41_session **session_out) +{ + char rest_of_path[NFS41_MAX_PATH_LEN]; + fs_locations4 locations = { 0 }; + const fs_location4 *location; + nfs41_client *client; + int status; + + /* get fs_locations */ + status = nfs41_fs_locations(session_in, &referral->parent, + &referral->name, &locations); + if (status) { + eprintf("nfs41_fs_locations() failed with %s\n", + nfs_error_string(status)); + status = nfs_to_windows_error(status, ERROR_PATH_NOT_FOUND); + goto out; + } + + /* mount the first location available */ + status = nfs41_root_mount_referral(root, &locations, &location, &client); + if (status) { + eprintf("nfs41_root_mount_referral() failed with %d\n", + status); + goto out; + } + + /* format a new path from that location's root */ + if (FAILED(StringCchCopyA(rest_of_path, NFS41_MAX_PATH_LEN, + referral->name.name + referral->name.len))) { + status = ERROR_FILENAME_EXCED_RANGE; + goto out; + } + + AcquireSRWLockExclusive(&path_out->lock); + abs_path_copy(path_out, &location->path); + if (FAILED(StringCchCatA(path_out->path, NFS41_MAX_PATH_LEN, rest_of_path))) + status = ERROR_FILENAME_EXCED_RANGE; + path_out->len = path_out->len + (unsigned short)strlen(rest_of_path); + ReleaseSRWLockExclusive(&path_out->lock); + + if (session_out) *session_out = client->session; +out: + referral_locations_free(&locations); + return status; +} + +int nfs41_lookup( + IN nfs41_root *root, + IN nfs41_session *session, + IN OUT nfs41_abs_path *path_inout, + OUT OPTIONAL nfs41_path_fh *parent_out, + OUT OPTIONAL nfs41_path_fh *target_out, + OUT OPTIONAL nfs41_file_info *info_out, + OUT nfs41_session **session_out) +{ + nfs41_abs_path path; + struct nfs41_name_cache *cache = session_name_cache(session); + nfs41_path_fh parent, target, *server_start; + const char *path_pos, *path_end; + struct lookup_referral referral; + bool_t negative = 0; + int status; + + if (session_out) *session_out = session; + + InitializeSRWLock(&path.lock); + + /* to avoid holding this lock over multiple rpcs, + * make a copy of the path and use that instead */ + AcquireSRWLockShared(&path_inout->lock); + abs_path_copy(&path, path_inout); + ReleaseSRWLockShared(&path_inout->lock); + + path_pos = path.path; + path_end = path.path + path.len; + + dprintf(LULVL, "--> nfs41_lookup('%s')\n", path.path); + + if (parent_out == NULL) parent_out = &parent; + if (target_out == NULL) target_out = ⌖ + parent_out->fh.len = target_out->fh.len = 0; + + status = nfs41_name_cache_lookup(cache, path_pos, path_end, &path_pos, + &parent_out->fh, &target_out->fh, info_out, &negative); + if (status == NO_ERROR || negative) + goto out; + + if (parent_out->fh.len) { + /* start where the name cache left off */ + if (&parent != parent_out) { + /* must make a copy for server_start, because + * server_lookup_loop() will overwrite parent_out */ + path_fh_copy(&parent, parent_out); + } + server_start = &parent; + } else { + /* start with PUTROOTFH */ + server_start = NULL; + } + + status = server_lookup_loop(session, server_start, + &path, path_pos, &referral, parent_out, target_out, info_out); + + if (status == ERROR_FILESYSTEM_ABSENT) { + nfs41_session *new_session; + + /* create a session to the referred server and + * reformat the path relative to that server's root */ + status = referral_resolve(root, session, + &referral, path_inout, &new_session); + if (status) { + eprintf("referral_resolve() failed with %d\n", status); + goto out; + } + + /* update the positions of the parent and target components */ + last_component(path_inout->path, path_inout->path + path_inout->len, + &target_out->name); + last_component(path_inout->path, target_out->name.name, + &parent_out->name); + + if (session_out) *session_out = new_session; + + /* look up the new path */ + status = nfs41_lookup(root, new_session, path_inout, + parent_out, target_out, info_out, session_out); + } +out: + dprintf(LULVL, "<-- nfs41_lookup() returning %d\n", status); + return status; +} diff --git a/reactos/base/services/nfsd/makefile b/reactos/base/services/nfsd/makefile new file mode 100644 index 00000000000..d5bedee2a8e --- /dev/null +++ b/reactos/base/services/nfsd/makefile @@ -0,0 +1,8 @@ +# +# DO NOT EDIT THIS FILE!!! Edit .\sources. if you want to add a new source +# file to this component. This file merely indirects to the real make file +# that is shared by all the driver components of the Windows NT DDK +# + +!INCLUDE $(NTMAKEENV)\makefile.def + diff --git a/reactos/base/services/nfsd/mount.c b/reactos/base/services/nfsd/mount.c new file mode 100644 index 00000000000..ea059c493b7 --- /dev/null +++ b/reactos/base/services/nfsd/mount.c @@ -0,0 +1,176 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include +#include +#include + +#include "daemon_debug.h" +#include "nfs41_ops.h" +#include "upcall.h" +#include "util.h" + + +/* NFS41_MOUNT */ +static int parse_mount(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall) +{ + int status; + mount_upcall_args *args = &upcall->args.mount; + + status = get_name(&buffer, &length, &args->hostname); + if(status) goto out; + status = get_name(&buffer, &length, &args->path); + if(status) goto out; + status = safe_read(&buffer, &length, &args->sec_flavor, sizeof(DWORD)); + if (status) goto out; + status = safe_read(&buffer, &length, &args->rsize, sizeof(DWORD)); + if (status) goto out; + status = safe_read(&buffer, &length, &args->wsize, sizeof(DWORD)); + if (status) goto out; + + dprintf(1, "parsing NFS14_MOUNT: srv_name=%s root=%s sec_flavor=%s " + "rsize=%d wsize=%d\n", args->hostname, args->path, + secflavorop2name(args->sec_flavor), args->rsize, args->wsize); +out: + return status; +} + +static int handle_mount(nfs41_upcall *upcall) +{ + int status; + mount_upcall_args *args = &upcall->args.mount; + nfs41_abs_path path; + multi_addr4 addrs; + nfs41_root *root; + nfs41_client *client; + nfs41_path_fh file; + + // resolve hostname,port + status = nfs41_server_resolve(args->hostname, 2049, &addrs); + if (status) { + eprintf("nfs41_server_resolve() failed with %d\n", status); + goto out; + } + + if (upcall->root_ref != INVALID_HANDLE_VALUE) { + /* use an existing root from a previous mount, but don't take an + * extra reference; we'll only get one UNMOUNT upcall for each root */ + root = upcall->root_ref; + } else { + // create root + status = nfs41_root_create(args->hostname, args->sec_flavor, + args->wsize + WRITE_OVERHEAD, args->rsize + READ_OVERHEAD, &root); + if (status) { + eprintf("nfs41_root_create() failed %d\n", status); + goto out; + } + root->uid = upcall->uid; + root->gid = upcall->gid; + } + + // find or create the client/session + status = nfs41_root_mount_addrs(root, &addrs, 0, 0, &client); + if (status) { + eprintf("nfs41_root_mount_addrs() failed with %d\n", status); + goto out_err; + } + + // make a copy of the path for nfs41_lookup() + InitializeSRWLock(&path.lock); + if (FAILED(StringCchCopyA(path.path, NFS41_MAX_PATH_LEN, args->path))) { + status = ERROR_FILENAME_EXCED_RANGE; + goto out_err; + } + path.len = (unsigned short)strlen(path.path); + + // look up the mount path, and fail if it doesn't exist + status = nfs41_lookup(root, client->session, + &path, NULL, &file, NULL, NULL); + if (status) { + eprintf("nfs41_lookup('%s') failed with %d\n", path.path, status); + status = ERROR_BAD_NETPATH; + goto out_err; + } + + nfs41_superblock_fs_attributes(file.fh.superblock, &args->FsAttrs); + + if (upcall->root_ref == INVALID_HANDLE_VALUE) + nfs41_root_ref(root); + upcall->root_ref = root; + args->lease_time = client->session->lease_time; +out: + return status; + +out_err: + if (upcall->root_ref == INVALID_HANDLE_VALUE) + nfs41_root_deref(root); + goto out; +} + +static int marshall_mount(unsigned char *buffer, uint32_t *length, nfs41_upcall *upcall) +{ + mount_upcall_args *args = &upcall->args.mount; + int status; + dprintf(2, "NFS41_MOUNT: writing pointer to nfs41_root %p, version %d, " + "lease_time %d\n", upcall->root_ref, NFS41D_VERSION, args->lease_time); + status = safe_write(&buffer, length, &upcall->root_ref, sizeof(HANDLE)); + if (status) goto out; + status = safe_write(&buffer, length, &NFS41D_VERSION, sizeof(DWORD)); + if (status) goto out; + status = safe_write(&buffer, length, &args->lease_time, sizeof(DWORD)); + if (status) goto out; + status = safe_write(&buffer, length, &args->FsAttrs, sizeof(args->FsAttrs)); +out: + return status; +} + +static void cancel_mount(IN nfs41_upcall *upcall) +{ + if (upcall->root_ref != INVALID_HANDLE_VALUE) + nfs41_root_deref(upcall->root_ref); +} + +const nfs41_upcall_op nfs41_op_mount = { + parse_mount, + handle_mount, + marshall_mount, + cancel_mount +}; + + +/* NFS41_UNMOUNT */ +static int parse_unmount(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall) +{ + dprintf(1, "parsing NFS41_UNMOUNT: root=%p\n", upcall->root_ref); + return ERROR_SUCCESS; +} + +static int handle_unmount(nfs41_upcall *upcall) +{ + /* release the original reference from nfs41_root_create() */ + nfs41_root_deref(upcall->root_ref); + return ERROR_SUCCESS; +} + +const nfs41_upcall_op nfs41_op_unmount = { + parse_unmount, + handle_unmount +}; diff --git a/reactos/base/services/nfsd/ms-nfs41-idmap.conf b/reactos/base/services/nfsd/ms-nfs41-idmap.conf new file mode 100644 index 00000000000..111a34bae14 --- /dev/null +++ b/reactos/base/services/nfsd/ms-nfs41-idmap.conf @@ -0,0 +1,20 @@ +# ldap server information +#ldap_hostname="localhost" +#ldap_port="389" +#ldap_version="3" +#ldap_timeout="5" + +# ldap schema information +#ldap_base="cn=localhost" + +#ldap_class_users="user" +#ldap_class_groups="group" + +#ldap_attr_username="cn" +#ldap_attr_groupname="cn" +#ldap_attr_gssAuthName="gssAuthName" +#ldap_attr_uidNumber="uidNumber" +#ldap_attr_gidNumber="gidNumber" + +# caching configuration +#cache_ttl="60" diff --git a/reactos/base/services/nfsd/name_cache.c b/reactos/base/services/nfsd/name_cache.c new file mode 100644 index 00000000000..f8c73028e49 --- /dev/null +++ b/reactos/base/services/nfsd/name_cache.c @@ -0,0 +1,1399 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include +#include +#include +#include + +#include "nfs41_ops.h" +#include "nfs41_compound.h" +#include "name_cache.h" +#include "util.h" +#include "tree.h" +#include "daemon_debug.h" + + +/* dprintf levels for name cache logging */ +enum { + NCLVL1 = 2, + NCLVL2 +}; + + +#define NAME_CACHE_EXPIRATION 20 /* TODO: get from configuration */ + +/* allow up to 256K of memory for name and attribute cache entries */ +#define NAME_CACHE_MAX_SIZE 262144 + +/* negative lookup caching + * + * by caching lookups that result in NOENT, we can avoid sending subsequent + * lookups over the wire. a name cache entry is negative when its attributes + * pointer is NULL. negative entries are created by three functions: + * nfs41_name_cache_remove(), _insert() when called with NULL for the fh and + * attributes, and _rename() for the source entry */ + +/* delegations and cache feedback + * + * delegations provide a guarantee that no links or attributes will change + * without notice. the name cache takes advantage of this by preventing + * delegated entries from being removed on NAME_CACHE_EXPIRATION, though + * they're still removed when a parent is invalidated. the attribute cache + * holds an extra reference on delegated entries to prevent their removal + * entirely, until the delegation is returned. + * this extra reference presents a problem when the number of delegations + * approaches the maximum number of attribute cache entries. when there are + * not enough available entries to store the parent directories, every lookup + * results in a name cache miss, and cache performance degrades significantly. + * the solution is to provide feedback via nfs41_name_cache_insert() when + * delegations reach a certain percent of the cache capacity. the error code + * ERROR_TOO_MANY_OPEN_FILES, chosen arbitrarily for this case, instructs the + * caller to return an outstanding delegation before caching a new one. + */ +static __inline bool_t is_delegation( + IN enum open_delegation_type4 type) +{ + return type == OPEN_DELEGATE_READ || type == OPEN_DELEGATE_WRITE; +} + + +/* attribute cache */ +struct attr_cache_entry { + RB_ENTRY(attr_cache_entry) rbnode; + struct list_entry free_entry; + uint64_t change; + uint64_t size; + uint64_t fileid; + int64_t time_access_s; + int64_t time_create_s; + int64_t time_modify_s; + uint32_t time_access_ns; + uint32_t time_create_ns; + uint32_t time_modify_ns; + uint32_t numlinks; + unsigned mode : 30; + unsigned hidden : 1; + unsigned system : 1; + unsigned archive : 1; + time_t expiration; + unsigned ref_count : 26; + unsigned type : 4; + unsigned invalidated : 1; + unsigned delegated : 1; +}; +#define ATTR_ENTRY_SIZE sizeof(struct attr_cache_entry) + +RB_HEAD(attr_tree, attr_cache_entry); + +struct attr_cache { + struct attr_tree head; + struct attr_cache_entry *pool; + struct list_entry free_entries; +}; + +int attr_cmp(struct attr_cache_entry *lhs, struct attr_cache_entry *rhs) +{ + return lhs->fileid < rhs->fileid ? -1 : lhs->fileid > rhs->fileid; +} +RB_GENERATE(attr_tree, attr_cache_entry, rbnode, attr_cmp) + + +/* attr_cache_entry */ +#define attr_entry(pos) list_container(pos, struct attr_cache_entry, free_entry) + +static int attr_cache_entry_create( + IN struct attr_cache *cache, + IN uint64_t fileid, + OUT struct attr_cache_entry **entry_out) +{ + struct attr_cache_entry *entry; + int status = NO_ERROR; + + /* get the next entry from free_entries and remove it */ + if (list_empty(&cache->free_entries)) { + status = ERROR_OUTOFMEMORY; + goto out; + } + entry = attr_entry(cache->free_entries.next); + list_remove(&entry->free_entry); + + entry->fileid = fileid; + entry->invalidated = FALSE; + entry->delegated = FALSE; + *entry_out = entry; +out: + return status; +} + +static __inline void attr_cache_entry_free( + IN struct attr_cache *cache, + IN struct attr_cache_entry *entry) +{ + dprintf(NCLVL1, "attr_cache_entry_free(%llu)\n", entry->fileid); + RB_REMOVE(attr_tree, &cache->head, entry); + /* add it back to free_entries */ + list_add_tail(&cache->free_entries, &entry->free_entry); +} + +static __inline void attr_cache_entry_ref( + IN struct attr_cache *cache, + IN struct attr_cache_entry *entry) +{ + const uint32_t previous = entry->ref_count++; + dprintf(NCLVL2, "attr_cache_entry_ref(%llu) %u -> %u\n", + entry->fileid, previous, entry->ref_count); +} + +static __inline void attr_cache_entry_deref( + IN struct attr_cache *cache, + IN struct attr_cache_entry *entry) +{ + const uint32_t previous = entry->ref_count--; + dprintf(NCLVL2, "attr_cache_entry_deref(%llu) %u -> %u\n", + entry->fileid, previous, entry->ref_count); + + if (entry->ref_count == 0) + attr_cache_entry_free(cache, entry); +} + +static __inline int attr_cache_entry_expired( + IN const struct attr_cache_entry *entry) +{ + return entry->invalidated || + (!entry->delegated && time(NULL) > entry->expiration); +} + +/* attr_cache */ +static int attr_cache_init( + IN struct attr_cache *cache, + IN uint32_t max_entries) +{ + uint32_t i; + int status = NO_ERROR; + + /* allocate a pool of entries */ + cache->pool = calloc(max_entries, ATTR_ENTRY_SIZE); + if (cache->pool == NULL) { + status = GetLastError(); + goto out; + } + + /* initialize the list of free entries */ + list_init(&cache->free_entries); + for (i = 0; i < max_entries; i++) { + list_init(&cache->pool[i].free_entry); + list_add_tail(&cache->free_entries, &cache->pool[i].free_entry); + } +out: + return status; +} + +static void attr_cache_free( + IN struct attr_cache *cache) +{ + /* free the pool */ + free(cache->pool); + cache->pool = NULL; + list_init(&cache->free_entries); +} + +static struct attr_cache_entry* attr_cache_search( + IN struct attr_cache *cache, + IN uint64_t fileid) +{ + /* find an entry that matches fileid */ + struct attr_cache_entry tmp; + tmp.fileid = fileid; + return RB_FIND(attr_tree, &cache->head, &tmp); +} + +static int attr_cache_insert( + IN struct attr_cache *cache, + IN struct attr_cache_entry *entry) +{ + int status = NO_ERROR; + + dprintf(NCLVL2, "--> attr_cache_insert(%llu)\n", entry->fileid); + + if (RB_INSERT(attr_tree, &cache->head, entry)) + status = ERROR_FILE_EXISTS; + + dprintf(NCLVL2, "<-- attr_cache_insert() returning %d\n", status); + return status; +} + +static int attr_cache_find_or_create( + IN struct attr_cache *cache, + IN uint64_t fileid, + OUT struct attr_cache_entry **entry_out) +{ + struct attr_cache_entry *entry; + int status = NO_ERROR; + + dprintf(NCLVL1, "--> attr_cache_find_or_create(%llu)\n", fileid); + + /* look for an existing entry */ + entry = attr_cache_search(cache, fileid); + if (entry == NULL) { + /* create and insert */ + status = attr_cache_entry_create(cache, fileid, &entry); + if (status) + goto out; + + status = attr_cache_insert(cache, entry); + if (status) + goto out_err_free; + } + + /* take a reference on success */ + attr_cache_entry_ref(cache, entry); + +out: + *entry_out = entry; + dprintf(NCLVL1, "<-- attr_cache_find_or_create() returning %d\n", + status); + return status; + +out_err_free: + attr_cache_entry_free(cache, entry); + entry = NULL; + goto out; +} + +static void attr_cache_update( + IN struct attr_cache_entry *entry, + IN const nfs41_file_info *info, + IN enum open_delegation_type4 delegation) +{ + /* update the attributes present in mask */ + if (info->attrmask.count >= 1) { + if (info->attrmask.arr[0] & FATTR4_WORD0_TYPE) + entry->type = (unsigned char)(info->type & NFS_FTYPE_MASK); + if (info->attrmask.arr[0] & FATTR4_WORD0_CHANGE) { + entry->change = info->change; + /* revalidate whenever we get a change attribute */ + entry->invalidated = 0; + entry->expiration = time(NULL) + NAME_CACHE_EXPIRATION; + } + if (info->attrmask.arr[0] & FATTR4_WORD0_SIZE) + entry->size = info->size; + if (info->attrmask.arr[0] & FATTR4_WORD0_HIDDEN) + entry->hidden = info->hidden; + if (info->attrmask.arr[0] & FATTR4_WORD0_ARCHIVE) + entry->archive = info->archive; + } + if (info->attrmask.count >= 2) { + if (info->attrmask.arr[1] & FATTR4_WORD1_MODE) + entry->mode = info->mode; + if (info->attrmask.arr[1] & FATTR4_WORD1_NUMLINKS) + entry->numlinks = info->numlinks; + if (info->attrmask.arr[1] & FATTR4_WORD1_TIME_ACCESS) { + entry->time_access_s = info->time_access.seconds; + entry->time_access_ns = info->time_access.nseconds; + } + if (info->attrmask.arr[1] & FATTR4_WORD1_TIME_CREATE) { + entry->time_create_s = info->time_create.seconds; + entry->time_create_ns = info->time_create.nseconds; + } + if (info->attrmask.arr[1] & FATTR4_WORD1_TIME_MODIFY) { + entry->time_modify_s = info->time_modify.seconds; + entry->time_modify_ns = info->time_modify.nseconds; + } + if (info->attrmask.arr[1] & FATTR4_WORD1_SYSTEM) + entry->system = info->system; + } + + if (is_delegation(delegation)) + entry->delegated = TRUE; +} + +static void copy_attrs( + OUT nfs41_file_info *dst, + IN const struct attr_cache_entry *src) +{ + dst->change = src->change; + dst->size = src->size; + dst->time_access.seconds = src->time_access_s; + dst->time_access.nseconds = src->time_access_ns; + dst->time_create.seconds = src->time_create_s; + dst->time_create.nseconds = src->time_create_ns; + dst->time_modify.seconds = src->time_modify_s; + dst->time_modify.nseconds = src->time_modify_ns; + dst->type = src->type; + dst->numlinks = src->numlinks; + dst->mode = src->mode; + dst->fileid = src->fileid; + dst->hidden = src->hidden; + dst->system = src->system; + dst->archive = src->archive; + + dst->attrmask.count = 2; + dst->attrmask.arr[0] = FATTR4_WORD0_TYPE | FATTR4_WORD0_CHANGE + | FATTR4_WORD0_SIZE | FATTR4_WORD0_FILEID + | FATTR4_WORD0_HIDDEN | FATTR4_WORD0_ARCHIVE; + dst->attrmask.arr[1] = FATTR4_WORD1_MODE + | FATTR4_WORD1_NUMLINKS | FATTR4_WORD1_TIME_ACCESS + | FATTR4_WORD1_TIME_CREATE | FATTR4_WORD1_TIME_MODIFY + | FATTR4_WORD1_SYSTEM; +} + + +/* name cache */ +RB_HEAD(name_tree, name_cache_entry); +struct name_cache_entry { + char component[NFS41_MAX_COMPONENT_LEN]; + nfs41_fh fh; + RB_ENTRY(name_cache_entry) rbnode; + struct name_tree rbchildren; + struct attr_cache_entry *attributes; + struct name_cache_entry *parent; + struct list_entry exp_entry; + time_t expiration; + unsigned short component_len; +}; +#define NAME_ENTRY_SIZE sizeof(struct name_cache_entry) + +int name_cmp(struct name_cache_entry *lhs, struct name_cache_entry *rhs) +{ + const int diff = rhs->component_len - lhs->component_len; + return diff ? diff : strncmp(lhs->component, rhs->component, lhs->component_len); +} +RB_GENERATE(name_tree, name_cache_entry, rbnode, name_cmp) + +struct nfs41_name_cache { + struct name_cache_entry *root; + struct name_cache_entry *pool; + struct attr_cache attributes; + struct list_entry exp_entries; /* list of entries by expiry */ + uint32_t expiration; + uint32_t entries; + uint32_t max_entries; + uint32_t delegations; + uint32_t max_delegations; + SRWLOCK lock; +}; + + +/* internal name cache functions used by the public name cache interface; + * these functions expect the caller to hold a lock on the cache */ + +#define name_entry(pos) list_container(pos, struct name_cache_entry, exp_entry) + +static __inline bool_t name_cache_enabled( + IN struct nfs41_name_cache *cache) +{ + return cache->expiration > 0; +} + +static __inline void name_cache_entry_rename( + OUT struct name_cache_entry *entry, + IN const nfs41_component *component) +{ + StringCchCopyNA(entry->component, NFS41_MAX_COMPONENT_LEN, + component->name, component->len); + entry->component_len = component->len; +} + +static __inline void name_cache_remove( + IN struct name_cache_entry *entry, + IN struct name_cache_entry *parent) +{ + RB_REMOVE(name_tree, &parent->rbchildren, entry); + entry->parent = NULL; +} + +static void name_cache_unlink_children_recursive( + IN struct nfs41_name_cache *cache, + IN struct name_cache_entry *parent); + +static __inline void name_cache_unlink( + IN struct nfs41_name_cache *cache, + IN struct name_cache_entry *entry) +{ + /* remove the entry from the tree */ + if (entry->parent) + name_cache_remove(entry, entry->parent); + else if (entry == cache->root) + cache->root = NULL; + + /* unlink all of its children */ + name_cache_unlink_children_recursive(cache, entry); + /* release the cached attributes */ + if (entry->attributes) { + attr_cache_entry_deref(&cache->attributes, entry->attributes); + entry->attributes = NULL; + } + /* move it to the end of exp_entries for scavenging */ + list_remove(&entry->exp_entry); + list_add_tail(&cache->exp_entries, &entry->exp_entry); +} + +static void name_cache_unlink_children_recursive( + IN struct nfs41_name_cache *cache, + IN struct name_cache_entry *parent) +{ + struct name_cache_entry *entry, *tmp; + RB_FOREACH_SAFE(entry, name_tree, &parent->rbchildren, tmp) + name_cache_unlink(cache, entry); +} + +static int name_cache_entry_create( + IN struct nfs41_name_cache *cache, + IN const nfs41_component *component, + OUT struct name_cache_entry **entry_out) +{ + int status = NO_ERROR; + struct name_cache_entry *entry; + + if (cache->entries >= cache->max_entries) { + /* scavenge the oldest entry */ + if (list_empty(&cache->exp_entries)) { + status = ERROR_OUTOFMEMORY; + goto out; + } + entry = name_entry(cache->exp_entries.prev); + name_cache_unlink(cache, entry); + + dprintf(NCLVL2, "name_cache_entry_create('%s') scavenged 0x%p\n", + component->name, entry); + } else { + /* take the next entry in the pool and add it to exp_entries */ + entry = &cache->pool[cache->entries++]; + list_init(&entry->exp_entry); + list_add_tail(&cache->exp_entries, &entry->exp_entry); + } + + name_cache_entry_rename(entry, component); + + *entry_out = entry; +out: + return status; +} + +static void name_cache_entry_accessed( + IN struct nfs41_name_cache *cache, + IN struct name_cache_entry *entry) +{ + /* move the entry to the front of cache->exp_entries, then do + * the same for its parents, which are more costly to evict */ + while (entry) { + /* if entry is delegated, it won't be in the list */ + if (!list_empty(&entry->exp_entry)) { + list_remove(&entry->exp_entry); + list_add_head(&cache->exp_entries, &entry->exp_entry); + } + if (entry == entry->parent) + break; + entry = entry->parent; + } +} + +static void name_cache_entry_updated( + IN struct nfs41_name_cache *cache, + IN struct name_cache_entry *entry) +{ + /* update the expiration timer */ + entry->expiration = time(NULL) + cache->expiration; + name_cache_entry_accessed(cache, entry); +} + +static int name_cache_entry_update( + IN struct nfs41_name_cache *cache, + IN struct name_cache_entry *entry, + IN OPTIONAL const nfs41_fh *fh, + IN OPTIONAL const nfs41_file_info *info, + IN enum open_delegation_type4 delegation) +{ + int status = NO_ERROR; + + if (fh) + fh_copy(&entry->fh, fh); + else + entry->fh.len = 0; + + if (info) { + if (entry->attributes == NULL) { + /* negative -> positive entry, create the attributes */ + status = attr_cache_find_or_create(&cache->attributes, + info->fileid, &entry->attributes); + if (status) + goto out; + } + + attr_cache_update(entry->attributes, info, delegation); + + /* hold a reference as long as we have the delegation */ + if (is_delegation(delegation)) { + attr_cache_entry_ref(&cache->attributes, entry->attributes); + cache->delegations++; + } + + /* keep the entry from expiring */ + if (entry->attributes->delegated) + list_remove(&entry->exp_entry); + } else if (entry->attributes) { + /* positive -> negative entry, deref the attributes */ + attr_cache_entry_deref(&cache->attributes, entry->attributes); + entry->attributes = NULL; + } + name_cache_entry_updated(cache, entry); +out: + return status; +} + +static int name_cache_entry_changed( + IN struct nfs41_name_cache *cache, + IN struct name_cache_entry *entry, + IN const change_info4 *cinfo) +{ + if (entry->attributes == NULL) + return FALSE; + + if (cinfo->after == entry->attributes->change || + (cinfo->atomic && cinfo->before == entry->attributes->change)) { + entry->attributes->change = cinfo->after; + name_cache_entry_updated(cache, entry); + dprintf(NCLVL1, "name_cache_entry_changed('%s') has not changed. " + "updated change=%llu\n", entry->component, + entry->attributes->change); + return FALSE; + } else { + dprintf(NCLVL1, "name_cache_entry_changed('%s') has changed: was %llu, " + "got before=%llu\n", entry->component, + entry->attributes->change, cinfo->before); + return TRUE; + } +} + +static void name_cache_entry_invalidate( + IN struct nfs41_name_cache *cache, + IN struct name_cache_entry *entry) +{ + dprintf(NCLVL1, "name_cache_entry_invalidate('%s')\n", entry->component); + + if (entry->attributes) { + /* flag attributes so that entry_invis() will return true + * if another entry attempts to use them */ + entry->attributes->invalidated = 1; + } + name_cache_unlink(cache, entry); +} + +static struct name_cache_entry* name_cache_search( + IN struct nfs41_name_cache *cache, + IN struct name_cache_entry *parent, + IN const nfs41_component *component) +{ + struct name_cache_entry tmp, *entry; + + dprintf(NCLVL2, "--> name_cache_search('%.*s' under '%s')\n", + component->len, component->name, parent->component); + + StringCchCopyNA(tmp.component, NFS41_MAX_COMPONENT_LEN, + component->name, component->len); + tmp.component_len = component->len; + + entry = RB_FIND(name_tree, &parent->rbchildren, &tmp); + if (entry) + dprintf(NCLVL2, "<-- name_cache_search() " + "found existing entry 0x%p\n", entry); + else + dprintf(NCLVL2, "<-- name_cache_search() returning NULL\n"); + return entry; +} + +static int entry_invis( + IN struct name_cache_entry *entry, + OUT OPTIONAL bool_t *is_negative) +{ + /* name entry timer expired? */ + if (!list_empty(&entry->exp_entry) && time(NULL) > entry->expiration) { + dprintf(NCLVL2, "name_entry_expired('%s')\n", entry->component); + return 1; + } + /* negative lookup entry? */ + if (entry->attributes == NULL) { + if (is_negative) *is_negative = 1; + dprintf(NCLVL2, "name_entry_negative('%s')\n", entry->component); + return 1; + } + /* attribute entry expired? */ + if (attr_cache_entry_expired(entry->attributes)) { + dprintf(NCLVL2, "attr_entry_expired(%llu)\n", + entry->attributes->fileid); + return 1; + } + return 0; +} + +static int name_cache_lookup( + IN struct nfs41_name_cache *cache, + IN bool_t skip_invis, + IN const char *path, + IN const char *path_end, + OUT OPTIONAL const char **remaining_path_out, + OUT OPTIONAL struct name_cache_entry **parent_out, + OUT OPTIONAL struct name_cache_entry **target_out, + OUT OPTIONAL bool_t *is_negative) +{ + struct name_cache_entry *parent, *target; + nfs41_component component; + const char *path_pos; + int status = NO_ERROR; + + dprintf(NCLVL1, "--> name_cache_lookup('%s')\n", path); + + parent = NULL; + target = cache->root; + component.name = path_pos = path; + + if (target == NULL || (skip_invis && entry_invis(target, is_negative))) { + target = NULL; + status = ERROR_PATH_NOT_FOUND; + goto out; + } + + while (next_component(path_pos, path_end, &component)) { + parent = target; + target = name_cache_search(cache, parent, &component); + path_pos = component.name + component.len; + if (target == NULL || (skip_invis && entry_invis(target, is_negative))) { + target = NULL; + if (is_last_component(component.name, path_end)) + status = ERROR_FILE_NOT_FOUND; + else + status = ERROR_PATH_NOT_FOUND; + break; + } + } +out: + if (remaining_path_out) *remaining_path_out = component.name; + if (parent_out) *parent_out = parent; + if (target_out) *target_out = target; + dprintf(NCLVL1, "<-- name_cache_lookup() returning %d\n", status); + return status; +} + +static int name_cache_insert( + IN struct name_cache_entry *entry, + IN struct name_cache_entry *parent) +{ + int status = NO_ERROR; + + dprintf(NCLVL2, "--> name_cache_insert('%s')\n", entry->component); + + if (RB_INSERT(name_tree, &parent->rbchildren, entry)) + status = ERROR_FILE_EXISTS; + entry->parent = parent; + + dprintf(NCLVL2, "<-- name_cache_insert() returning %u\n", status); + return status; +} + +static int name_cache_find_or_create( + IN struct nfs41_name_cache *cache, + IN struct name_cache_entry *parent, + IN const nfs41_component *component, + OUT struct name_cache_entry **target_out) +{ + int status = NO_ERROR; + + dprintf(NCLVL1, "--> name_cache_find_or_create('%.*s' under '%s')\n", + component->len, component->name, parent->component); + + *target_out = name_cache_search(cache, parent, component); + if (*target_out) + goto out; + + status = name_cache_entry_create(cache, component, target_out); + if (status) + goto out; + + status = name_cache_insert(*target_out, parent); + if (status) + goto out_err; + +out: + dprintf(NCLVL1, "<-- name_cache_find_or_create() returning %d\n", + status); + return status; + +out_err: + *target_out = NULL; + goto out; +} + + +/* public name cache interface, declared in name_cache.h */ + +/* assuming no hard links, calculate how many entries will fit in the cache */ +#define SIZE_PER_ENTRY (ATTR_ENTRY_SIZE + NAME_ENTRY_SIZE) +#define NAME_CACHE_MAX_ENTRIES (NAME_CACHE_MAX_SIZE / SIZE_PER_ENTRY) + +int nfs41_name_cache_create( + OUT struct nfs41_name_cache **cache_out) +{ + struct nfs41_name_cache *cache; + int status = NO_ERROR; + + dprintf(NCLVL1, "nfs41_name_cache_create()\n"); + + /* allocate the cache */ + cache = calloc(1, sizeof(struct nfs41_name_cache)); + if (cache == NULL) { + status = GetLastError(); + goto out; + } + + list_init(&cache->exp_entries); + cache->expiration = NAME_CACHE_EXPIRATION; + cache->max_entries = NAME_CACHE_MAX_ENTRIES; + cache->max_delegations = NAME_CACHE_MAX_ENTRIES / 2; + InitializeSRWLock(&cache->lock); + + /* allocate a pool of entries */ + cache->pool = calloc(cache->max_entries, NAME_ENTRY_SIZE); + if (cache->pool == NULL) { + status = GetLastError(); + goto out_err_cache; + } + + /* initialize the attribute cache */ + status = attr_cache_init(&cache->attributes, cache->max_entries); + if (status) + goto out_err_pool; + + *cache_out = cache; +out: + return status; + +out_err_pool: + free(cache->pool); +out_err_cache: + free(cache); + goto out; +} + +int nfs41_name_cache_free( + IN struct nfs41_name_cache **cache_out) +{ + struct nfs41_name_cache *cache = *cache_out; + int status = NO_ERROR; + + dprintf(NCLVL1, "nfs41_name_cache_free()\n"); + + /* free the attribute cache */ + attr_cache_free(&cache->attributes); + + /* free the name entry pool */ + free(cache->pool); + free(cache); + *cache_out = NULL; + return status; +} + +static __inline void copy_fh( + OUT nfs41_fh *dst, + IN OPTIONAL const struct name_cache_entry *src) +{ + if (src) + fh_copy(dst, &src->fh); + else + dst->len = 0; +} + +int nfs41_name_cache_lookup( + IN struct nfs41_name_cache *cache, + IN const char *path, + IN const char *path_end, + OUT OPTIONAL const char **remaining_path_out, + OUT OPTIONAL nfs41_fh *parent_out, + OUT OPTIONAL nfs41_fh *target_out, + OUT OPTIONAL nfs41_file_info *info_out, + OUT OPTIONAL bool_t *is_negative) +{ + struct name_cache_entry *parent, *target; + const char *path_pos = path; + int status; + + AcquireSRWLockShared(&cache->lock); + + if (!name_cache_enabled(cache)) { + status = ERROR_NOT_SUPPORTED; + goto out_unlock; + } + + status = name_cache_lookup(cache, 1, path, path_end, + &path_pos, &parent, &target, is_negative); + + if (parent_out) copy_fh(parent_out, parent); + if (target_out) copy_fh(target_out, target); + if (info_out && target && target->attributes) + copy_attrs(info_out, target->attributes); + +out_unlock: + ReleaseSRWLockShared(&cache->lock); + if (remaining_path_out) *remaining_path_out = path_pos; + return status; +} + +int nfs41_attr_cache_lookup( + IN struct nfs41_name_cache *cache, + IN uint64_t fileid, + OUT nfs41_file_info *info_out) +{ + struct attr_cache_entry *entry; + int status = NO_ERROR; + + dprintf(NCLVL1, "--> nfs41_attr_cache_lookup(%llu)\n", fileid); + + AcquireSRWLockShared(&cache->lock); + + if (!name_cache_enabled(cache)) { + status = ERROR_NOT_SUPPORTED; + goto out_unlock; + } + + entry = attr_cache_search(&cache->attributes, fileid); + if (entry == NULL || attr_cache_entry_expired(entry)) { + status = ERROR_FILE_NOT_FOUND; + goto out_unlock; + } + + copy_attrs(info_out, entry); + +out_unlock: + ReleaseSRWLockShared(&cache->lock); + + dprintf(NCLVL1, "<-- nfs41_attr_cache_lookup() returning %d\n", status); + return status; +} + +int nfs41_attr_cache_update( + IN struct nfs41_name_cache *cache, + IN uint64_t fileid, + IN const nfs41_file_info *info) +{ + struct attr_cache_entry *entry; + int status = NO_ERROR; + + dprintf(NCLVL1, "--> nfs41_attr_cache_update(%llu)\n", fileid); + + AcquireSRWLockExclusive(&cache->lock); + + if (!name_cache_enabled(cache)) { + status = ERROR_NOT_SUPPORTED; + goto out_unlock; + } + + entry = attr_cache_search(&cache->attributes, fileid); + if (entry == NULL) { + status = ERROR_FILE_NOT_FOUND; + goto out_unlock; + } + + attr_cache_update(entry, info, OPEN_DELEGATE_NONE); + +out_unlock: + ReleaseSRWLockExclusive(&cache->lock); + + dprintf(NCLVL1, "<-- nfs41_attr_cache_update() returning %d\n", status); + return status; +} + +int nfs41_name_cache_insert( + IN struct nfs41_name_cache *cache, + IN const char *path, + IN const nfs41_component *name, + IN OPTIONAL const nfs41_fh *fh, + IN OPTIONAL const nfs41_file_info *info, + IN OPTIONAL const change_info4 *cinfo, + IN enum open_delegation_type4 delegation) +{ + struct name_cache_entry *parent, *target; + int status; + + dprintf(NCLVL1, "--> nfs41_name_cache_insert('%.*s')\n", + name->name + name->len - path, path); + + AcquireSRWLockExclusive(&cache->lock); + + if (!name_cache_enabled(cache)) { + status = ERROR_NOT_SUPPORTED; + goto out_unlock; + } + + /* limit the number of delegations to prevent attr cache starvation */ + if (is_delegation(delegation) && + cache->delegations >= cache->max_delegations) { + status = ERROR_TOO_MANY_OPEN_FILES; + goto out_unlock; + } + + /* an empty path or component implies the root entry */ + if (path == NULL || name == NULL || name->len == 0) { + /* create the root entry if it doesn't exist */ + if (cache->root == NULL) { + const nfs41_component name = { "ROOT", 4 }; + status = name_cache_entry_create(cache, &name, &cache->root); + if (status) + goto out_err_deleg; + } + target = cache->root; + } else { + /* find/create an entry under its parent */ + status = name_cache_lookup(cache, 0, path, + name->name, NULL, NULL, &parent, NULL); + if (status) + goto out_err_deleg; + + if (cinfo && name_cache_entry_changed(cache, parent, cinfo)) { + name_cache_entry_invalidate(cache, parent); + goto out_err_deleg; + } + + status = name_cache_find_or_create(cache, parent, name, &target); + if (status) + goto out_err_deleg; + } + + /* pass in the new fh/attributes */ + status = name_cache_entry_update(cache, target, fh, info, delegation); + if (status) + goto out_err_update; + +out_unlock: + ReleaseSRWLockExclusive(&cache->lock); + + dprintf(NCLVL1, "<-- nfs41_name_cache_insert() returning %d\n", + status); + return status; + +out_err_update: + /* a failure in name_cache_entry_update() leaves a negative entry + * where there shouldn't be one; remove it from the cache */ + name_cache_entry_invalidate(cache, target); + +out_err_deleg: + if (is_delegation(delegation)) { + /* we still need a reference to the attributes for the delegation */ + struct attr_cache_entry *attributes; + status = attr_cache_find_or_create(&cache->attributes, + info->fileid, &attributes); + if (status == NO_ERROR) { + attr_cache_update(attributes, info, delegation); + cache->delegations++; + } + else + status = ERROR_TOO_MANY_OPEN_FILES; + } + goto out_unlock; +} + +int nfs41_name_cache_delegreturn( + IN struct nfs41_name_cache *cache, + IN uint64_t fileid, + IN const char *path, + IN const nfs41_component *name) +{ + struct name_cache_entry *parent, *target; + struct attr_cache_entry *attributes; + int status; + + dprintf(NCLVL1, "--> nfs41_name_cache_delegreturn(%llu, '%s')\n", + fileid, path); + + AcquireSRWLockExclusive(&cache->lock); + + if (!name_cache_enabled(cache)) { + status = ERROR_NOT_SUPPORTED; + goto out_unlock; + } + + status = name_cache_lookup(cache, 0, path, + name->name + name->len, NULL, &parent, &target, NULL); + if (status == NO_ERROR) { + /* put the name cache entry back on the exp_entries list */ + list_add_head(&cache->exp_entries, &target->exp_entry); + name_cache_entry_updated(cache, target); + + attributes = target->attributes; + } else { + /* should still have an attr cache entry */ + attributes = attr_cache_search(&cache->attributes, fileid); + } + + if (attributes == NULL) { + status = ERROR_FILE_NOT_FOUND; + goto out_unlock; + } + + /* release the reference from name_cache_entry_update() */ + if (attributes->delegated) { + attributes->delegated = FALSE; + attr_cache_entry_deref(&cache->attributes, attributes); + assert(cache->delegations > 0); + cache->delegations--; + } + status = NO_ERROR; + +out_unlock: + ReleaseSRWLockExclusive(&cache->lock); + + dprintf(NCLVL1, "<-- nfs41_name_cache_delegreturn() returning %d\n", status); + return status; +} + +int nfs41_name_cache_remove( + IN struct nfs41_name_cache *cache, + IN const char *path, + IN const nfs41_component *name, + IN uint64_t fileid, + IN const change_info4 *cinfo) +{ + struct name_cache_entry *parent, *target; + struct attr_cache_entry *attributes = NULL; + int status; + + dprintf(NCLVL1, "--> nfs41_name_cache_remove('%s')\n", path); + + AcquireSRWLockExclusive(&cache->lock); + + if (!name_cache_enabled(cache)) { + status = ERROR_NOT_SUPPORTED; + goto out_unlock; + } + + status = name_cache_lookup(cache, 0, path, + name->name + name->len, NULL, &parent, &target, NULL); + if (status == ERROR_PATH_NOT_FOUND) + goto out_attributes; + + if (cinfo && name_cache_entry_changed(cache, parent, cinfo)) { + name_cache_entry_invalidate(cache, parent); + goto out_attributes; + } + + if (status == ERROR_FILE_NOT_FOUND) + goto out_attributes; + + if (target->attributes) + target->attributes->numlinks--; + + /* make this a negative entry and unlink children */ + name_cache_entry_update(cache, target, NULL, NULL, OPEN_DELEGATE_NONE); + name_cache_unlink_children_recursive(cache, target); + +out_unlock: + ReleaseSRWLockExclusive(&cache->lock); + + dprintf(NCLVL1, "<-- nfs41_name_cache_remove() returning %d\n", status); + return status; + +out_attributes: + /* in the presence of other links, we need to update numlinks + * regardless of a failure to find the target entry */ + dprintf(NCLVL1, "nfs41_name_cache_remove: need to find attributes for %s\n", path); + attributes = attr_cache_search(&cache->attributes, fileid); + if (attributes) + attributes->numlinks--; + goto out_unlock; +} + +int nfs41_name_cache_rename( + IN struct nfs41_name_cache *cache, + IN const char *src_path, + IN const nfs41_component *src_name, + IN const change_info4 *src_cinfo, + IN const char *dst_path, + IN const nfs41_component *dst_name, + IN const change_info4 *dst_cinfo) +{ + struct name_cache_entry *src_parent, *src; + struct name_cache_entry *dst_parent; + int status = NO_ERROR; + + dprintf(NCLVL1, "--> nfs41_name_cache_rename('%s' to '%s')\n", + src_path, dst_path); + + AcquireSRWLockExclusive(&cache->lock); + + if (!name_cache_enabled(cache)) { + status = ERROR_NOT_SUPPORTED; + goto out_unlock; + } + + /* look up dst_parent */ + status = name_cache_lookup(cache, 0, dst_path, + dst_name->name, NULL, NULL, &dst_parent, NULL); + /* we can't create the dst entry without a parent */ + if (status || dst_parent->attributes == NULL) { + /* if src exists, make it negative */ + dprintf(NCLVL1, "nfs41_name_cache_rename: adding negative cache " + "entry for %.*s\n", src_name->len, src_name->name); + status = name_cache_lookup(cache, 0, src_path, + src_name->name + src_name->len, NULL, NULL, &src, NULL); + if (status == NO_ERROR) { + name_cache_entry_update(cache, src, NULL, NULL, OPEN_DELEGATE_NONE); + name_cache_unlink_children_recursive(cache, src); + } + status = ERROR_PATH_NOT_FOUND; + goto out_unlock; + } + + /* look up src_parent and src */ + status = name_cache_lookup(cache, 0, src_path, + src_name->name + src_name->len, NULL, &src_parent, &src, NULL); + /* we can't create the dst entry without valid attributes */ + if (status || src->attributes == NULL) { + /* remove dst if it exists */ + struct name_cache_entry *dst; + dprintf(NCLVL1, "nfs41_name_cache_rename: removing negative cache " + "entry for %.*s\n", dst_name->len, dst_name->name); + dst = name_cache_search(cache, dst_parent, dst_name); + if (dst) name_cache_unlink(cache, dst); + goto out_unlock; + } + + if (name_cache_entry_changed(cache, dst_parent, dst_cinfo)) { + name_cache_entry_invalidate(cache, dst_parent); + /* if dst_parent and src_parent are both gone, + * we no longer have an entry to rename */ + if (dst_parent == src_parent) + goto out_unlock; + } else { + struct name_cache_entry *existing; + existing = name_cache_search(cache, dst_parent, dst_name); + if (existing) { + if (existing == src) + goto out_unlock; + /* remove the existing entry, but don't unlink it yet; + * we may reuse it for a negative entry */ + name_cache_remove(existing, dst_parent); + } + + /* move the src entry under dst_parent */ + name_cache_remove(src, src_parent); + name_cache_entry_rename(src, dst_name); + name_cache_insert(src, dst_parent); + + if (existing) { + /* recycle 'existing' as the negative entry 'src' */ + name_cache_entry_rename(existing, src_name); + name_cache_insert(existing, src_parent); + } + src = existing; + } + + if (name_cache_entry_changed(cache, src_parent, src_cinfo)) { + name_cache_entry_invalidate(cache, src_parent); + goto out_unlock; + } + + /* leave a negative entry where the file used to be */ + if (src == NULL) { + /* src was moved, create a new entry in its place */ + status = name_cache_find_or_create(cache, src_parent, src_name, &src); + if (status) + goto out_unlock; + } + name_cache_entry_update(cache, src, NULL, NULL, OPEN_DELEGATE_NONE); + name_cache_unlink_children_recursive(cache, src); + +out_unlock: + ReleaseSRWLockExclusive(&cache->lock); + + dprintf(NCLVL1, "<-- nfs41_name_cache_rename() returning %d\n", status); + return status; +} + +/* nfs41_name_cache_resolve_fh() */ + +#define MAX_PUTFH_PER_COMPOUND 16 + +static bool_t get_path_fhs( + IN struct nfs41_name_cache *cache, + IN nfs41_abs_path *path, + IN OUT const char **path_pos, + IN uint32_t max_components, + OUT nfs41_path_fh *files, + OUT uint32_t *count) +{ + struct name_cache_entry *target; + const char *path_end = path->path + path->len; + nfs41_component *name; + uint32_t i; + int status; + + *count = 0; + + AcquireSRWLockShared(&cache->lock); + + /* look up the parent of the first component */ + status = name_cache_lookup(cache, 1, path->path, + *path_pos, NULL, NULL, &target, NULL); + if (status) + goto out_unlock; + + for (i = 0; i < max_components; i++) { + files[i].path = path; + name = &files[i].name; + + if (!next_component(*path_pos, path_end, name)) + break; + *path_pos = name->name + name->len; + + target = name_cache_search(cache, target, name); + if (target == NULL || entry_invis(target, NULL)) { + if (is_last_component(name->name, path_end)) + status = ERROR_FILE_NOT_FOUND; + else + status = ERROR_PATH_NOT_FOUND; + goto out_unlock; + } + /* make copies for use outside of cache->lock */ + fh_copy(&files[i].fh, &target->fh); + (*count)++; + } + +out_unlock: + ReleaseSRWLockShared(&cache->lock); + return *count && status == 0; +} + +static int rpc_array_putfh( + IN nfs41_session *session, + IN nfs41_path_fh *files, + IN uint32_t count, + OUT uint32_t *valid_out) +{ + nfs41_compound compound; + nfs_argop4 argops[1+MAX_PUTFH_PER_COMPOUND]; + nfs_resop4 resops[1+MAX_PUTFH_PER_COMPOUND]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res = { 0 }; + nfs41_putfh_args putfh_args[MAX_PUTFH_PER_COMPOUND]; + nfs41_putfh_res putfh_res[MAX_PUTFH_PER_COMPOUND] = { 0 }; + uint32_t i; + int status; + + *valid_out = 0; + + compound_init(&compound, argops, resops, "array_putfh"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 0); + + for (i = 0; i < count; i++){ + compound_add_op(&compound, OP_PUTFH, &putfh_args[i], &putfh_res[i]); + putfh_args[i].file = &files[i]; + putfh_args[i].in_recovery = 1; + } + + status = compound_encode_send_decode(session, &compound, TRUE); + if (status) goto out; + + status = sequence_res.sr_status; + if (status) goto out; + + for (i = 0; i < count; i++) { + status = putfh_res[i].status; + if (status) break; + } + *valid_out = i; +out: + return status; +} + +static int delete_stale_component( + IN struct nfs41_name_cache *cache, + IN nfs41_session *session, + IN const nfs41_abs_path *path, + IN const nfs41_component *component) +{ + struct name_cache_entry *target; + int status; + + dprintf(NCLVL1, "--> delete_stale_component('%s')\n", + component->name); + + AcquireSRWLockExclusive(&cache->lock); + + status = name_cache_lookup(cache, 0, path->path, + component->name + component->len, NULL, NULL, &target, NULL); + if (status == NO_ERROR) + name_cache_unlink(cache, target); + + ReleaseSRWLockExclusive(&cache->lock); + + dprintf(NCLVL1, "<-- delete_stale_component() returning %d\n", status); + return status; +} + +static __inline uint32_t max_putfh_components( + IN const nfs41_session *session) +{ + const uint32_t comps = session->fore_chan_attrs.ca_maxoperations - 1; + return min(comps, MAX_PUTFH_PER_COMPOUND); +} + +int nfs41_name_cache_remove_stale( + IN struct nfs41_name_cache *cache, + IN nfs41_session *session, + IN nfs41_abs_path *path) +{ + nfs41_path_fh files[MAX_PUTFH_PER_COMPOUND]; + const char *path_pos = path->path; + const char* const path_end = path->path + path->len; + const uint32_t max_components = max_putfh_components(session); + uint32_t count, index; + int status = NO_ERROR; + + AcquireSRWLockShared(&cache->lock); + + /* if there's no cache, don't check any components */ + if (!name_cache_enabled(cache)) + path_pos = path_end; + + ReleaseSRWLockShared(&cache->lock); + + /* hold a lock on the path to protect against rename */ + AcquireSRWLockShared(&path->lock); + + while (get_path_fhs(cache, path, &path_pos, max_components, files, &count)) { + status = rpc_array_putfh(session, files, count, &index); + + if (status == NFS4ERR_STALE || status == NFS4ERR_FHEXPIRED) { + status = delete_stale_component(cache, + session, path, &files[index].name); + break; + } + if (status) { + status = nfs_to_windows_error(status, ERROR_FILE_NOT_FOUND); + break; + } + } + + ReleaseSRWLockShared(&path->lock); + + return status; +} diff --git a/reactos/base/services/nfsd/name_cache.h b/reactos/base/services/nfsd/name_cache.h new file mode 100644 index 00000000000..ffab1735867 --- /dev/null +++ b/reactos/base/services/nfsd/name_cache.h @@ -0,0 +1,106 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#ifndef __NFS41_DAEMON_NAME_CACHE_H__ +#define __NFS41_DAEMON_NAME_CACHE_H__ + +#include "nfs41.h" + + +static __inline struct nfs41_name_cache* client_name_cache( + IN nfs41_client *client) +{ + return client_server(client)->name_cache; +} + +static __inline struct nfs41_name_cache* session_name_cache( + IN nfs41_session *session) +{ + return client_name_cache(session->client); +} + + +/* attribute cache */ +int nfs41_attr_cache_lookup( + IN struct nfs41_name_cache *cache, + IN uint64_t fileid, + OUT nfs41_file_info *info_out); + +int nfs41_attr_cache_update( + IN struct nfs41_name_cache *cache, + IN uint64_t fileid, + IN const nfs41_file_info *info); + + +/* name cache */ +int nfs41_name_cache_create( + OUT struct nfs41_name_cache **cache_out); + +int nfs41_name_cache_free( + IN OUT struct nfs41_name_cache **cache_out); + +int nfs41_name_cache_lookup( + IN struct nfs41_name_cache *cache, + IN const char *path, + IN const char *path_end, + OUT OPTIONAL const char **remaining_path_out, + OUT OPTIONAL nfs41_fh *parent_out, + OUT OPTIONAL nfs41_fh *target_out, + OUT OPTIONAL nfs41_file_info *info_out, + OUT OPTIONAL bool_t *is_negative); + +int nfs41_name_cache_insert( + IN struct nfs41_name_cache *cache, + IN const char *path, + IN const nfs41_component *name, + IN OPTIONAL const nfs41_fh *fh, + IN OPTIONAL const nfs41_file_info *info, + IN OPTIONAL const change_info4 *cinfo, + IN enum open_delegation_type4 delegation); + +int nfs41_name_cache_delegreturn( + IN struct nfs41_name_cache *cache, + IN uint64_t fileid, + IN const char *path, + IN const nfs41_component *name); + +int nfs41_name_cache_remove( + IN struct nfs41_name_cache *cache, + IN const char *path, + IN const nfs41_component *name, + IN uint64_t fileid, + IN const change_info4 *cinfo); + +int nfs41_name_cache_rename( + IN struct nfs41_name_cache *cache, + IN const char *src_path, + IN const nfs41_component *src_name, + IN const change_info4 *src_cinfo, + IN const char *dst_path, + IN const nfs41_component *dst_name, + IN const change_info4 *dst_cinfo); + +int nfs41_name_cache_remove_stale( + IN struct nfs41_name_cache *cache, + IN nfs41_session *session, + IN nfs41_abs_path *path); + +#endif /* !__NFS41_DAEMON_NAME_CACHE_H__ */ diff --git a/reactos/base/services/nfsd/namespace.c b/reactos/base/services/nfsd/namespace.c new file mode 100644 index 00000000000..c593b414d73 --- /dev/null +++ b/reactos/base/services/nfsd/namespace.c @@ -0,0 +1,478 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include +#include + +#include "nfs41_ops.h" +#include "util.h" +#include "daemon_debug.h" + + +#define NSLVL 2 /* dprintf level for namespace logging */ + + +#define client_entry(pos) list_container(pos, nfs41_client, root_entry) + + +/* nfs41_root */ +int nfs41_root_create( + IN const char *name, + IN uint32_t sec_flavor, + IN uint32_t wsize, + IN uint32_t rsize, + OUT nfs41_root **root_out) +{ + int status = NO_ERROR; + nfs41_root *root; + + dprintf(NSLVL, "--> nfs41_root_create()\n"); + + root = calloc(1, sizeof(nfs41_root)); + if (root == NULL) { + status = GetLastError(); + goto out; + } + + list_init(&root->clients); + root->wsize = wsize; + root->rsize = rsize; + InitializeCriticalSection(&root->lock); + root->ref_count = 1; + root->sec_flavor = sec_flavor; + + /* generate a unique client_owner */ + status = nfs41_client_owner(name, sec_flavor, &root->client_owner); + if (status) { + eprintf("nfs41_client_owner() failed with %d\n", status); + free(root); + goto out; + } + + *root_out = root; +out: + dprintf(NSLVL, "<-- nfs41_root_create() returning %d\n", status); + return status; +} + +static void root_free( + IN nfs41_root *root) +{ + struct list_entry *entry, *tmp; + + dprintf(NSLVL, "--> nfs41_root_free()\n"); + + /* free clients */ + list_for_each_tmp(entry, tmp, &root->clients) + nfs41_client_free(client_entry(entry)); + DeleteCriticalSection(&root->lock); + free(root); + + dprintf(NSLVL, "<-- nfs41_root_free()\n"); +} + +void nfs41_root_ref( + IN nfs41_root *root) +{ + const LONG count = InterlockedIncrement(&root->ref_count); + + dprintf(NSLVL, "nfs41_root_ref() count %d\n", count); +} + +void nfs41_root_deref( + IN nfs41_root *root) +{ + const LONG count = InterlockedDecrement(&root->ref_count); + + dprintf(NSLVL, "nfs41_root_deref() count %d\n", count); + if (count == 0) + root_free(root); +} + + +/* root_client_find_addrs() */ +struct cl_addr_info { + const multi_addr4 *addrs; + uint32_t roles; +}; + +static int cl_addr_compare( + IN const struct list_entry *entry, + IN const void *value) +{ + nfs41_client *client = client_entry(entry); + const struct cl_addr_info *info = (const struct cl_addr_info*)value; + uint32_t i, roles; + + /* match any of the desired roles */ + AcquireSRWLockShared(&client->exid_lock); + roles = info->roles & client->roles; + ReleaseSRWLockShared(&client->exid_lock); + + if (roles == 0) + return ERROR_FILE_NOT_FOUND; + + /* match any address in 'addrs' with any address in client->rpc->addrs */ + for (i = 0; i < info->addrs->count; i++) + if (multi_addr_find(&client->rpc->addrs, &info->addrs->arr[i], NULL)) + return NO_ERROR; + + return ERROR_FILE_NOT_FOUND; +} + +static int root_client_find_addrs( + IN nfs41_root *root, + IN const multi_addr4 *addrs, + IN bool_t is_data, + OUT nfs41_client **client_out) +{ + struct cl_addr_info info; + struct list_entry *entry; + int status; + + dprintf(NSLVL, "--> root_client_find_addrs()\n"); + + info.addrs = addrs; + info.roles = nfs41_exchange_id_flags(is_data) & EXCHGID4_FLAG_MASK_PNFS; + + entry = list_search(&root->clients, &info, cl_addr_compare); + if (entry) { + *client_out = client_entry(entry); + status = NO_ERROR; + dprintf(NSLVL, "<-- root_client_find_addrs() returning 0x%p\n", + *client_out); + } else { + status = ERROR_FILE_NOT_FOUND; + dprintf(NSLVL, "<-- root_client_find_addrs() failed with %d\n", + status); + } + return status; +} + +/* root_client_find() */ +struct cl_exid_info { + const nfs41_exchange_id_res *exchangeid; + uint32_t roles; +}; + +static int cl_exid_compare( + IN const struct list_entry *entry, + IN const void *value) +{ + nfs41_client *client = client_entry(entry); + const struct cl_exid_info *info = (const struct cl_exid_info*)value; + int status = ERROR_FILE_NOT_FOUND; + + AcquireSRWLockShared(&client->exid_lock); + + /* match any of the desired roles */ + if ((info->roles & client->roles) == 0) + goto out; + /* match server_owner.major_id */ + if (strncmp(info->exchangeid->server_owner.so_major_id, + client->server->owner, NFS4_OPAQUE_LIMIT) != 0) + goto out; + /* match server_scope */ + if (strncmp(info->exchangeid->server_scope, + client->server->scope, NFS4_OPAQUE_LIMIT) != 0) + goto out; + /* match clientid */ + if (info->exchangeid->clientid != client->clnt_id) + goto out; + + status = NO_ERROR; +out: + ReleaseSRWLockShared(&client->exid_lock); + return status; +} + +static int root_client_find( + IN nfs41_root *root, + IN const nfs41_exchange_id_res *exchangeid, + IN bool_t is_data, + OUT nfs41_client **client_out) +{ + struct cl_exid_info info; + struct list_entry *entry; + int status; + + dprintf(NSLVL, "--> root_client_find()\n"); + + info.exchangeid = exchangeid; + info.roles = nfs41_exchange_id_flags(is_data) & EXCHGID4_FLAG_MASK_PNFS; + + entry = list_search(&root->clients, &info, cl_exid_compare); + if (entry) { + *client_out = client_entry(entry); + status = NO_ERROR; + dprintf(NSLVL, "<-- root_client_find() returning 0x%p\n", + *client_out); + } else { + status = ERROR_FILE_NOT_FOUND; + dprintf(NSLVL, "<-- root_client_find() failed with %d\n", + status); + } + return status; +} + +static int session_get_lease( + IN nfs41_session *session, + IN OPTIONAL uint32_t lease_time) +{ + bool_t use_mds_lease; + int status; + + /* http://tools.ietf.org/html/rfc5661#section-13.1.1 + * 13.1.1. Sessions Considerations for Data Servers: + * If the reply to EXCHANGE_ID has just the EXCHGID4_FLAG_USE_PNFS_DS role + * set, then (as noted in Section 13.6) the client will not be able to + * determine the data server's lease_time attribute because GETATTR will + * not be permitted. Instead, the rule is that any time a client + * receives a layout referring it to a data server that returns just the + * EXCHGID4_FLAG_USE_PNFS_DS role, the client MAY assume that the + * lease_time attribute from the metadata server that returned the + * layout applies to the data server. */ + AcquireSRWLockShared(&session->client->exid_lock); + use_mds_lease = session->client->roles == EXCHGID4_FLAG_USE_PNFS_DS; + ReleaseSRWLockShared(&session->client->exid_lock); + + if (!use_mds_lease) { + /* the client is allowed to GETATTR, so query the lease_time */ + nfs41_file_info info = { 0 }; + bitmap4 attr_request = { 1, { FATTR4_WORD0_LEASE_TIME, 0, 0 } }; + + status = nfs41_getattr(session, NULL, &attr_request, &info); + if (status) { + eprintf("nfs41_getattr() failed with %s\n", + nfs_error_string(status)); + status = nfs_to_windows_error(status, ERROR_BAD_NET_RESP); + goto out; + } + lease_time = info.lease_time; + } + + status = nfs41_session_set_lease(session, lease_time); + if (status) { + eprintf("nfs41_session_set_lease() failed %d\n", status); + goto out; + } +out: + return status; +} + +static int root_client_create( + IN nfs41_root *root, + IN nfs41_rpc_clnt *rpc, + IN bool_t is_data, + IN OPTIONAL uint32_t lease_time, + IN const nfs41_exchange_id_res *exchangeid, + OUT nfs41_client **client_out) +{ + nfs41_client *client; + nfs41_session *session; + int status; + + /* create client (transfers ownership of rpc to client) */ + status = nfs41_client_create(rpc, &root->client_owner, + is_data, exchangeid, &client); + if (status) { + eprintf("nfs41_client_create() failed with %d\n", status); + goto out; + } + client->root = root; + rpc->client = client; + + /* create session (and client takes ownership) */ + status = nfs41_session_create(client, &session); + if (status) { + eprintf("nfs41_session_create failed %d\n", status); + goto out_err; + } + + if (!is_data) { + /* send RECLAIM_COMPLETE, but don't fail on ERR_NOTSUPP */ + status = nfs41_reclaim_complete(session); + if (status && status != NFS4ERR_NOTSUPP) { + eprintf("nfs41_reclaim_complete() failed with %s\n", + nfs_error_string(status)); + status = ERROR_BAD_NETPATH; + goto out_err; + } + } + + /* get least time and start session renewal thread */ + status = session_get_lease(session, lease_time); + if (status) + goto out_err; + + *client_out = client; +out: + return status; + +out_err: + nfs41_client_free(client); + goto out; +} + +int nfs41_root_mount_addrs( + IN nfs41_root *root, + IN const multi_addr4 *addrs, + IN bool_t is_data, + IN OPTIONAL uint32_t lease_time, + OUT nfs41_client **client_out) +{ + nfs41_exchange_id_res exchangeid = { 0 }; + nfs41_rpc_clnt *rpc; + nfs41_client *client, *existing; + int status; + + dprintf(NSLVL, "--> nfs41_root_mount_addrs()\n"); + + /* look for an existing client that matches the address and role */ + EnterCriticalSection(&root->lock); + status = root_client_find_addrs(root, addrs, is_data, &client); + LeaveCriticalSection(&root->lock); + + if (status == NO_ERROR) + goto out; + + /* create an rpc client */ + status = nfs41_rpc_clnt_create(addrs, root->wsize, root->rsize, + root->uid, root->gid, root->sec_flavor, &rpc); + if (status) { + eprintf("nfs41_rpc_clnt_create() failed %d\n", status); + goto out; + } + + /* get a clientid with exchangeid */ + status = nfs41_exchange_id(rpc, &root->client_owner, + nfs41_exchange_id_flags(is_data), &exchangeid); + if (status) { + eprintf("nfs41_exchange_id() failed %s\n", nfs_error_string(status)); + status = ERROR_BAD_NET_RESP; + goto out_free_rpc; + } + + /* attempt to match existing clients by the exchangeid response */ + EnterCriticalSection(&root->lock); + status = root_client_find(root, &exchangeid, is_data, &client); + LeaveCriticalSection(&root->lock); + + if (status == NO_ERROR) + goto out_free_rpc; + + /* create a client for this clientid */ + status = root_client_create(root, rpc, is_data, + lease_time, &exchangeid, &client); + if (status) { + eprintf("nfs41_client_create() failed %d\n", status); + /* root_client_create takes care of cleaning up + * thus don't go to out_free_rpc */ + goto out; + } + + /* because we don't hold the root's lock over session creation, + * we could end up creating multiple clients with the same + * server and roles */ + EnterCriticalSection(&root->lock); + status = root_client_find(root, &exchangeid, is_data, &existing); + + if (status) { + dprintf(NSLVL, "caching new client 0x%p\n", client); + + /* the client is not a duplicate, so add it to the list */ + list_add_tail(&root->clients, &client->root_entry); + status = NO_ERROR; + } else { + dprintf(NSLVL, "created a duplicate client 0x%p! using " + "existing client 0x%p instead\n", client, existing); + + /* a matching client has been created in parallel, so free + * the one we created and use the existing client instead */ + nfs41_client_free(client); + client = existing; + } + LeaveCriticalSection(&root->lock); + +out: + if (status == NO_ERROR) + *client_out = client; + dprintf(NSLVL, "<-- nfs41_root_mount_addrs() returning %d\n", status); + return status; + +out_free_rpc: + nfs41_rpc_clnt_free(rpc); + goto out; +} + + +/* http://tools.ietf.org/html/rfc5661#section-11.9 + * 11.9. The Attribute fs_locations + * An entry in the server array is a UTF-8 string and represents one of a + * traditional DNS host name, IPv4 address, IPv6 address, or a zero-length + * string. An IPv4 or IPv6 address is represented as a universal address + * (see Section 3.3.9 and [15]), minus the netid, and either with or without + * the trailing ".p1.p2" suffix that represents the port number. If the + * suffix is omitted, then the default port, 2049, SHOULD be assumed. A + * zero-length string SHOULD be used to indicate the current address being + * used for the RPC call. */ +static int referral_mount_location( + IN nfs41_root *root, + IN const fs_location4 *loc, + OUT nfs41_client **client_out) +{ + multi_addr4 addrs; + int status = ERROR_BAD_NET_NAME; + uint32_t i; + + /* create a client and session for the first available server */ + for (i = 0; i < loc->server_count; i++) { + /* XXX: only deals with 'address' as a hostname with default port */ + status = nfs41_server_resolve(loc->servers[i].address, 2049, &addrs); + if (status) continue; + + status = nfs41_root_mount_addrs(root, &addrs, 0, 0, client_out); + if (status == NO_ERROR) + break; + } + return status; +} + +int nfs41_root_mount_referral( + IN nfs41_root *root, + IN const fs_locations4 *locations, + OUT const fs_location4 **loc_out, + OUT nfs41_client **client_out) +{ + int status = ERROR_BAD_NET_NAME; + uint32_t i; + + /* establish a mount to the first available location */ + for (i = 0; i < locations->location_count; i++) { + status = referral_mount_location(root, + &locations->locations[i], client_out); + if (status == NO_ERROR) { + *loc_out = &locations->locations[i]; + break; + } + } + return status; +} diff --git a/reactos/base/services/nfsd/netconfig b/reactos/base/services/nfsd/netconfig new file mode 100644 index 00000000000..162ae83beec --- /dev/null +++ b/reactos/base/services/nfsd/netconfig @@ -0,0 +1,19 @@ +# +# The network configuration file. This file is currently only used in +# conjunction with the TI-RPC code in the libtirpc library. +# +# Entries consist of: +# +# \ +# +# +# The and fields are always empty in this +# implementation. +# +udp tpi_clts v inet udp - - +tcp tpi_cots_ord v inet tcp - - +udp6 tpi_clts v inet6 udp - - +tcp6 tpi_cots_ord v inet6 tcp - - +rawip tpi_raw - inet - - - +local tpi_cots_ord - loopback - - - +unix tpi_cots_ord - loopback - - - diff --git a/reactos/base/services/nfsd/nfs41.h b/reactos/base/services/nfsd/nfs41.h new file mode 100644 index 00000000000..438bb75427a --- /dev/null +++ b/reactos/base/services/nfsd/nfs41.h @@ -0,0 +1,532 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#ifndef __NFS41__ +#define __NFS41__ + +#include "util.h" +#include "list.h" + + +struct __nfs41_session; +struct __nfs41_client; +struct __rpc_client; +struct __nfs41_root; + +struct _FILE_GET_EA_INFORMATION; +struct _FILE_FULL_EA_INFORMATION; + +typedef struct __nfs41_superblock { + nfs41_fsid fsid; + struct list_entry entry; /* position in nfs41_server.superblocks */ + + bitmap4 supported_attrs; + bitmap4 suppattr_exclcreat; + bitmap4 default_getattr; + + nfstime4 time_delta; + uint64_t maxread; + uint64_t maxwrite; + + /* constant filesystem attributes */ + unsigned int layout_types : 3; + unsigned int aclsupport : 3; + unsigned int cansettime : 1; + unsigned int link_support : 1; + unsigned int symlink_support : 1; + unsigned int ea_support : 1; + unsigned int case_preserving : 1; + unsigned int case_insensitive : 1; + + /* variable filesystem attributes */ + uint64_t space_avail; + uint64_t space_free; + uint64_t space_total; + time_t cache_expiration; /* applies to space_ attributes */ + + SRWLOCK lock; +} nfs41_superblock; + +typedef struct __nfs41_superblock_list { + struct list_entry head; + SRWLOCK lock; +} nfs41_superblock_list; + +struct server_addrs { + multi_addr4 addrs; /* list of addrs we've used with this server */ + uint32_t next_index; + SRWLOCK lock; +}; + +typedef struct __nfs41_server { + char scope[NFS4_OPAQUE_LIMIT]; /* server_scope from exchangeid */ + char owner[NFS4_OPAQUE_LIMIT]; /* server_owner.major_id from exchangeid */ + struct server_addrs addrs; + nfs41_superblock_list superblocks; + struct nfs41_name_cache *name_cache; + struct list_entry entry; /* position in global server list */ + LONG ref_count; +} nfs41_server; + +enum delegation_status { + DELEGATION_GRANTED, + DELEGATION_RETURNING, + DELEGATION_RETURNED, +}; + +typedef struct __nfs41_delegation_state { + open_delegation4 state; + nfs41_abs_path path; + nfs41_path_fh parent; + nfs41_path_fh file; + struct list_entry client_entry; /* entry in nfs41_client.delegations */ + LONG ref_count; + + enum delegation_status status; + SRWLOCK lock; + CONDITION_VARIABLE cond; + + bool_t revoked; /* for recovery, accessed under client.state.lock */ + + HANDLE srv_open; /* for rdbss cache invalidation */ +} nfs41_delegation_state; + +typedef struct __nfs41_lock_state { + struct list_entry open_entry; /* entry in nfs41_open_state.locks */ + uint64_t offset; + uint64_t length; + uint32_t exclusive : 1; + uint32_t delegated : 1; /* whether or not there is state on the server */ + uint32_t id : 30; +} nfs41_lock_state; + +/* nfs41_open_state reference counting: + * one reference is held implicitly by the driver (initialized to 1 on + * OPEN and released on CLOSE). other references must be held during + * upcalls to prevent a parallel CLOSE from freeing it prematurely. by + * calling upcall_open_state_ref() when parsing the upcall, you are + * guaranteed a matching dereference on upcall_cleanup() */ +typedef struct __nfs41_open_state { + nfs41_abs_path path; + nfs41_path_fh parent; + nfs41_path_fh file; + nfs41_readdir_cookie cookie; + struct __nfs41_session *session; + uint32_t type; + bool_t do_close; + stateid4 stateid; + state_owner4 owner; + struct __pnfs_layout_state *layout; + struct list_entry client_entry; /* entry in nfs41_client.opens */ + SRWLOCK lock; + LONG ref_count; + uint32_t share_access; + uint32_t share_deny; + uint64_t pnfs_last_offset; /* for layoutcommit */ + + struct { + nfs41_delegation_state *state; + bool_t reclaim; + CONDITION_VARIABLE cond; + } delegation; + + struct { /* list of open lock state for recovery */ + stateid4 stateid; + struct list_entry list; + uint32_t counter; + CRITICAL_SECTION lock; + } locks; + + struct { + struct _FILE_GET_EA_INFORMATION *list; + uint32_t index; + CRITICAL_SECTION lock; + } ea; + + HANDLE srv_open; /* for data cache invalidation */ +} nfs41_open_state; + +typedef struct __nfs41_rpc_clnt { + struct __rpc_client *rpc; + SRWLOCK lock; + HANDLE cond; + struct __nfs41_client *client; + multi_addr4 addrs; + uint32_t addr_index; /* index of addr we're using */ + uint32_t wsize; + uint32_t rsize; + uint32_t version; + uint32_t sec_flavor; + uint32_t uid; + uint32_t gid; + char server_name[NI_MAXHOST]; + bool_t is_valid_session; + bool_t in_recovery; + bool_t needcb; +} nfs41_rpc_clnt; + +struct client_state { + struct list_entry opens; /* list of associated nfs41_open_state */ + struct list_entry delegations; /* list of associated delegations */ + CRITICAL_SECTION lock; +}; + +typedef struct __nfs41_client { + nfs41_server *server; + client_owner4 owner; + uint64_t clnt_id; + uint32_t seq_id; + uint32_t roles; + SRWLOCK exid_lock; + struct __nfs41_session *session; + SRWLOCK session_lock; + nfs41_rpc_clnt *rpc; + bool_t is_data; + struct pnfs_layout_list *layouts; + struct pnfs_file_device_list *devices; + struct list_entry root_entry; /* position in nfs41_root.clients */ + struct __nfs41_root *root; + + struct { + CONDITION_VARIABLE cond; + CRITICAL_SECTION lock; + bool_t in_recovery; + } recovery; + + /* for state recovery on server reboot */ + struct client_state state; +} nfs41_client; + +#define NFS41_MAX_NUM_SLOTS NFS41_MAX_RPC_REQS +typedef struct __nfs41_slot_table { + uint32_t seq_nums[NFS41_MAX_NUM_SLOTS]; + uint32_t used_slots[NFS41_MAX_NUM_SLOTS]; + uint32_t max_slots; + uint32_t highest_used; + uint32_t num_used; + ULONGLONG target_delay; + CRITICAL_SECTION lock; + CONDITION_VARIABLE cond; +} nfs41_slot_table; + +typedef struct __nfs41_channel_attrs { + uint32_t ca_headerpadsize; + uint32_t ca_maxrequestsize; + uint32_t ca_maxresponsesize; + uint32_t ca_maxresponsesize_cached; + uint32_t ca_maxoperations; + uint32_t ca_maxrequests; + uint32_t *ca_rdma_ird; +} nfs41_channel_attrs; + +struct replay_cache { + unsigned char buffer[NFS41_MAX_SERVER_CACHE]; + uint32_t length; +}; + +typedef struct __nfs41_cb_session { + struct { + struct replay_cache arg; + struct replay_cache res; + } replay; + const unsigned char *cb_sessionid; /* -> nfs41_session.session_id */ + uint32_t cb_seqnum; + uint32_t cb_slotid; +} nfs41_cb_session; + +typedef struct __nfs41_session { + nfs41_client *client; + unsigned char session_id[NFS4_SESSIONID_SIZE]; + nfs41_channel_attrs fore_chan_attrs; + nfs41_channel_attrs back_chan_attrs; + uint32_t lease_time; + nfs41_slot_table table; + // array of slots + HANDLE renew_thread; + bool_t isValidState; + uint32_t flags; + nfs41_cb_session cb_session; +} nfs41_session; + +/* nfs41_root reference counting: + * similar to nfs41_open_state, the driver holds an implicit reference + * between MOUNT and UNMOUNT. all other upcalls use upcall_root_ref() on + * upcall_parse(), which prevents the root/clients from being freed and + * guarantees a matching deref on upcall_cleanup() */ +typedef struct __nfs41_root { + client_owner4 client_owner; + CRITICAL_SECTION lock; + struct list_entry clients; + uint32_t wsize; + uint32_t rsize; + LONG ref_count; + uint32_t uid; + uint32_t gid; + DWORD sec_flavor; +} nfs41_root; + + +/* nfs41_namespace.c */ +int nfs41_root_create( + IN const char *name, + IN uint32_t sec_flavor, + IN uint32_t wsize, + IN uint32_t rsize, + OUT nfs41_root **root_out); + +void nfs41_root_ref( + IN nfs41_root *root); + +void nfs41_root_deref( + IN nfs41_root *root); + +int nfs41_root_mount_addrs( + IN nfs41_root *root, + IN const multi_addr4 *addrs, + IN bool_t is_data, + IN OPTIONAL uint32_t lease_time, + OUT nfs41_client **client_out); + +int nfs41_root_mount_server( + IN nfs41_root *root, + IN nfs41_server *server, + IN bool_t is_data, + IN OPTIONAL uint32_t lease_time, + OUT nfs41_client **client_out); + +int nfs41_root_mount_referral( + IN nfs41_root *root, + IN const fs_locations4 *locations, + OUT const fs_location4 **loc_out, + OUT nfs41_client **client_out); + +static __inline nfs41_session* nfs41_root_session( + IN nfs41_root *root) +{ + nfs41_client *client; + /* return a session for the server at the root of the namespace. + * because we created it on mount, it's the first one in the list */ + EnterCriticalSection(&root->lock); + client = list_container(root->clients.next, nfs41_client, root_entry); + LeaveCriticalSection(&root->lock); + return client->session; +} + + +/* nfs41_session.c */ +int nfs41_session_create( + IN nfs41_client *client, + IN nfs41_session **session_out); + +int nfs41_session_renew( + IN nfs41_session *session); + +int nfs41_session_set_lease( + IN nfs41_session *session, + IN uint32_t lease_time); + +void nfs41_session_free( + IN nfs41_session *session); + +void nfs41_session_bump_seq( + IN nfs41_session *session, + IN uint32_t slotid, + IN uint32_t target_highest_slotid); + +void nfs41_session_free_slot( + IN nfs41_session *session, + IN uint32_t slotid); + +void nfs41_session_get_slot( + IN nfs41_session *session, + OUT uint32_t *slot, + OUT uint32_t *seq, + OUT uint32_t *highest); + +int nfs41_session_recall_slot( + IN nfs41_session *session, + IN OUT uint32_t target_highest_slotid); + +struct __nfs41_sequence_args; +void nfs41_session_sequence( + struct __nfs41_sequence_args *args, + nfs41_session *session, + bool_t cachethis); + +int nfs41_session_bad_slot( + IN nfs41_session *session, + IN OUT struct __nfs41_sequence_args *args); + + +/* nfs41_server.c */ +void nfs41_server_list_init(); + +int nfs41_server_resolve( + IN const char *hostname, + IN unsigned short port, + OUT multi_addr4 *addrs); + +int nfs41_server_find_or_create( + IN const char *server_owner_major_id, + IN const char *server_scope, + IN const netaddr4 *addr, + OUT nfs41_server **server_out); + +void nfs41_server_ref( + IN nfs41_server *server); + +void nfs41_server_deref( + IN nfs41_server *server); + +void nfs41_server_addrs( + IN nfs41_server *server, + OUT multi_addr4 *addrs); + + +/* nfs41_client.c */ +int nfs41_client_owner( + IN const char *name, + IN uint32_t sec_flavor, + OUT client_owner4 *owner); + +uint32_t nfs41_exchange_id_flags( + IN bool_t is_data); + +struct __nfs41_exchange_id_res; + +int nfs41_client_create( + IN nfs41_rpc_clnt *rpc, + IN const client_owner4 *owner, + IN bool_t is_data, + IN const struct __nfs41_exchange_id_res *exchangeid, + OUT nfs41_client **client_out); + +int nfs41_client_renew( + IN nfs41_client *client); + +void nfs41_client_free( + IN nfs41_client *client); + +static __inline nfs41_server* client_server( + IN nfs41_client *client) +{ + /* the client's server could change during nfs41_client_renew(), + * so access to client->server must be protected */ + nfs41_server *server; + AcquireSRWLockShared(&client->exid_lock); + server = client->server; + ReleaseSRWLockShared(&client->exid_lock); + return server; +} + + +/* nfs41_superblock.c */ +int nfs41_superblock_for_fh( + IN nfs41_session *session, + IN const nfs41_fsid *fsid, + IN const nfs41_fh *parent OPTIONAL, + OUT nfs41_path_fh *file); + +static __inline void nfs41_superblock_getattr_mask( + IN const nfs41_superblock *superblock, + OUT bitmap4 *attrs) +{ + memcpy(attrs, &superblock->default_getattr, sizeof(bitmap4)); +} +static __inline void nfs41_superblock_supported_attrs( + IN const nfs41_superblock *superblock, + IN OUT bitmap4 *attrs) +{ + bitmap_intersect(attrs, &superblock->supported_attrs); +} +static __inline void nfs41_superblock_supported_attrs_exclcreat( + IN const nfs41_superblock *superblock, + IN OUT bitmap4 *attrs) +{ + bitmap_intersect(attrs, &superblock->suppattr_exclcreat); +} + +struct _FILE_FS_ATTRIBUTE_INFORMATION; +void nfs41_superblock_fs_attributes( + IN const nfs41_superblock *superblock, + OUT struct _FILE_FS_ATTRIBUTE_INFORMATION *FsAttrs); + +void nfs41_superblock_space_changed( + IN nfs41_superblock *superblock); + +void nfs41_superblock_list_init( + IN nfs41_superblock_list *superblocks); + +void nfs41_superblock_list_free( + IN nfs41_superblock_list *superblocks); + + +/* nfs41_rpc.c */ +int nfs41_rpc_clnt_create( + IN const multi_addr4 *addrs, + IN uint32_t wsize, + IN uint32_t rsize, + IN uint32_t uid, + IN uint32_t gid, + IN uint32_t sec_flavor, + OUT nfs41_rpc_clnt **rpc_out); + +void nfs41_rpc_clnt_free( + IN nfs41_rpc_clnt *rpc); + +int nfs41_send_compound( + IN nfs41_rpc_clnt *rpc, + IN char *inbuf, + OUT char *outbuf); + +static __inline netaddr4* nfs41_rpc_netaddr( + IN nfs41_rpc_clnt *rpc) +{ + uint32_t id; + AcquireSRWLockShared(&rpc->lock); + /* only addr_index needs to be protected, as rpc->addrs is write-once */ + id = rpc->addr_index; + ReleaseSRWLockShared(&rpc->lock); + + /* return the netaddr used to create the rpc client */ + return &rpc->addrs.arr[id]; +} + + +/* open.c */ +void nfs41_open_state_ref( + IN nfs41_open_state *state); + +void nfs41_open_state_deref( + IN nfs41_open_state *state); + +struct __stateid_arg; +void nfs41_open_stateid_arg( + IN nfs41_open_state *state, + OUT struct __stateid_arg *arg); + + +/* ea.c */ +int nfs41_ea_set( + IN nfs41_open_state *state, + IN struct _FILE_FULL_EA_INFORMATION *ea); + +#endif /* __NFS41__ */ diff --git a/reactos/base/services/nfsd/nfs41_callback.h b/reactos/base/services/nfsd/nfs41_callback.h new file mode 100644 index 00000000000..18c8cb64f01 --- /dev/null +++ b/reactos/base/services/nfsd/nfs41_callback.h @@ -0,0 +1,295 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#ifndef __NFS41_CALLBACK_H__ +#define __NFS41_CALLBACK_H__ + +#include "wintirpc.h" +#include "rpc/rpc.h" +#include "nfs41_types.h" + + +enum nfs41_callback_proc { + CB_NULL = 0, + CB_COMPOUND = 1, +}; + +enum nfs41_callback_op { + OP_CB_GETATTR = 3, + OP_CB_RECALL = 4, + OP_CB_LAYOUTRECALL = 5, + OP_CB_NOTIFY = 6, + OP_CB_PUSH_DELEG = 7, + OP_CB_RECALL_ANY = 8, + OP_CB_RECALLABLE_OBJ_AVAIL = 9, + OP_CB_RECALL_SLOT = 10, + OP_CB_SEQUENCE = 11, + OP_CB_WANTS_CANCELLED = 12, + OP_CB_NOTIFY_LOCK = 13, + OP_CB_NOTIFY_DEVICEID = 14, + OP_CB_ILLEGAL = 10044 +}; + +int nfs41_handle_callback(void *, void *, void *); + +/* OP_CB_LAYOUTRECALL */ +struct cb_recall_file { + nfs41_fh fh; + uint64_t offset; + uint64_t length; + stateid4 stateid; +}; +union cb_recall_file_args { + struct cb_recall_file file; + nfs41_fsid fsid; +}; +struct cb_recall { +#ifdef __REACTOS__ + uint32_t type; +#else + enum pnfs_return_type type; +#endif + union cb_recall_file_args args; +}; +struct cb_layoutrecall_args { +#ifdef __REACTOS__ + uint32_t type; + uint32_t iomode; +#else + enum pnfs_return_type type; + enum pnfs_iomode iomode; +#endif + bool_t changed; + struct cb_recall recall; +}; + +struct cb_layoutrecall_res { + enum_t status; +}; + +/* OP_CB_RECALL_SLOT */ +struct cb_recall_slot_args { + uint32_t target_highest_slotid; +}; + +struct cb_recall_slot_res { + enum_t status; +}; + +/* OP_CB_SEQUENCE */ +struct cb_sequence_ref { + uint32_t sequenceid; + uint32_t slotid; +}; +struct cb_sequence_ref_list { + char sessionid[NFS4_SESSIONID_SIZE]; + struct cb_sequence_ref *calls; + uint32_t call_count; +}; +struct cb_sequence_args { + char sessionid[NFS4_SESSIONID_SIZE]; + uint32_t sequenceid; + uint32_t slotid; + uint32_t highest_slotid; + bool_t cachethis; + struct cb_sequence_ref_list *ref_lists; + uint32_t ref_list_count; +}; + +struct cb_sequence_res_ok { + char sessionid[NFS4_SESSIONID_SIZE]; + uint32_t sequenceid; + uint32_t slotid; + uint32_t highest_slotid; + uint32_t target_highest_slotid; +}; +struct cb_sequence_res { + enum_t status; + struct cb_sequence_res_ok ok; +}; + +/* OP_CB_GETATTR */ +struct cb_getattr_args { + nfs41_fh fh; + bitmap4 attr_request; +}; + +struct cb_getattr_res { + enum_t status; + nfs41_file_info info; +}; + +/* OP_CB_RECALL */ +struct cb_recall_args { + stateid4 stateid; + bool_t truncate; + nfs41_fh fh; +}; + +struct cb_recall_res { + enum_t status; +}; + +/* OP_CB_NOTIFY */ +struct cb_notify_args { + uint32_t target_highest_slotid; +}; + +struct cb_notify_res { + enum_t status; +}; + +/* OP_CB_PUSH_DELEG */ +struct cb_push_deleg_args { + uint32_t target_highest_slotid; +}; + +struct cb_push_deleg_res { + enum_t status; +}; + +/* OP_CB_RECALL_ANY */ +struct cb_recall_any_args { + uint32_t target_highest_slotid; +}; + +struct cb_recall_any_res { + enum_t status; +}; + +/* OP_CB_RECALLABLE_OBJ_AVAIL */ +struct cb_recallable_obj_avail_args { + uint32_t target_highest_slotid; +}; + +struct cb_recallable_obj_avail_res { + enum_t status; +}; + +/* OP_CB_WANTS_CANCELLED */ +struct cb_wants_cancelled_args { + uint32_t target_highest_slotid; +}; + +struct cb_wants_cancelled_res { + enum_t status; +}; + +/* OP_CB_NOTIFY_LOCK */ +struct cb_notify_lock_args { + uint32_t target_highest_slotid; +}; + +struct cb_notify_lock_res { + enum_t status; +}; + +/* OP_CB_NOTIFY_DEVICEID */ +enum notify_deviceid_type4 { + NOTIFY_DEVICEID4_CHANGE = 1, + NOTIFY_DEVICEID4_DELETE = 2 +}; +struct notify_deviceid4 { + unsigned char deviceid[16]; + enum notify_deviceid_type4 type; +#ifdef __REACTOS__ + uint32_t layouttype; +#else + enum pnfs_layout_type layouttype; +#endif + bool_t immediate; +}; +struct notify4 { + bitmap4 mask; + char *list; + uint32_t len; +}; +struct cb_notify_deviceid_args { + struct notify4 *notify_list; + uint32_t notify_count; + struct notify_deviceid4 *change_list; + uint32_t change_count; +}; + +struct cb_notify_deviceid_res { + enum_t status; +}; + +/* CB_COMPOUND */ +#define CB_COMPOUND_MAX_TAG 64 +#define CB_COMPOUND_MAX_OPERATIONS 16 + +union cb_op_args { + struct cb_layoutrecall_args layoutrecall; + struct cb_recall_slot_args recall_slot; + struct cb_sequence_args sequence; + struct cb_getattr_args getattr; + struct cb_recall_args recall; + struct cb_notify_deviceid_args notify_deviceid; +}; +struct cb_argop { + enum_t opnum; + union cb_op_args args; +}; +struct cb_compound_tag { + char str[CB_COMPOUND_MAX_TAG]; + uint32_t len; +}; +struct cb_compound_args { + struct cb_compound_tag tag; + uint32_t minorversion; + uint32_t callback_ident; /* client MUST ignore */ + struct cb_argop *argarray; + uint32_t argarray_count; /* <= CB_COMPOUND_MAX_OPERATIONS */ +}; + +union cb_op_res { + enum_t status; /* all results start with status */ + struct cb_layoutrecall_res layoutrecall; + struct cb_recall_slot_res recall_slot; + struct cb_sequence_res sequence; + struct cb_getattr_res getattr; + struct cb_recall_res recall; + struct cb_notify_deviceid_res notify_deviceid; +}; +struct cb_resop { + enum_t opnum; + union cb_op_res res; + bool_t xdr_ok; +}; +struct cb_compound_res { + enum_t status; + struct cb_compound_tag tag; + struct cb_resop *resarray; + uint32_t resarray_count; /* <= CB_COMPOUND_MAX_OPERATIONS */ +}; + + +/* callback_xdr.c */ +bool_t proc_cb_compound_args(XDR *xdr, struct cb_compound_args *args); +bool_t proc_cb_compound_res(XDR *xdr, struct cb_compound_res *res); + +/* callback_server.c */ +struct __nfs41_session; +void nfs41_callback_session_init( + IN struct __nfs41_session *session); + +#endif /* !__NFS41_CALLBACK_H__ */ diff --git a/reactos/base/services/nfsd/nfs41_client.c b/reactos/base/services/nfsd/nfs41_client.c new file mode 100644 index 00000000000..566f17eea67 --- /dev/null +++ b/reactos/base/services/nfsd/nfs41_client.c @@ -0,0 +1,442 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include +#include +#include +#include +#include /* for GetAdaptersAddresses() */ +#include /* for Crypt*() functions */ +#include /* for hostent struct */ + +#include "tree.h" +#include "delegation.h" +#include "daemon_debug.h" +#include "nfs41_ops.h" + + +uint32_t nfs41_exchange_id_flags( + IN bool_t is_data) +{ + uint32_t flags = EXCHGID4_FLAG_SUPP_MOVED_REFER; + if (is_data) + flags |= EXCHGID4_FLAG_USE_PNFS_DS; + else + flags |= EXCHGID4_FLAG_USE_NON_PNFS | EXCHGID4_FLAG_USE_PNFS_MDS; + return flags; +} + +static int pnfs_client_init( + IN nfs41_client *client) +{ + enum pnfs_status pnfsstat; + int status = NO_ERROR; + + /* initialize the pnfs layout and device lists for metadata clients */ + pnfsstat = pnfs_layout_list_create(&client->layouts); + if (pnfsstat) { + status = ERROR_NOT_ENOUGH_MEMORY; + goto out; + } + pnfsstat = pnfs_file_device_list_create(&client->devices); + if (pnfsstat) { + status = ERROR_NOT_ENOUGH_MEMORY; + goto out_err_layouts; + } +out: + return status; + +out_err_layouts: + pnfs_layout_list_free(client->layouts); + client->layouts = NULL; + goto out; +} + +static int update_server( + IN nfs41_client *client, + IN const char *server_scope, + IN const server_owner4 *owner) +{ + nfs41_server *server; + int status; + + /* find a server matching the owner.major_id and scope */ + status = nfs41_server_find_or_create(owner->so_major_id, + server_scope, nfs41_rpc_netaddr(client->rpc), &server); + if (status) + goto out; + + /* if the server is the same, we now have an extra reference. if + * the servers are different, we still need to deref the old server. + * so both cases can be treated the same */ + if (client->server) + nfs41_server_deref(client->server); + client->server = server; +out: + return status; +} + +static int update_exchangeid_res( + IN nfs41_client *client, + IN const nfs41_exchange_id_res *exchangeid) +{ + client->clnt_id = exchangeid->clientid; + client->seq_id = exchangeid->sequenceid; + client->roles = exchangeid->flags & EXCHGID4_FLAG_MASK_PNFS; + return update_server(client, exchangeid->server_scope, + &exchangeid->server_owner); +} + +int nfs41_client_create( + IN nfs41_rpc_clnt *rpc, + IN const client_owner4 *owner, + IN bool_t is_data, + IN const nfs41_exchange_id_res *exchangeid, + OUT nfs41_client **client_out) +{ + int status; + nfs41_client *client; + + client = calloc(1, sizeof(nfs41_client)); + if (client == NULL) { + status = GetLastError(); + goto out_err_rpc; + } + + memcpy(&client->owner, owner, sizeof(client_owner4)); + client->rpc = rpc; + client->is_data = is_data; + + status = update_exchangeid_res(client, exchangeid); + if (status) + goto out_err_client; + + list_init(&client->state.opens); + list_init(&client->state.delegations); + InitializeCriticalSection(&client->state.lock); + + //initialize a lock used to protect access to client id and client id seq# + InitializeSRWLock(&client->exid_lock); + + InitializeConditionVariable(&client->recovery.cond); + InitializeCriticalSection(&client->recovery.lock); + + status = pnfs_client_init(client); + if (status) { + eprintf("pnfs_client_init() failed with %d\n", status); + goto out_err_client; + } + *client_out = client; +out: + return status; +out_err_client: + nfs41_client_free(client); /* also calls nfs41_rpc_clnt_free() */ + goto out; +out_err_rpc: + nfs41_rpc_clnt_free(rpc); + goto out; +} + +static void dprint_roles( + IN int level, + IN uint32_t roles) +{ + dprintf(level, "roles: %s%s%s\n", + (roles & EXCHGID4_FLAG_USE_NON_PNFS) ? "USE_NON_PNFS " : "", + (roles & EXCHGID4_FLAG_USE_PNFS_MDS) ? "USE_PNFS_MDS " : "", + (roles & EXCHGID4_FLAG_USE_PNFS_DS) ? "USE_PNFS_DS" : ""); +} + +int nfs41_client_renew( + IN nfs41_client *client) +{ + nfs41_exchange_id_res exchangeid = { 0 }; + int status; + + status = nfs41_exchange_id(client->rpc, &client->owner, + nfs41_exchange_id_flags(client->is_data), &exchangeid); + if (status) { + eprintf("nfs41_exchange_id() failed with %d\n", status); + status = ERROR_BAD_NET_RESP; + goto out; + } + + if (client->is_data) { /* require USE_PNFS_DS */ + if ((exchangeid.flags & EXCHGID4_FLAG_USE_PNFS_DS) == 0) { + eprintf("client expected USE_PNFS_DS\n"); + status = ERROR_BAD_NET_RESP; + goto out; + } + } else { /* require USE_NON_PNFS or USE_PNFS_MDS */ + if ((exchangeid.flags & EXCHGID4_FLAG_USE_NON_PNFS) == 0 && + (exchangeid.flags & EXCHGID4_FLAG_USE_PNFS_MDS) == 0) { + eprintf("client expected USE_NON_PNFS OR USE_PNFS_MDS\n"); + status = ERROR_BAD_NET_RESP; + goto out; + } + } + + dprint_roles(2, exchangeid.flags); + + AcquireSRWLockExclusive(&client->exid_lock); + status = update_exchangeid_res(client, &exchangeid); + ReleaseSRWLockExclusive(&client->exid_lock); +out: + return status; +} + +void nfs41_client_free( + IN nfs41_client *client) +{ + dprintf(2, "nfs41_client_free(%llu)\n", client->clnt_id); + nfs41_client_delegation_free(client); + if (client->session) nfs41_session_free(client->session); + nfs41_destroy_clientid(client->rpc, client->clnt_id); + if (client->server) nfs41_server_deref(client->server); + nfs41_rpc_clnt_free(client->rpc); + if (client->layouts) pnfs_layout_list_free(client->layouts); + if (client->devices) pnfs_file_device_list_free(client->devices); + DeleteCriticalSection(&client->state.lock); + DeleteCriticalSection(&client->recovery.lock); + free(client); +} + + +/* client_owner generation + * we choose to use MAC addresses to generate a client_owner value that + * is unique to a machine and persists over restarts. because the client + * can have multiple network adapters/addresses, we take each adapter into + * account. the specification suggests that "for privacy reasons, it is + * best to perform some one-way function," so we apply an md5 hash to the + * sorted list of MAC addresses */ + +/* References: + * RFC 5661: 2.4. Client Identifiers and Client Owners + * http://tools.ietf.org/html/rfc5661#section-2.4 + * + * MSDN: GetAdaptersAddresses Function + * http://msdn.microsoft.com/en-us/library/aa365915%28VS.85%29.aspx + * + * MSDN: Example C Program: Creating an MD5 Hash from File Content + * http://msdn.microsoft.com/en-us/library/aa382380%28VS.85%29.aspx */ + + +/* use an rbtree to sort mac address entries */ +struct mac_entry { + RB_ENTRY(mac_entry) rbnode; + PBYTE address; + ULONG length; +}; + +int mac_cmp(struct mac_entry *lhs, struct mac_entry *rhs) +{ + const int diff = rhs->length - lhs->length; + return diff ? diff : strncmp((const char*)lhs->address, + (const char*)rhs->address, lhs->length); +} +RB_HEAD(mac_tree, mac_entry); +RB_GENERATE(mac_tree, mac_entry, rbnode, mac_cmp) + +static void mac_entry_insert( + IN struct mac_tree *root, + IN PBYTE address, + IN ULONG length) +{ + struct mac_entry *entry; + + entry = calloc(1, sizeof(struct mac_entry)); + if (entry == NULL) + return; + + entry->address = address; + entry->length = length; + + if (RB_INSERT(mac_tree, root, entry)) + free(entry); +} + +static int adapter_valid( + IN const IP_ADAPTER_ADDRESSES *addr) +{ + /* ignore generic interfaces whose address is not unique */ + switch (addr->IfType) { + case IF_TYPE_SOFTWARE_LOOPBACK: + case IF_TYPE_TUNNEL: + return 0; + } + /* must have an address */ + if (addr->PhysicalAddressLength == 0) + return 0; +#ifndef __REACTOS__ + /* must support ip */ + return addr->Ipv4Enabled || addr->Ipv6Enabled; +#else + return 1; +#endif +} + +static DWORD hash_mac_addrs( + IN HCRYPTHASH hash) +{ + PIP_ADAPTER_ADDRESSES addr, addrs = NULL; + struct mac_tree rbtree = RB_INITIALIZER(rbtree); + struct mac_entry *entry, *node; + ULONG len; + DWORD status; + + /* start with enough room for DEFAULT_MINIMUM_ENTITIES */ + len = DEFAULT_MINIMUM_ENTITIES * sizeof(IP_ADAPTER_ADDRESSES); + + do { + PIP_ADAPTER_ADDRESSES tmp; + /* reallocate the buffer until we can fit all of it */ + tmp = realloc(addrs, len); + if (tmp == NULL) { + status = GetLastError(); + goto out; + } + addrs = tmp; + status = GetAdaptersAddresses(AF_UNSPEC, + GAA_FLAG_INCLUDE_ALL_INTERFACES | GAA_FLAG_SKIP_ANYCAST | + GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_FRIENDLY_NAME | + GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_UNICAST, + NULL, addrs, &len); + } while (status == ERROR_BUFFER_OVERFLOW); + + if (status) { + eprintf("GetAdaptersAddresses() failed with %d\n", status); + goto out; + } + + /* get the mac address of each adapter */ + for (addr = addrs; addr; addr = addr->Next) + if (adapter_valid(addr)) + mac_entry_insert(&rbtree, addr->PhysicalAddress, + addr->PhysicalAddressLength); + + /* require at least one valid address */ + if (RB_EMPTY(&rbtree)) { + status = ERROR_FILE_NOT_FOUND; + eprintf("GetAdaptersAddresses() did not return " + "any valid mac addresses, failing with %d.\n", status); + goto out; + } + + RB_FOREACH_SAFE(entry, mac_tree, &rbtree, node) { + RB_REMOVE(mac_tree, &rbtree, entry); + + if (!CryptHashData(hash, entry->address, entry->length, 0)) { + status = GetLastError(); + eprintf("CryptHashData() failed with %d\n", status); + /* don't break here, we need to free the rest */ + } + free(entry); + } +out: + free(addrs); + return status; +} + +int nfs41_client_owner( + IN const char *name, + IN uint32_t sec_flavor, + OUT client_owner4 *owner) +{ + HCRYPTPROV context; + HCRYPTHASH hash; + PBYTE buffer; + DWORD length; + const ULONGLONG time_created = GetTickCount64(); + int status; + char username[UNLEN + 1]; + DWORD len = UNLEN + 1; + + if (!GetUserNameA(username, &len)) { + status = GetLastError(); + eprintf("GetUserName() failed with %d\n", status); + goto out; + } + + /* owner.verifier = "time created" */ + memcpy(owner->co_verifier, &time_created, sizeof(time_created)); + + /* set up the md5 hash generator */ + if (!CryptAcquireContext(&context, NULL, NULL, + PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { + status = GetLastError(); + eprintf("CryptAcquireContext() failed with %d\n", status); + goto out; + } + if (!CryptCreateHash(context, CALG_MD5, 0, 0, &hash)) { + status = GetLastError(); + eprintf("CryptCreateHash() failed with %d\n", status); + goto out_context; + } + + if (!CryptHashData(hash, (const BYTE*)&sec_flavor, (DWORD)sizeof(sec_flavor), 0)) { + status = GetLastError(); + eprintf("CryptHashData() failed with %d\n", status); + goto out_hash; + } + + if (!CryptHashData(hash, (const BYTE*)username, (DWORD)strlen(username), 0)) { + status = GetLastError(); + eprintf("CryptHashData() failed with %d\n", status); + goto out_hash; + } + + if (!CryptHashData(hash, (const BYTE*)name, (DWORD)strlen(name), 0)) { + status = GetLastError(); + eprintf("CryptHashData() failed with %d\n", status); + goto out_hash; + } + + /* add the mac address from each applicable adapter to the hash */ + status = hash_mac_addrs(hash); + if (status) { + eprintf("hash_mac_addrs() failed with %d\n", status); + goto out_hash; + } + + /* extract the hash size (should always be 16 for md5) */ + buffer = (PBYTE)&owner->co_ownerid_len; + length = (DWORD)sizeof(DWORD); + if (!CryptGetHashParam(hash, HP_HASHSIZE, buffer, &length, 0)) { + status = GetLastError(); + eprintf("CryptGetHashParam(size) failed with %d\n", status); + goto out_hash; + } + /* extract the hash buffer */ + buffer = owner->co_ownerid; + length = owner->co_ownerid_len; + if (!CryptGetHashParam(hash, HP_HASHVAL, buffer, &length, 0)) { + status = GetLastError(); + eprintf("CryptGetHashParam(val) failed with %d\n", status); + goto out_hash; + } + +out_hash: + CryptDestroyHash(hash); +out_context: + CryptReleaseContext(context, 0); +out: + return status; +} diff --git a/reactos/base/services/nfsd/nfs41_compound.c b/reactos/base/services/nfsd/nfs41_compound.c new file mode 100644 index 00000000000..7017dda08d5 --- /dev/null +++ b/reactos/base/services/nfsd/nfs41_compound.c @@ -0,0 +1,457 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include +#include + +#include "nfs41_compound.h" +#include "nfs41_xdr.h" +#include "nfs41_ops.h" +#include "recovery.h" +#include "name_cache.h" +#include "daemon_debug.h" +#include "rpc/rpc.h" +#include "rpc/auth_sspi.h" + +int compound_error(int status) +{ + if (status != NFS4_OK) + dprintf(1, "COMPOUND failed with status %d.\n", status); + return status; +} + +void compound_init( + nfs41_compound *compound, + nfs_argop4 *argops, + nfs_resop4 *resops, + const char *tag) +{ + /* initialize args */ + compound->args.tag_len = (uint32_t)strlen(tag); + memcpy(compound->args.tag, tag, compound->args.tag_len); + compound->args.minorversion = 1; + compound->args.argarray_count = 0; + compound->args.argarray = argops; + + /* initialize results */ + ZeroMemory(&compound->res, sizeof(nfs41_compound_res)); + compound->res.tag_len = NFS4_OPAQUE_LIMIT; + compound->res.resarray_count = 0; + compound->res.resarray = resops; +} + +void compound_add_op( + nfs41_compound *compound, + uint32_t opnum, + void *arg, + void *res) +{ + const uint32_t i = compound->args.argarray_count++; + const uint32_t j = compound->res.resarray_count++; + compound->args.argarray[i].op = opnum; + compound->args.argarray[i].arg = arg; + compound->res.resarray[j].op = opnum; + compound->res.resarray[j].res = res; +} + +/* Due to the possibility of replays, we might get a response to a different + * call than the one we're expecting. If we don't have a way to check for + * this, we'll likely crash trying to decode into the wrong structures. + * This function copies the number of operations and all of the operation + * numbers from the compound arguments into the response, so we can verify + * them on decode and fail before doing any damage. */ +static void set_expected_res( + nfs41_compound *compound) +{ + uint32_t i; + compound->res.resarray_count = compound->args.argarray_count; + for (i = 0; i < compound->res.resarray_count; i++) + compound->res.resarray[i].op = compound->args.argarray[i].op; +} + + +static int create_new_rpc_auth(nfs41_session *session, uint32_t op, + nfs41_secinfo_info *secinfo) +{ + AUTH *auth = NULL; + int status = ERROR_NETWORK_UNREACHABLE, i; + uint32_t sec_flavor; + + for (i = 0; i < MAX_SECINFOS; i++) { + if (!secinfo[i].sec_flavor && !secinfo[i].type) + goto out; + if (secinfo[i].sec_flavor == RPCSEC_GSS) { + auth = authsspi_create_default(session->client->rpc->rpc, + session->client->rpc->server_name, secinfo[i].type); + if (auth == NULL) { + eprintf("handle_wrongsecinfo_noname: authsspi_create_default for " + "gsstype %s failed\n", gssauth_string(secinfo[i].type)); + continue; + } + sec_flavor = secinfo[i].type; + } else { + char machname[MAXHOSTNAMELEN + 1]; + gid_t gids[1]; + if (gethostname(machname, sizeof(machname)) == -1) { + eprintf("nfs41_rpc_clnt_create: gethostname failed\n"); + continue; + } + machname[sizeof(machname) - 1] = '\0'; + auth = authsys_create(machname, session->client->rpc->uid, + session->client->rpc->gid, 0, gids); + if (auth == NULL) { + eprintf("handle_wrongsecinfo_noname: authsys_create failed\n"); + continue; + } + sec_flavor = AUTH_SYS; + } + AcquireSRWLockExclusive(&session->client->rpc->lock); + session->client->rpc->sec_flavor = sec_flavor; + session->client->rpc->rpc->cl_auth = auth; + ReleaseSRWLockExclusive(&session->client->rpc->lock); + status = 0; + break; + } +out: + return status; +} + +int compound_encode_send_decode( + nfs41_session *session, + nfs41_compound *compound, + bool_t try_recovery) +{ + int status, retry_count = 0, delayby = 0, secinfo_status; + nfs41_sequence_args *args = (nfs41_sequence_args *) + compound->args.argarray[0].arg; + uint32_t saved_sec_flavor; + AUTH *saved_auth; + int op1 = compound->args.argarray[0].op; + +retry: + /* send compound */ + retry_count++; + set_expected_res(compound); + status = nfs41_send_compound(session->client->rpc, + (char *)&compound->args, (char *)&compound->res); + // bump sequence number if sequence op succeeded. + if (compound->res.resarray_count > 0 && + compound->res.resarray[0].op == OP_SEQUENCE) { + nfs41_sequence_res *seq = + (nfs41_sequence_res *)compound->res.resarray[0].res; + if (seq->sr_status == NFS4_OK) { + // returned slotid must be the same we sent + if (seq->sr_resok4.sr_slotid != args->sa_slotid) { + eprintf("[session] sr_slotid=%d != sa_slotid=%d\n", + seq->sr_resok4.sr_slotid, args->sa_slotid); + status = NFS4ERR_IO; + goto out_free_slot; + } + // returned sessionid must be the same we sent + if (memcmp(seq->sr_resok4.sr_sessionid, args->sa_sessionid, + NFS4_SESSIONID_SIZE)) { + eprintf("[session] sr_sessionid != sa_sessionid\n"); + print_hexbuf(1, (unsigned char *)"sr_sessionid", + seq->sr_resok4.sr_sessionid, NFS4_SESSIONID_SIZE); + print_hexbuf(1, (unsigned char *)"sa_sessionid", + args->sa_sessionid, NFS4_SESSIONID_SIZE); + status = NFS4ERR_IO; + goto out_free_slot; + } + if (seq->sr_resok4.sr_status_flags) + print_sr_status_flags(1, seq->sr_resok4.sr_status_flags); + + nfs41_session_bump_seq(session, args->sa_slotid, + seq->sr_resok4.sr_target_highest_slotid); + + /* check sequence status flags for state revocation */ + if (try_recovery && seq->sr_resok4.sr_status_flags) + nfs41_recover_sequence_flags(session, + seq->sr_resok4.sr_status_flags); + } + } + + if (status) { + eprintf("nfs41_send_compound failed %d for seqid=%d, slotid=%d\n", + status, args->sa_sequenceid, args->sa_slotid); + status = NFS4ERR_IO; + goto out_free_slot; + } + + if (compound->res.status != NFS4_OK) + dprintf(1, "\n################ %s ################\n\n", + nfs_error_string(compound->res.status)); + + switch (compound->res.status) { + case NFS4_OK: + break; + + case NFS4ERR_STALE_CLIENTID: + if (!try_recovery) + goto out; + if (!nfs41_recovery_start_or_wait(session->client)) + goto do_retry; + // try to create a new client + status = nfs41_client_renew(session->client); + + nfs41_recovery_finish(session->client); + if (status) { + eprintf("nfs41_client_renew() failed with %d\n", status); + status = ERROR_BAD_NET_RESP; + goto out; + } + if (op1 == OP_CREATE_SESSION) { + nfs41_create_session_args *csa = (nfs41_create_session_args*) + compound->args.argarray[0].arg; + AcquireSRWLockShared(&session->client->exid_lock); + csa->csa_clientid = session->client->clnt_id; + csa->csa_sequence = session->client->seq_id; + AcquireSRWLockShared(&session->client->exid_lock); + } + goto do_retry; + + case NFS4ERR_BADSESSION: + if (!try_recovery) + goto out; + if (!nfs41_recovery_start_or_wait(session->client)) + goto do_retry; + // try to create a new session + status = nfs41_recover_session(session, FALSE); + + nfs41_recovery_finish(session->client); + if (status) { + eprintf("nfs41_recover_session() failed with %d\n", status); + status = ERROR_BAD_NET_RESP; + goto out; + } + goto do_retry; + + case NFS4ERR_EXPIRED: /* revoked by lease expiration */ + case NFS4ERR_BAD_STATEID: + case NFS4ERR_STALE_STATEID: /* server reboot */ + if (op1 == OP_SEQUENCE) + nfs41_session_free_slot(session, args->sa_slotid); + if (try_recovery && nfs41_recover_stateid(session, + &compound->args.argarray[compound->res.resarray_count-1])) + goto do_retry; + goto out; + + case NFS4ERR_BADSLOT: + /* free the slot and retry with a new one */ + if (op1 != OP_SEQUENCE || nfs41_session_bad_slot(session, args)) + goto out; + goto retry; + + case NFS4ERR_GRACE: + case NFS4ERR_DELAY: +#define RETRY_INDEFINITELY +#ifndef RETRY_INDEFINITELY +#define NUMBER_2_RETRY 19 +#endif + +#ifndef RETRY_INDEFINITELY + if (retry_count < NUMBER_2_RETRY) { +#endif + if (op1 == OP_SEQUENCE) + nfs41_session_free_slot(session, args->sa_slotid); + if (compound->res.status == NFS4ERR_GRACE) + delayby = 5000; + else + delayby = 500*retry_count; + dprintf(1, "Compound returned %s: sleeping for %ums..\n", + (compound->res.status==NFS4ERR_GRACE)?"NFS4ERR_GRACE":"NFS4ERR_DELAY", + delayby); + Sleep(delayby); + dprintf(1, "Attempting to resend compound.\n"); + goto do_retry; +#ifndef RETRY_INDEFINITELY + } +#endif + break; + + case NFS4ERR_FHEXPIRED: /* TODO: recover expired volatile filehandles */ + status = NFS4ERR_STALE; /* for now, treat them as ERR_STALE */ + /* no break */ + case NFS4ERR_STALE: + { + nfs_argop4 *argarray = compound->args.argarray; + struct nfs41_name_cache *name_cache = + session_name_cache(session); + nfs41_putfh_args *putfh; + uint32_t i, start = 0; + + /* NFS4ERR_STALE generally comes from a PUTFH operation. in + * this case, remove its filehandle from the name cache. but + * because COMPOUNDs are not atomic, a file can be removed + * between PUTFH and the operation that uses it. in this + * case, we can't tell which PUTFH operation is to blame, so + * we must invalidate filehandles of all PUTFH operations in + * the COMPOUND */ + + if (argarray[compound->res.resarray_count-1].op == OP_PUTFH) + start = compound->res.resarray_count-1; + + for (i = start; i < compound->res.resarray_count; i++) { + if (argarray[i].op == OP_PUTFH) { + putfh = (nfs41_putfh_args*)argarray[i].arg; + + if (!putfh->in_recovery && putfh->file->path) + nfs41_name_cache_remove_stale(name_cache, + session, putfh->file->path); + } + } + } + break; + case NFS4ERR_WRONGSEC: + { + nfs41_secinfo_info secinfo[MAX_SECINFOS] = { 0 }; + uint32_t rcount = compound->res.resarray_count; + nfs_argop4 *argarray = compound->args.argarray; + uint32_t op = argarray[rcount-1].op; + nfs41_putfh_args *putfh; + nfs41_path_fh *file = NULL; + switch(op) { + case OP_PUTFH: + case OP_RESTOREFH: + case OP_LINK: + case OP_RENAME: + case OP_PUTROOTFH: + case OP_LOOKUP: + case OP_OPEN: + case OP_SECINFO_NO_NAME: + case OP_SECINFO: + if (op1 == OP_SEQUENCE) + nfs41_session_free_slot(session, args->sa_slotid); + /* from: 2.6.3.1.1.5. Put Filehandle Operation + SECINFO/SECINFO_NO_NAME + * The NFSv4.1 server MUST NOT return NFS4ERR_WRONGSEC to a put + * filehandle operation that is immediately followed by SECINFO or + * SECINFO_NO_NAME. The NFSv4.1 server MUST NOT return NFS4ERR_WRONGSEC + * from SECINFO or SECINFO_NO_NAME. + */ + if (op1 == OP_SEQUENCE && + (argarray[1].op == OP_PUTFH || + argarray[1].op == OP_PUTROOTFH) && + (argarray[2].op == OP_SECINFO_NO_NAME || + argarray[2].op == OP_SECINFO)) { + dprintf(1, "SECINFO: BROKEN SERVER\n"); + goto out; + } + if (!try_recovery) + goto out; + if (!nfs41_recovery_start_or_wait(session->client)) + goto do_retry; + + saved_sec_flavor = session->client->rpc->sec_flavor; + saved_auth = session->client->rpc->rpc->cl_auth; + if (op == OP_LOOKUP || op == OP_OPEN) { + const nfs41_component *name; + nfs41_path_fh tmp = { 0 }; + nfs41_getfh_res *getfh; + nfs41_lookup_args *largs; + nfs41_op_open_args *oargs; + if (argarray[rcount-2].op == OP_PUTFH) { + putfh = (nfs41_putfh_args *)argarray[rcount-2].arg; + file = putfh->file; + } else if (argarray[rcount-2].op == OP_GETATTR && + argarray[rcount-3].op == OP_GETFH) { + getfh = (nfs41_getfh_res *)compound->res.resarray[rcount-3].res; + memcpy(&tmp.fh, getfh->fh, sizeof(nfs41_fh)); + file = &tmp; + } + else { + nfs41_recovery_finish(session->client); + goto out; + } + + if (op == OP_LOOKUP) { + largs = (nfs41_lookup_args *)argarray[rcount-1].arg; + name = largs->name; + } else if (op == OP_OPEN) { + oargs = (nfs41_op_open_args *)argarray[rcount-1].arg; + name = oargs->claim->u.null.filename; + } + secinfo_status = nfs41_secinfo(session, file, name, secinfo); + if (secinfo_status) { + eprintf("nfs41_secinfo failed with %d\n", secinfo_status); + nfs41_recovery_finish(session->client); + if (secinfo_status == NFS4ERR_BADSESSION) { + if (op1 == OP_SEQUENCE) + nfs41_session_free_slot(session, args->sa_slotid); + goto do_retry; + } + goto out_free_slot; + } + } + else { + if (op == OP_PUTFH) { + putfh = (nfs41_putfh_args *)argarray[rcount-1].arg; + file = putfh->file; + } + secinfo_status = nfs41_secinfo_noname(session, file, secinfo); + if (secinfo_status) { + eprintf("nfs41_secinfo_noname failed with %d\n", + secinfo_status); + nfs41_recovery_finish(session->client); + if (op1 == OP_SEQUENCE) + nfs41_session_free_slot(session, args->sa_slotid); + goto out_free_slot; + } + } + secinfo_status = create_new_rpc_auth(session, op, secinfo); + if (!secinfo_status) { + auth_destroy(saved_auth); + nfs41_recovery_finish(session->client); + // Need to retry only + goto do_retry; + } else { + AcquireSRWLockExclusive(&session->client->rpc->lock); + session->client->rpc->sec_flavor = saved_sec_flavor; + session->client->rpc->rpc->cl_auth = saved_auth; + ReleaseSRWLockExclusive(&session->client->rpc->lock); + nfs41_recovery_finish(session->client); + } + break; + } + } + } + if (compound->res.resarray[0].op == OP_SEQUENCE) { + nfs41_sequence_res *seq = + (nfs41_sequence_res *)compound->res.resarray[0].res; + if (seq->sr_status == NFS4_OK && session->client->rpc->needcb && + (seq->sr_resok4.sr_status_flags & SEQ4_STATUS_CB_PATH_DOWN)) { + nfs41_session_free_slot(session, args->sa_slotid); + nfs41_bind_conn_to_session(session->client->rpc, + session->session_id, CDFC4_BACK_OR_BOTH); + goto out; + } + } +out_free_slot: + if (op1 == OP_SEQUENCE) + nfs41_session_free_slot(session, args->sa_slotid); +out: + return status; + +do_retry: + if (compound->res.resarray[0].op == OP_SEQUENCE) + nfs41_session_get_slot(session, &args->sa_slotid, + &args->sa_sequenceid, &args->sa_highest_slotid); + goto retry; +} diff --git a/reactos/base/services/nfsd/nfs41_compound.h b/reactos/base/services/nfsd/nfs41_compound.h new file mode 100644 index 00000000000..bd4e3c30d75 --- /dev/null +++ b/reactos/base/services/nfsd/nfs41_compound.h @@ -0,0 +1,80 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#ifndef __NFS41_DAEMON_COMPOUND_H__ +#define __NFS41_DAEMON_COMPOUND_H__ + +#include "nfs41.h" + + +/* COMPOUND */ +typedef struct __nfs_argop4 { + uint32_t op; + void *arg; +} nfs_argop4; + +typedef struct __nfs41_compound_args { + uint32_t tag_len; + unsigned char tag[NFS4_OPAQUE_LIMIT]; + uint32_t minorversion; + uint32_t argarray_count; + nfs_argop4 *argarray; /* <> */ +} nfs41_compound_args; + +typedef struct __nfs_resop4 { + uint32_t op; + void *res; +} nfs_resop4; + +typedef struct __nfs41_compound_res { + uint32_t status; + uint32_t tag_len; + unsigned char tag[NFS4_OPAQUE_LIMIT]; + uint32_t resarray_count; + nfs_resop4 *resarray; /* <> */ +} nfs41_compound_res; + +typedef struct __nfs41_compound { + nfs41_compound_args args; + nfs41_compound_res res; +} nfs41_compound; + + +int compound_error(int status); + +void compound_init( + nfs41_compound *compound, + nfs_argop4 *argops, + nfs_resop4 *resops, + const char *tag); + +void compound_add_op( + nfs41_compound *compound, + uint32_t opnum, + void *arg, + void *res); + +int compound_encode_send_decode( + nfs41_session *session, + nfs41_compound *compound, + bool_t try_recovery); + +#endif /* __NFS41_DAEMON_COMPOUND_H__ */ diff --git a/reactos/base/services/nfsd/nfs41_const.h b/reactos/base/services/nfsd/nfs41_const.h new file mode 100644 index 00000000000..c47b833efdd --- /dev/null +++ b/reactos/base/services/nfsd/nfs41_const.h @@ -0,0 +1,399 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#ifndef __NFS41_NFS_CONST_H__ +#define __NFS41_NFS_CONST_H__ + + +/* + * Sizes + */ +#define NFS4_FHSIZE 128 +#define NFS4_VERIFIER_SIZE 8 +#define NFS4_OPAQUE_LIMIT 1024 +#define NFS4_SESSIONID_SIZE 16 +#define NFS4_STATEID_OTHER 12 +#define NFS4_EASIZE 256 +#define NFS4_EANAME_SIZE 128 + + +#define NFS41_MAX_FILEIO_SIZE (1024 * 1024) +#define NFS41_MAX_SERVER_CACHE 1024 +#define NFS41_MAX_RPC_REQS 128 + +#define UPCALL_BUF_SIZE 2048 + +/* MaximumComponentNameLength reported for FileFsAttributeInformation */ +#define NFS41_MAX_COMPONENT_LEN 256 +#define NFS41_MAX_PATH_LEN 1280 + +#define NFS41_HOSTNAME_LEN 64 +#define NFS41_ADDRS_PER_SERVER 4 + +/* max length of ipv6 address 48 + * sizeof(".255.255") + 8 */ +#define NFS41_UNIVERSAL_ADDR_LEN 56 + +/* "udp" "tcp" "udp6" "tcp6" */ +#define NFS41_NETWORK_ID_LEN 4 + +/* msdn: There is a maximum of 31 reparse points (and + * therefore symbolic links) allowed in a particular path. */ +#define NFS41_MAX_SYMLINK_DEPTH 31 + + +/* 424 bytes: max rpc header for reply with data */ +/* 32 bytes: max COMPOUND response */ +/* 40 bytes: max SEQUENCE response */ +/* 4 bytes: max PUTFH response */ +/* 12 bytes: max READ response */ +#define READ_OVERHEAD 512 + +/* 840 bytes: max rpc header for call */ +/* 32 bytes: max COMPOUND request */ +/* 32 bytes: max SEQUENCE request */ +/* 132 bytes: max PUTFH request */ +/* 32 bytes: max WRITE request */ +#define WRITE_OVERHEAD 1068 + + +#define NFS41_RPC_PROGRAM 100003 +#define NFS41_RPC_VERSION 4 +#define NFS41_RPC_CBPROGRAM 0x2358 + + +/* + * Error status + */ +enum nfsstat4 { + NFS4_OK = 0, /* everything is okay */ + NFS4ERR_PERM = 1, /* caller not privileged */ + NFS4ERR_NOENT = 2, /* no such file/directory */ + NFS4ERR_IO = 5, /* hard I/O error */ + NFS4ERR_NXIO = 6, /* no such device */ + NFS4ERR_ACCESS = 13, /* access denied */ + NFS4ERR_EXIST = 17, /* file already exists */ + NFS4ERR_XDEV = 18, /* different filesystems */ + + NFS4ERR_NOTDIR = 20, /* should be a directory */ + NFS4ERR_ISDIR = 21, /* should not be directory */ + NFS4ERR_INVAL = 22, /* invalid argument */ + NFS4ERR_FBIG = 27, /* file exceeds server max */ + NFS4ERR_NOSPC = 28, /* no space on filesystem */ + NFS4ERR_ROFS = 30, /* read-only filesystem */ + NFS4ERR_MLINK = 31, /* too many hard links */ + NFS4ERR_NAMETOOLONG = 63, /* name exceeds server max */ + NFS4ERR_NOTEMPTY = 66, /* directory not empty */ + NFS4ERR_DQUOT = 69, /* hard quota limit reached*/ + NFS4ERR_STALE = 70, /* file no longer exists */ + NFS4ERR_BADHANDLE = 10001, /* Illegal filehandle */ + NFS4ERR_BAD_COOKIE = 10003, /* READDIR cookie is stale */ + NFS4ERR_NOTSUPP = 10004, /* operation not supported */ + NFS4ERR_TOOSMALL = 10005, /* response limit exceeded */ + NFS4ERR_SERVERFAULT = 10006, /* undefined server error */ + NFS4ERR_BADTYPE = 10007, /* type invalid for CREATE */ + NFS4ERR_DELAY = 10008, /* file "busy" - retry */ + NFS4ERR_SAME = 10009, /* nverify says attrs same */ + NFS4ERR_DENIED = 10010, /* lock unavailable */ + NFS4ERR_EXPIRED = 10011, /* lock lease expired */ + NFS4ERR_LOCKED = 10012, /* I/O failed due to lock */ + NFS4ERR_GRACE = 10013, /* in grace period */ + NFS4ERR_FHEXPIRED = 10014, /* filehandle expired */ + NFS4ERR_SHARE_DENIED = 10015, /* share reserve denied */ + NFS4ERR_WRONGSEC = 10016, /* wrong security flavor */ + NFS4ERR_CLID_INUSE = 10017, /* clientid in use */ + + /* NFS4ERR_RESOURCE is not a valid error in NFSv4.1 */ + NFS4ERR_RESOURCE = 10018, /* resource exhaustion */ + NFS4ERR_MOVED = 10019, /* filesystem relocated */ + NFS4ERR_NOFILEHANDLE = 10020, /* current FH is not set */ + NFS4ERR_MINOR_VERS_MISMATCH = 10021, /* minor vers not supp */ + NFS4ERR_STALE_CLIENTID = 10022, /* server has rebooted */ + NFS4ERR_STALE_STATEID = 10023, /* server has rebooted */ + NFS4ERR_OLD_STATEID = 10024, /* state is out of sync */ + NFS4ERR_BAD_STATEID = 10025, /* incorrect stateid */ + NFS4ERR_BAD_SEQID = 10026, /* request is out of seq. */ + NFS4ERR_NOT_SAME = 10027, /* verify - attrs not same */ + NFS4ERR_LOCK_RANGE = 10028, /* overlapping lock range */ + NFS4ERR_SYMLINK = 10029, /* should be file/directory*/ + NFS4ERR_RESTOREFH = 10030, /* no saved filehandle */ + NFS4ERR_LEASE_MOVED = 10031, /* some filesystem moved */ + NFS4ERR_ATTRNOTSUPP = 10032, /* recommended attr not sup*/ + NFS4ERR_NO_GRACE = 10033, /* reclaim outside of grace*/ + NFS4ERR_RECLAIM_BAD = 10034, /* reclaim error at server */ + NFS4ERR_RECLAIM_CONFLICT = 10035, /* conflict on reclaim */ + NFS4ERR_BADXDR = 10036, /* XDR decode failed */ + NFS4ERR_LOCKS_HELD = 10037, /* file locks held at CLOSE*/ + NFS4ERR_OPENMODE = 10038, /* conflict in OPEN and I/O*/ + NFS4ERR_BADOWNER = 10039, /* owner translation bad */ + NFS4ERR_BADCHAR = 10040, /* utf-8 char not supported*/ + NFS4ERR_BADNAME = 10041, /* name not supported */ + NFS4ERR_BAD_RANGE = 10042, /* lock range not supported*/ + NFS4ERR_LOCK_NOTSUPP = 10043, /* no atomic up/downgrade */ + NFS4ERR_OP_ILLEGAL = 10044, /* undefined operation */ + NFS4ERR_DEADLOCK = 10045, /* file locking deadlock */ + NFS4ERR_FILE_OPEN = 10046, /* open file blocks op. */ + NFS4ERR_ADMIN_REVOKED = 10047, /* lockowner state revoked */ + NFS4ERR_CB_PATH_DOWN = 10048, /* callback path down */ + + /* NFSv4.1 errors start here. */ + NFS4ERR_BADIOMODE = 10049, + NFS4ERR_BADLAYOUT = 10050, + NFS4ERR_BAD_SESSION_DIGEST = 10051, + NFS4ERR_BADSESSION = 10052, + NFS4ERR_BADSLOT = 10053, + NFS4ERR_COMPLETE_ALREADY = 10054, + NFS4ERR_CONN_NOT_BOUND_TO_SESSION = 10055, + NFS4ERR_DELEG_ALREADY_WANTED = 10056, + NFS4ERR_BACK_CHAN_BUSY = 10057, /*backchan reqs outstanding*/ + NFS4ERR_LAYOUTTRYLATER = 10058, + NFS4ERR_LAYOUTUNAVAILABLE = 10059, + NFS4ERR_NOMATCHING_LAYOUT = 10060, + NFS4ERR_RECALLCONFLICT = 10061, + NFS4ERR_UNKNOWN_LAYOUTTYPE = 10062, + NFS4ERR_SEQ_MISORDERED = 10063, /* unexpected seq.ID in req*/ + NFS4ERR_SEQUENCE_POS = 10064, /* [CB_]SEQ. op not 1st op */ + NFS4ERR_REQ_TOO_BIG = 10065, /* request too big */ + NFS4ERR_REP_TOO_BIG = 10066, /* reply too big */ + NFS4ERR_REP_TOO_BIG_TO_CACHE = 10067, /* rep. not all cached */ + NFS4ERR_RETRY_UNCACHED_REP = 10068, /* retry & rep. uncached */ + NFS4ERR_UNSAFE_COMPOUND = 10069, /* retry/recovery too hard */ + NFS4ERR_TOO_MANY_OPS = 10070, /*too many ops in [CB_]COMP*/ + NFS4ERR_OP_NOT_IN_SESSION = 10071, /* op needs [CB_]SEQ. op */ + NFS4ERR_HASH_ALG_UNSUPP = 10072, /* hash alg. not supp. */ + /* Error 10073 is unused. */ + NFS4ERR_CLIENTID_BUSY = 10074, /* clientid has state */ + NFS4ERR_PNFS_IO_HOLE = 10075, /* IO to _SPARSE file hole */ + NFS4ERR_SEQ_FALSE_RETRY = 10076, /* Retry != original req. */ + NFS4ERR_BAD_HIGH_SLOT = 10077, /* req has bad highest_slot*/ + NFS4ERR_DEADSESSION = 10078, /*new req sent to dead sess*/ + NFS4ERR_ENCR_ALG_UNSUPP = 10079, /* encr alg. not supp. */ + NFS4ERR_PNFS_NO_LAYOUT = 10080, /* I/O without a layout */ + NFS4ERR_NOT_ONLY_OP = 10081, /* addl ops not allowed */ + NFS4ERR_WRONG_CRED = 10082, /* op done by wrong cred */ + NFS4ERR_WRONG_TYPE = 10083, /* op on wrong type object */ + NFS4ERR_DIRDELEG_UNAVAIL = 10084, /* delegation not avail. */ + NFS4ERR_REJECT_DELEG = 10085, /* cb rejected delegation */ + NFS4ERR_RETURNCONFLICT = 10086, /* layout get before return*/ + NFS4ERR_DELEG_REVOKED = 10087 /* deleg./layout revoked */ +}; + +#define MAKE_WORD0(XXX) (1 << XXX) +#define MAKE_WORD1(XXX) (1 << (XXX-32)) +#define MAKE_WORD2(XXX) (1 << (XXX-64)) + +enum { +/* + * Mandatory Attributes + */ + FATTR4_WORD0_SUPPORTED_ATTRS = MAKE_WORD0(0), + FATTR4_WORD0_TYPE = MAKE_WORD0(1), + FATTR4_WORD0_FH_EXPIRE_TYPE = MAKE_WORD0(2), + FATTR4_WORD0_CHANGE = MAKE_WORD0(3), + FATTR4_WORD0_SIZE = MAKE_WORD0(4), + FATTR4_WORD0_LINK_SUPPORT = MAKE_WORD0(5), + FATTR4_WORD0_SYMLINK_SUPPORT = MAKE_WORD0(6), + FATTR4_WORD0_NAMED_ATTR = MAKE_WORD0(7), + FATTR4_WORD0_FSID = MAKE_WORD0(8), + FATTR4_WORD0_UNIQUE_HANDLES = MAKE_WORD0(9), + FATTR4_WORD0_LEASE_TIME = MAKE_WORD0(10), + FATTR4_WORD0_RDATTR_ERROR = MAKE_WORD0(11), + FATTR4_WORD0_FILEHANDLE = MAKE_WORD0(19), + FATTR4_WORD2_SUPPATTR_EXCLCREAT = MAKE_WORD2(75), + +/* + * Recommended Attributes + */ + FATTR4_WORD0_ACL = MAKE_WORD0(12), + FATTR4_WORD0_ACLSUPPORT = MAKE_WORD0(13), + FATTR4_WORD0_ARCHIVE = MAKE_WORD0(14), + FATTR4_WORD0_CANSETTIME = MAKE_WORD0(15), + FATTR4_WORD0_CASE_INSENSITIVE = MAKE_WORD0(16), + FATTR4_WORD0_CASE_PRESERVING = MAKE_WORD0(17), + FATTR4_WORD0_CHOWN_RESTRICTED = MAKE_WORD0(18), + FATTR4_WORD0_FILEID = MAKE_WORD0(20), + FATTR4_WORD0_FILES_AVAIL = MAKE_WORD0(21), + FATTR4_WORD0_FILES_FREE = MAKE_WORD0(22), + FATTR4_WORD0_FILES_TOTAL = MAKE_WORD0(23), + FATTR4_WORD0_FS_LOCATIONS = MAKE_WORD0(24), + FATTR4_WORD0_HIDDEN = MAKE_WORD0(25), + FATTR4_WORD0_HOMOGENEOUS = MAKE_WORD0(26), + FATTR4_WORD0_MAXFILESIZE = MAKE_WORD0(27), + FATTR4_WORD0_MAXLINK = MAKE_WORD0(28), + FATTR4_WORD0_MAXNAME = MAKE_WORD0(29), + FATTR4_WORD0_MAXREAD = MAKE_WORD0(30), + FATTR4_WORD0_MAXWRITE = MAKE_WORD0(31), + FATTR4_WORD1_MIMETYPE = MAKE_WORD1(32), + FATTR4_WORD1_MODE = MAKE_WORD1(33), + FATTR4_WORD1_NO_TRUNC = MAKE_WORD1(34), + FATTR4_WORD1_NUMLINKS = MAKE_WORD1(35), + FATTR4_WORD1_OWNER = MAKE_WORD1(36), + FATTR4_WORD1_OWNER_GROUP = MAKE_WORD1(37), + FATTR4_WORD1_QUOTA_AVAIL_HARD = MAKE_WORD1(38), + FATTR4_WORD1_QUOTA_AVAIL_SOFT = MAKE_WORD1(39), + FATTR4_WORD1_QUOTA_USED = MAKE_WORD1(40), + FATTR4_WORD1_RAWDEV = MAKE_WORD1(41), + FATTR4_WORD1_SPACE_AVAIL = MAKE_WORD1(42), + FATTR4_WORD1_SPACE_FREE = MAKE_WORD1(43), + FATTR4_WORD1_SPACE_TOTAL = MAKE_WORD1(44), + FATTR4_WORD1_SPACE_USED = MAKE_WORD1(45), + FATTR4_WORD1_SYSTEM = MAKE_WORD1(46), + FATTR4_WORD1_TIME_ACCESS = MAKE_WORD1(47), + FATTR4_WORD1_TIME_ACCESS_SET = MAKE_WORD1(48), + FATTR4_WORD1_TIME_BACKUP = MAKE_WORD1(49), + FATTR4_WORD1_TIME_CREATE = MAKE_WORD1(50), + FATTR4_WORD1_TIME_DELTA = MAKE_WORD1(51), + FATTR4_WORD1_TIME_METADATA = MAKE_WORD1(52), + FATTR4_WORD1_TIME_MODIFY = MAKE_WORD1(53), + FATTR4_WORD1_TIME_MODIFY_SET = MAKE_WORD1(54), + FATTR4_WORD1_MOUNTED_ON_FILEID = MAKE_WORD1(55), + FATTR4_WORD1_DIR_NOTIF_DELAY = MAKE_WORD1(56), + FATTR4_WORD1_DIRENT_NOTIF_DELAY = MAKE_WORD1(57), + FATTR4_WORD1_DACL = MAKE_WORD1(58), + FATTR4_WORD1_SACL = MAKE_WORD1(59), + FATTR4_WORD1_CHANGE_POLICY = MAKE_WORD1(60), + FATTR4_WORD1_FS_STATUS = MAKE_WORD1(61), + FATTR4_WORD1_FS_LAYOUT_TYPE = MAKE_WORD1(62), + FATTR4_WORD1_LAYOUT_HINT = MAKE_WORD1(63), + FATTR4_WORD2_LAYOUT_TYPE = MAKE_WORD2(64), + FATTR4_WORD2_LAYOUT_BLKSIZE = MAKE_WORD2(65), + FATTR4_WORD2_LAYOUT_ALIGNMENT = MAKE_WORD2(66), + FATTR4_WORD2_FS_LOCATIONS_INFO = MAKE_WORD2(67), + FATTR4_WORD2_MDSTHRESHOLD = MAKE_WORD2(68), + FATTR4_WORD2_RETENTION_GET = MAKE_WORD2(69), + FATTR4_WORD2_RETENTION_SET = MAKE_WORD2(70), + FATTR4_WORD2_RETENTEVT_GET = MAKE_WORD2(71), + FATTR4_WORD2_RETENTEVT_SET = MAKE_WORD2(72), + FATTR4_WORD2_RETENTION_HOLD = MAKE_WORD2(73), + FATTR4_WORD2_MODE_SET_MASKED = MAKE_WORD2(74), + FATTR4_WORD2_FS_CHARSET_CAP = MAKE_WORD2(76), +}; + +/* + * File types + */ +enum nfs_ftype4 { + NF4REG = 1, /* Regular File */ + NF4DIR = 2, /* Directory */ + NF4BLK = 3, /* Special File - block device */ + NF4CHR = 4, /* Special File - character device */ + NF4LNK = 5, /* Symbolic Link */ + NF4SOCK = 6, /* Special File - socket */ + NF4FIFO = 7, /* Special File - fifo */ + NF4ATTRDIR = 8, /* Attribute Directory */ + NF4NAMEDATTR = 9, /* Named Attribute */ + + NFS_FTYPE_MASK = 0xF +}; + +#define CREATE_SESSION4_FLAG_PERSIST 0x00000001 +#define CREATE_SESSION4_FLAG_CONN_BACK_CHAN 0x00000002 +#define CREATE_SESSION4_FLAG_CONN_RDMA 0x00000004 + +/* ACLS aclsupport attribute values */ +#define ACL4_SUPPORT_ALLOW_ACL 0x00000001 +#define ACL4_SUPPORT_DENY_ACL 0x00000002 +#define ACL4_SUPPORT_AUDIT_ACL 0x00000004 +#define ACL4_SUPPORT_ALARM_ACL 0x00000008 + +/* ACLS acetype4 field constants */ +#define ACE4_ACCESS_ALLOWED_ACE_TYPE 0x00000000 +#define ACE4_ACCESS_DENIED_ACE_TYPE 0x00000001 +#define ACE4_SYSTEM_AUDIT_ACE_TYPE 0x00000002 +#define ACE4_SYSTEM_ALARM_ACE_TYPE 0x00000003 + +/* ACLS acemask4 field constants */ +#define ACE4_READ_DATA 0x00000001 +#define ACE4_LIST_DIRECTORY 0x00000001 +#define ACE4_WRITE_DATA 0x00000002 +#define ACE4_ADD_FILE 0x00000002 +#define ACE4_APPEND_DATA 0x00000004 +#define ACE4_ADD_SUBDIRECTORY 0x00000004 +#define ACE4_READ_NAMED_ATTRS 0x00000008 +#define ACE4_WRITE_NAMED_ATTRS 0x00000010 +#define ACE4_EXECUTE 0x00000020 +#define ACE4_DELETE_CHILD 0x00000040 +#define ACE4_READ_ATTRIBUTES 0x00000080 +#define ACE4_WRITE_ATTRIBUTES 0x00000100 +#define ACE4_WRITE_RETENTION 0x00000200 +#define ACE4_WRITE_RETENTION_HOLD 0x00000400 + +#define ACE4_DELETE 0x00010000 +#define ACE4_READ_ACL 0x00020000 +#define ACE4_WRITE_ACL 0x00040000 +#define ACE4_WRITE_OWNER 0x00080000 +#define ACE4_SYNCHRONIZE 0x00100000 + +#define ACE4_ALL_FILE ACE4_READ_DATA|ACE4_WRITE_DATA|ACE4_APPEND_DATA| \ + ACE4_READ_NAMED_ATTRS|ACE4_WRITE_NAMED_ATTRS|ACE4_EXECUTE| \ + ACE4_READ_ATTRIBUTES|ACE4_WRITE_ATTRIBUTES| \ + ACE4_DELETE|ACE4_READ_ACL|ACE4_WRITE_ACL|ACE4_WRITE_OWNER| \ + ACE4_SYNCHRONIZE +#define ACE4_ALL_DIR ACE4_READ_DATA|ACE4_WRITE_DATA|ACE4_APPEND_DATA| \ + ACE4_READ_NAMED_ATTRS|ACE4_WRITE_NAMED_ATTRS|ACE4_EXECUTE| \ + ACE4_DELETE_CHILD|ACE4_READ_ATTRIBUTES|ACE4_WRITE_ATTRIBUTES| \ + ACE4_DELETE|ACE4_READ_ACL|ACE4_WRITE_ACL|ACE4_WRITE_OWNER| \ + ACE4_SYNCHRONIZE + +#define ACE4_GENERIC_READ ACE4_READ_DATA|ACE4_READ_NAMED_ATTRS| \ + ACE4_READ_ATTRIBUTES|ACE4_READ_ACL|ACE4_SYNCHRONIZE +#define ACE4_GENERIC_WRITE ACE4_WRITE_DATA|ACE4_WRITE_NAMED_ATTRS| \ + ACE4_WRITE_ATTRIBUTES|ACE4_READ_ACL|ACE4_SYNCHRONIZE +#define ACE4_GENERIC_EXECUTE ACE4_EXECUTE|ACE4_READ_ATTRIBUTES| \ + ACE4_READ_ACL|ACE4_SYNCHRONIZE + + + +#define ACE4_FILE_ALL_ACCESS ACE4_READ_DATA|ACE4_LIST_DIRECTORY| \ + ACE4_WRITE_DATA|ACE4_ADD_FILE|ACE4_APPEND_DATA|ACE4_ADD_SUBDIRECTORY| \ + ACE4_READ_NAMED_ATTRS|ACE4_WRITE_NAMED_ATTRS|ACE4_EXECUTE| \ + ACE4_READ_ATTRIBUTES|ACE4_WRITE_ATTRIBUTES + +/* ACLS aceflag4 field constants */ +#define ACE4_FILE_INHERIT_ACE 0x00000001 +#define ACE4_DIRECTORY_INHERIT_ACE 0x00000002 +#define ACE4_NO_PROPAGATE_INHERIT_ACE 0x00000004 +#define ACE4_INHERIT_ONLY_ACE 0x00000008 +#define ACE4_SUCCESSFUL_ACCESS_ACE_FLAG 0x00000010 +#define ACE4_FAILED_ACCESS_ACE_FLAG 0x00000020 +#define ACE4_IDENTIFIER_GROUP 0x00000040 +#define ACE4_INHERITED_ACE 0x00000080 + +/* ACLS well-defined WHOs */ +#define ACE4_OWNER "OWNER@" +#define ACE4_GROUP "GROUP@" +#define ACE4_EVERYONE "EVERYONE@" +#define ACE4_INTERACTIVE "INTERACTIVE@" +#define ACE4_NETWORK "NETWORK@" +#define ACE4_DIALUP "DIALUP@" +#define ACE4_BATCH "BATCH@" +#define ACE4_ANONYMOUS "ANONYMOUS@" +#define ACE4_AUTHENTICATED "AUTHENTICATED@" +#define ACE4_SERVICE "SERVICE@" +#define ACE4_NOBODY "nobody" + +/* ACLE nfsacl41 aclflag4 constants */ +#define ACL4_AUTO_INHERIT 0x00000001 +#define ACL4_PROTECTED 0x00000002 +#define ACL4_DEFAULTED 0x00000004 + + +#endif /* !__NFS41_NFS_CONST_H__ */ diff --git a/reactos/base/services/nfsd/nfs41_daemon.c b/reactos/base/services/nfsd/nfs41_daemon.c new file mode 100644 index 00000000000..7ad4ad1b12b --- /dev/null +++ b/reactos/base/services/nfsd/nfs41_daemon.c @@ -0,0 +1,501 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include +#include +#include +#include + +#include +#include /* UNLEN for GetUserName() */ +#include /* for GetNetworkParam() */ +#include "nfs41_driver.h" /* for NFS41_USER_DEVICE_NAME_A */ +#include "nfs41_np.h" /* for NFS41NP_SHARED_MEMORY */ + +#include "idmap.h" +#include "daemon_debug.h" +#include "upcall.h" +#include "util.h" + +#define MAX_NUM_THREADS 128 +DWORD NFS41D_VERSION = 0; + +static const char FILE_NETCONFIG[] = "C:\\ReactOS\\System32\\drivers\\etc\\netconfig"; + +/* Globals */ +char localdomain_name[NFS41_HOSTNAME_LEN]; +int default_uid = 666; +int default_gid = 777; + +#ifndef STANDALONE_NFSD //make sure to define it in "sources" not here +#include "service.h" +HANDLE stop_event = NULL; +#endif +typedef struct _nfs41_process_thread { + HANDLE handle; + uint32_t tid; +} nfs41_process_thread; + +static int map_user_to_ids(nfs41_idmapper *idmapper, uid_t *uid, gid_t *gid) +{ + char username[UNLEN + 1]; + DWORD len = UNLEN + 1; + int status = NO_ERROR; + + if (!GetUserNameA(username, &len)) { + status = GetLastError(); + eprintf("GetUserName() failed with %d\n", status); + goto out; + } + dprintf(1, "map_user_to_ids: mapping user %s\n", username); + + if (nfs41_idmap_name_to_ids(idmapper, username, uid, gid)) { + /* instead of failing for auth_sys, fall back to 'nobody' uid/gid */ + *uid = default_uid; + *gid = default_gid; + } +out: + return status; +} + +static unsigned int WINAPI thread_main(void *args) +{ + nfs41_idmapper *idmapper = (nfs41_idmapper*)args; + DWORD status = 0; + HANDLE pipe; + // buffer used to process upcall, assumed to be fixed size. + // if we ever need to handle non-cached IO, need to make it dynamic + unsigned char outbuf[UPCALL_BUF_SIZE], inbuf[UPCALL_BUF_SIZE]; + DWORD inbuf_len = UPCALL_BUF_SIZE, outbuf_len; + nfs41_upcall upcall; + + pipe = CreateFile(NFS41_USER_DEVICE_NAME_A, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, + 0, NULL); + if (pipe == INVALID_HANDLE_VALUE) + { + eprintf("Unable to open upcall pipe %d\n", GetLastError()); + return GetLastError(); + } + + while(1) { + status = DeviceIoControl(pipe, IOCTL_NFS41_READ, NULL, 0, + outbuf, UPCALL_BUF_SIZE, (LPDWORD)&outbuf_len, NULL); + if (!status) { + eprintf("IOCTL_NFS41_READ failed %d\n", GetLastError()); + continue; + } + + status = upcall_parse(outbuf, (uint32_t)outbuf_len, &upcall); + if (status) { + upcall.status = status; + goto write_downcall; + } + + /* map username to uid/gid */ + status = map_user_to_ids(idmapper, &upcall.uid, &upcall.gid); + if (status) { + upcall.status = status; + goto write_downcall; + } + + if (upcall.opcode == NFS41_SHUTDOWN) { + printf("Shutting down..\n"); + exit(0); + } + + status = upcall_handle(&upcall); + +write_downcall: + dprintf(1, "writing downcall: xid=%lld opcode=%s status=%d " + "get_last_error=%d\n", upcall.xid, opcode2string(upcall.opcode), + upcall.status, upcall.last_error); + + upcall_marshall(&upcall, inbuf, (uint32_t)inbuf_len, (uint32_t*)&outbuf_len); + + dprintf(2, "making a downcall: outbuf_len %ld\n\n", outbuf_len); + status = DeviceIoControl(pipe, IOCTL_NFS41_WRITE, + inbuf, inbuf_len, NULL, 0, (LPDWORD)&outbuf_len, NULL); + if (!status) { + eprintf("IOCTL_NFS41_WRITE failed with %d xid=%lld opcode=%s\n", + GetLastError(), upcall.xid, opcode2string(upcall.opcode)); + upcall_cancel(&upcall); + } + if (upcall.status != NFSD_VERSION_MISMATCH) + upcall_cleanup(&upcall); + } + CloseHandle(pipe); + + return GetLastError(); +} + +#ifndef STANDALONE_NFSD +VOID ServiceStop() +{ + if (stop_event) + SetEvent(stop_event); +} +#endif + +typedef struct _nfsd_args { + bool_t ldap_enable; + int debug_level; +} nfsd_args; + +static bool_t check_for_files() +{ + FILE *fd; + + fd = fopen(FILE_NETCONFIG, "r"); + if (fd == NULL) { + fprintf(stderr,"nfsd() failed to open file '%s'\n", FILE_NETCONFIG); + return FALSE; + } + fclose(fd); + return TRUE; +} + +static void PrintUsage() +{ + fprintf(stderr, "Usage: nfsd.exe -d --noldap " + "--uid --gid\n"); +} +static bool_t parse_cmdlineargs(int argc, TCHAR *argv[], nfsd_args *out) +{ + int i; + + /* set defaults. */ + out->debug_level = 1; + out->ldap_enable = TRUE; + + /* parse command line */ + for (i = 1; i < argc; i++) { + if (argv[i][0] == TEXT('-')) { + if (_tcscmp(argv[i], TEXT("-h")) == 0) { /* help */ + PrintUsage(); + return FALSE; + } + else if (_tcscmp(argv[i], TEXT("-d")) == 0) { /* debug level */ + ++i; + if (i >= argc) { + fprintf(stderr, "Missing debug level value\n"); + PrintUsage(); + return FALSE; + } + out->debug_level = _ttoi(argv[i]); + } + else if (_tcscmp(argv[i], TEXT("--noldap")) == 0) { /* no LDAP */ + out->ldap_enable = FALSE; + } + else if (_tcscmp(argv[i], TEXT("--uid")) == 0) { /* no LDAP, setting default uid */ + ++i; + if (i >= argc) { + fprintf(stderr, "Missing uid value\n"); + PrintUsage(); + return FALSE; + } + default_uid = _ttoi(argv[i]); + if (!default_uid) { + fprintf(stderr, "Invalid (or missing) anonymous uid value of %d\n", + default_uid); + return FALSE; + } + } + else if (_tcscmp(argv[i], TEXT("--gid")) == 0) { /* no LDAP, setting default gid */ + ++i; + if (i >= argc) { + fprintf(stderr, "Missing gid value\n"); + PrintUsage(); + return FALSE; + } + default_gid = _ttoi(argv[i]); + } + else + fprintf(stderr, "Unrecognized option '%s', disregarding.\n", argv[i]); + } + } + fprintf(stdout, "parse_cmdlineargs: debug_level %d ldap is %d\n", + out->debug_level, out->ldap_enable); + return TRUE; +} + +static void print_getaddrinfo(struct addrinfo *ptr) +{ + char ipstringbuffer[46]; + DWORD ipbufferlength = 46; + + dprintf(1, "getaddrinfo response flags: 0x%x\n", ptr->ai_flags); + switch (ptr->ai_family) { + case AF_UNSPEC: dprintf(1, "Family: Unspecified\n"); break; + case AF_INET: + dprintf(1, "Family: AF_INET IPv4 address %s\n", + inet_ntoa(((struct sockaddr_in *)ptr->ai_addr)->sin_addr)); + break; + case AF_INET6: + if (WSAAddressToString((LPSOCKADDR)ptr->ai_addr, (DWORD)ptr->ai_addrlen, + NULL, ipstringbuffer, &ipbufferlength)) + dprintf(1, "WSAAddressToString failed with %u\n", WSAGetLastError()); + else + dprintf(1, "Family: AF_INET6 IPv6 address %s\n", ipstringbuffer); + break; + case AF_NETBIOS: dprintf(1, "AF_NETBIOS (NetBIOS)\n"); break; + default: dprintf(1, "Other %ld\n", ptr->ai_family); break; + } + dprintf(1, "Canonical name: %s\n", ptr->ai_canonname); +} + +static int getdomainname() +{ + int status = 0; + PFIXED_INFO net_info = NULL; + DWORD size = 0; + BOOLEAN flag = FALSE; + + status = GetNetworkParams(net_info, &size); + if (status != ERROR_BUFFER_OVERFLOW) { + eprintf("getdomainname: GetNetworkParams returned %d\n", status); + goto out; + } + net_info = calloc(1, size); + if (net_info == NULL) { + status = GetLastError(); + goto out; + } + status = GetNetworkParams(net_info, &size); + if (status) { + eprintf("getdomainname: GetNetworkParams returned %d\n", status); + goto out_free; + } + + if (net_info->DomainName[0] == '\0') { + struct addrinfo *result = NULL, *ptr = NULL, hints = { 0 }; + char hostname[NI_MAXHOST], servInfo[NI_MAXSERV]; + + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + status = getaddrinfo(net_info->HostName, NULL, &hints, &result); + if (status) { + status = WSAGetLastError(); + eprintf("getdomainname: getaddrinfo failed with %d\n", status); + goto out_free; + } + + for (ptr=result; ptr != NULL; ptr=ptr->ai_next) { + print_getaddrinfo(ptr); + + switch (ptr->ai_family) { + case AF_INET6: + case AF_INET: + status = getnameinfo((struct sockaddr *)ptr->ai_addr, + (socklen_t)ptr->ai_addrlen, hostname, NI_MAXHOST, + servInfo, NI_MAXSERV, NI_NAMEREQD); + if (status) + dprintf(1, "getnameinfo failed %d\n", WSAGetLastError()); + else { + size_t i, len = strlen(hostname); + char *p = hostname; + dprintf(1, "getdomainname: hostname %s %d\n", hostname, len); + for (i = 0; i < len; i++) + if (p[i] == '.') + break; + if (i == len) + break; + flag = TRUE; + memcpy(localdomain_name, &hostname[i+1], len-i); + dprintf(1, "getdomainname: domainname %s %d\n", + localdomain_name, strlen(localdomain_name)); + goto out_loop; + } + break; + default: + break; + } + } +out_loop: + if (!flag) { + status = ERROR_INTERNAL_ERROR; + eprintf("getdomainname: unable to get a domain name. " + "Set this machine's domain name:\n" + "System > ComputerName > Change > More > mydomain\n"); + } + freeaddrinfo(result); + } else { + dprintf(1, "domain name is %s\n", net_info->DomainName); + memcpy(localdomain_name, net_info->DomainName, + strlen(net_info->DomainName)); + localdomain_name[strlen(net_info->DomainName)] = '\0'; + } +out_free: + free(net_info); +out: + return status; +} + +#ifdef STANDALONE_NFSD +void __cdecl _tmain(int argc, TCHAR *argv[]) +#else +VOID ServiceStart(DWORD argc, LPTSTR *argv) +#endif +{ + DWORD status = 0, len; + // handle to our drivers + HANDLE pipe; + nfs41_process_thread tids[MAX_NUM_THREADS]; + nfs41_idmapper *idmapper = NULL; + int i; + nfsd_args cmd_args; + + if (!check_for_files()) + exit(0); + if (!parse_cmdlineargs(argc, argv, &cmd_args)) + exit(0); + set_debug_level(cmd_args.debug_level); + open_log_files(); + +#ifdef __REACTOS__ + /* Start the kernel part */ + { + HANDLE hSvcMan = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); + if (hSvcMan) + { + HANDLE hSvc = OpenService(hSvcMan, "nfs41_driver", SERVICE_ALL_ACCESS); + if (hSvc) + { + SERVICE_STATUS SvcSt; + QueryServiceStatus(hSvc, &SvcSt); + if (SvcSt.dwCurrentState != SERVICE_RUNNING) + { + if (StartService(hSvc, 0, NULL)) + { + dprintf(1, "NFS41 driver started\n"); + } + else + { + eprintf("Driver failed to start: %d\n", GetLastError()); + } + } + else + { + eprintf("Driver in state: %x\n", SvcSt.dwCurrentState); + } + + CloseServiceHandle(hSvc); + } + else + { + eprintf("Failed to open service: %d\n", GetLastError()); + } + + CloseServiceHandle(hSvcMan); + } + else + { + eprintf("Failed to open service manager: %d\n", GetLastError()); + } + } +#endif + +#ifdef _DEBUG + /* dump memory leaks to stderr on exit; this requires the debug heap, + /* available only when built in debug mode under visual studio -cbodley */ + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE); +#pragma warning (push) +#pragma warning (disable : 4306) /* conversion from 'int' to '_HFILE' of greater size */ + _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); +#pragma warning (pop) + dprintf(1, "debug mode. dumping memory leaks to stderr on exit.\n"); +#endif + /* acquire and store in global memory current dns domain name. + * needed for acls */ + if (getdomainname()) + exit(0); + + nfs41_server_list_init(); + + if (cmd_args.ldap_enable) { + status = nfs41_idmap_create(&idmapper); + if (status) { + eprintf("id mapping initialization failed with %d\n", status); + goto out_logs; + } + } + + NFS41D_VERSION = GetTickCount(); + dprintf(1, "NFS41 Daemon starting: version %d\n", NFS41D_VERSION); + + pipe = CreateFile(NFS41_USER_DEVICE_NAME_A, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, + 0, NULL); + if (pipe == INVALID_HANDLE_VALUE) + { + eprintf("Unable to open upcall pipe %d\n", GetLastError()); + goto out_idmap; + } + + dprintf(1, "starting nfs41 mini redirector\n"); + status = DeviceIoControl(pipe, IOCTL_NFS41_START, + &NFS41D_VERSION, sizeof(DWORD), NULL, 0, (LPDWORD)&len, NULL); + if (!status) { + eprintf("IOCTL_NFS41_START failed with %d\n", + GetLastError()); + goto out_pipe; + } + +#ifndef STANDALONE_NFSD + stop_event = CreateEvent(NULL, TRUE, FALSE, NULL); + if (stop_event == NULL) + goto out_pipe; +#endif + + for (i = 0; i < MAX_NUM_THREADS; i++) { + tids[i].handle = (HANDLE)_beginthreadex(NULL, 0, thread_main, + idmapper, 0, &tids[i].tid); + if (tids[i].handle == INVALID_HANDLE_VALUE) { + status = GetLastError(); + eprintf("_beginthreadex failed %d\n", status); + goto out_pipe; + } + } +#ifndef STANDALONE_NFSD + // report the status to the service control manager. + if (!ReportStatusToSCMgr(SERVICE_RUNNING, NO_ERROR, 0)) + goto out_pipe; + WaitForSingleObject(stop_event, INFINITE); +#else + //This can be changed to waiting on an array of handles and using waitformultipleobjects + dprintf(1, "Parent waiting for children threads\n"); + for (i = 0; i < MAX_NUM_THREADS; i++) + WaitForSingleObject(tids[i].handle, INFINITE ); +#endif + dprintf(1, "Parent woke up!!!!\n"); + +out_pipe: + CloseHandle(pipe); +out_idmap: + if (idmapper) nfs41_idmap_free(idmapper); +out_logs: +#ifndef STANDALONE_NFSD + close_log_files(); +#endif + return; +} diff --git a/reactos/base/services/nfsd/nfs41_ops.c b/reactos/base/services/nfsd/nfs41_ops.c new file mode 100644 index 00000000000..34545dc2c4b --- /dev/null +++ b/reactos/base/services/nfsd/nfs41_ops.c @@ -0,0 +1,2187 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include +#include +#include +#include +#include + +#include "nfs41_ops.h" +#include "nfs41_compound.h" +#include "nfs41_xdr.h" +#include "name_cache.h" +#include "delegation.h" +#include "daemon_debug.h" +#include "util.h" + +int nfs41_exchange_id( + IN nfs41_rpc_clnt *rpc, + IN client_owner4 *owner, + IN uint32_t flags_in, + OUT nfs41_exchange_id_res *res_out) +{ + int status = 0; + nfs41_compound compound; + nfs_argop4 argop; + nfs_resop4 resop; + nfs41_exchange_id_args ex_id; + + compound_init(&compound, &argop, &resop, "exchange_id"); + + compound_add_op(&compound, OP_EXCHANGE_ID, &ex_id, res_out); + ex_id.eia_clientowner = owner; + ex_id.eia_flags = flags_in; + ex_id.eia_state_protect.spa_how = SP4_NONE; + ex_id.eia_client_impl_id = NULL; + + res_out->server_owner.so_major_id_len = NFS4_OPAQUE_LIMIT; + res_out->server_scope_len = NFS4_OPAQUE_LIMIT; + + status = nfs41_send_compound(rpc, (char *)&compound.args, + (char *)&compound.res); + if (status) + goto out; + + compound_error(status = compound.res.status); +out: + return status; +} + +// AGLO: 10/07/2009 we might want lookup these values from the registry +static int set_fore_channel_attrs( + IN nfs41_rpc_clnt *rpc, + IN uint32_t max_req, + OUT nfs41_channel_attrs *attrs) +{ + attrs->ca_headerpadsize = 0; + attrs->ca_maxrequestsize = rpc->wsize; + attrs->ca_maxresponsesize = rpc->rsize; + attrs->ca_maxresponsesize_cached = NFS41_MAX_SERVER_CACHE; + attrs->ca_maxoperations = 0xffffffff; + attrs->ca_maxrequests = max_req; + attrs->ca_rdma_ird = NULL; + return 0; +} + +// AGLO: 10/07/2009 we might want lookup these values from the registry +static int set_back_channel_attrs( + IN nfs41_rpc_clnt *rpc, + IN uint32_t max_req, + OUT nfs41_channel_attrs *attrs) +{ + attrs->ca_headerpadsize = 0; + attrs->ca_maxrequestsize = rpc->wsize; + attrs->ca_maxresponsesize = rpc->rsize; + attrs->ca_maxresponsesize_cached = NFS41_MAX_SERVER_CACHE; + attrs->ca_maxoperations = 0xffffffff; + attrs->ca_maxrequests = max_req; + attrs->ca_rdma_ird = NULL; + return 0; +} + +int nfs41_create_session(nfs41_client *clnt, nfs41_session *session, bool_t try_recovery) +{ + int status = 0; + nfs41_compound compound; + nfs_argop4 argop; + nfs_resop4 resop; + nfs41_create_session_args req = { 0 }; + nfs41_create_session_res reply = { 0 }; + + compound_init(&compound, &argop, &resop, "create_session"); + + compound_add_op(&compound, OP_CREATE_SESSION, &req, &reply); + + AcquireSRWLockShared(&clnt->exid_lock); + req.csa_clientid = clnt->clnt_id; + req.csa_sequence = clnt->seq_id; + ReleaseSRWLockShared(&clnt->exid_lock); + req.csa_flags = session->flags; + req.csa_cb_program = NFS41_RPC_CBPROGRAM; + req.csa_cb_secparams[0].type = 0; /* AUTH_NONE */ + req.csa_cb_secparams[1].type = 1; /* AUTH_SYS */ + req.csa_cb_secparams[1].u.auth_sys.machinename = clnt->rpc->server_name; + req.csa_cb_secparams[1].u.auth_sys.stamp = (uint32_t)time(NULL); + + // ca_maxrequests should be gotten from the rpc layer + set_fore_channel_attrs(clnt->rpc, + NFS41_MAX_RPC_REQS, &req.csa_fore_chan_attrs); + set_back_channel_attrs(clnt->rpc, + 1, &req.csa_back_chan_attrs); + + reply.csr_sessionid = session->session_id; + reply.csr_fore_chan_attrs = &session->fore_chan_attrs; + reply.csr_back_chan_attrs = &session->back_chan_attrs; + + status = compound_encode_send_decode(session, &compound, try_recovery); + if (status) + goto out; + + if (compound_error(status = compound.res.status)) + goto out; + + print_hexbuf(1, (unsigned char *)"session id: ", session->session_id, NFS4_SESSIONID_SIZE); + // check that csa_sequence is same as csr_sequence + if (reply.csr_sequence != clnt->seq_id) { + eprintf("ERROR: CREATE_SESSION: csa_sequence %d != " + "csr_sequence %d\n", clnt->seq_id, reply.csr_sequence); + status = NFS4ERR_SEQ_MISORDERED; + goto out; + } else clnt->seq_id++; + + if (reply.csr_flags != req.csa_flags) { + eprintf("WARNING: requested session flags %x received %x\n", + req.csa_flags, reply.csr_flags); + if ((session->flags & CREATE_SESSION4_FLAG_CONN_BACK_CHAN) && + !(reply.csr_flags & CREATE_SESSION4_FLAG_CONN_BACK_CHAN)) + eprintf("WARNING: we asked to use this session for callbacks but " + "server refused\n"); + if ((session->flags & CREATE_SESSION4_FLAG_PERSIST) && + !(reply.csr_flags & CREATE_SESSION4_FLAG_PERSIST)) + eprintf("WARNING: we asked for persistent session but " + "server refused\n"); + session->flags = reply.csr_flags; + } + else + dprintf(1, "session flags %x\n", reply.csr_flags); + + dprintf(1, "session fore_chan_attrs:\n" + " %-32s%d\n %-32s%d\n %-32s%d\n %-32s%d\n %-32s%d\n %-32s%d\n", + "headerpadsize", session->fore_chan_attrs.ca_headerpadsize, + "maxrequestsize", session->fore_chan_attrs.ca_maxrequestsize, + "maxresponsesize", session->fore_chan_attrs.ca_maxresponsesize, + "maxresponsesize_cached", session->fore_chan_attrs.ca_maxresponsesize_cached, + "maxoperations", session->fore_chan_attrs.ca_maxoperations, + "maxrequests", session->fore_chan_attrs.ca_maxrequests); + dprintf(1, "client supports %d max rpc slots, but server has %d\n", + session->table.max_slots, session->fore_chan_attrs.ca_maxrequests); + /* use the server's ca_maxrequests unless it's bigger than our array */ + session->table.max_slots = min(session->table.max_slots, + session->fore_chan_attrs.ca_maxrequests); + status = 0; +out: + return status; +} + +enum nfsstat4 nfs41_bind_conn_to_session( + IN nfs41_rpc_clnt *rpc, + IN const unsigned char *sessionid, + IN enum channel_dir_from_client4 dir) +{ + int status; + nfs41_compound compound; + nfs_argop4 argop; + nfs_resop4 resop; + nfs41_bind_conn_to_session_args bind_args = { 0 }; + nfs41_bind_conn_to_session_res bind_res = { 0 }; + + compound_init(&compound, &argop, &resop, "bind_conn_to_session"); + + compound_add_op(&compound, OP_BIND_CONN_TO_SESSION, &bind_args, &bind_res); + bind_args.sessionid = (unsigned char *)sessionid; + bind_args.dir = dir; + + status = nfs41_send_compound(rpc, + (char*)&compound.args, (char*)&compound.res); + if (status) + goto out; + + compound_error(status = compound.res.status); + +out: + return status; +} + +int nfs41_destroy_session( + IN nfs41_session *session) +{ + int status; + nfs41_compound compound; + nfs_argop4 argop; + nfs_resop4 resop; + nfs41_destroy_session_args ds_args; + nfs41_destroy_session_res ds_res; + + compound_init(&compound, &argop, &resop, "destroy_session"); + + compound_add_op(&compound, OP_DESTROY_SESSION, &ds_args, &ds_res); + ds_args.dsa_sessionid = session->session_id; + + /* don't attempt to recover from BADSESSION/STALE_CLIENTID */ + status = compound_encode_send_decode(session, &compound, FALSE); + if (status) + goto out; + + status = compound.res.status; + if (status) + eprintf("%s failed with status %d.\n", + nfs_opnum_to_string(OP_DESTROY_SESSION), status); +out: + return status; +} + +int nfs41_destroy_clientid( + IN nfs41_rpc_clnt *rpc, + IN uint64_t clientid) +{ + int status; + nfs41_compound compound; + nfs_argop4 argops; + nfs_resop4 resops; + nfs41_destroy_clientid_args dc_args; + nfs41_destroy_clientid_res dc_res; + + compound_init(&compound, &argops, &resops, "destroy_clientid"); + + compound_add_op(&compound, OP_DESTROY_CLIENTID, &dc_args, &dc_res); + dc_args.dca_clientid = clientid; + + status = nfs41_send_compound(rpc, (char *)&compound.args, + (char *)&compound.res); + if (status) + goto out; + + compound_error(status = compound.res.status); +out: + return status; +} + +enum nfsstat4 nfs41_reclaim_complete( + IN nfs41_session *session) +{ + enum nfsstat4 status = NFS4_OK; + nfs41_compound compound; + nfs_argop4 argops[2]; + nfs_resop4 resops[2]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + nfs41_reclaim_complete_res reclaim_res; + + compound_init(&compound, argops, resops, "reclaim_complete"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 0); + + compound_add_op(&compound, OP_RECLAIM_COMPLETE, NULL, &reclaim_res); + + /* don't attempt to recover from BADSESSION */ + status = compound_encode_send_decode(session, &compound, FALSE); + if (status) + goto out; + + compound_error(status = compound.res.status); +out: + return status; +} + +static void open_delegation_return( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN open_delegation4 *delegation, + IN bool_t try_recovery) +{ + stateid_arg stateid; + int status; + + if (delegation->type == OPEN_DELEGATE_NONE || + delegation->type == OPEN_DELEGATE_NONE_EXT) + return; + + /* return the delegation */ + stateid.open = NULL; + stateid.delegation = NULL; + stateid.type = STATEID_DELEG_FILE; + memcpy(&stateid.stateid, &delegation->stateid, sizeof(stateid4)); + + status = nfs41_delegreturn(session, file, &stateid, try_recovery); + + /* clear the delegation type returned by nfs41_open() */ + delegation->type = OPEN_DELEGATE_NONE; +} + +static void open_update_cache( + IN nfs41_session *session, + IN nfs41_path_fh *parent, + IN nfs41_path_fh *file, + IN bool_t try_recovery, + IN open_delegation4 *delegation, + IN bool_t already_delegated, + IN change_info4 *changeinfo, + IN nfs41_getattr_res *dir_attrs, + IN nfs41_getattr_res *file_attrs) +{ + struct nfs41_name_cache *cache = session_name_cache(session); + uint32_t status; + + /* update the attributes of the parent directory */ + memcpy(&dir_attrs->info->attrmask, &dir_attrs->obj_attributes.attrmask, + sizeof(bitmap4)); + nfs41_attr_cache_update(cache, parent->fh.fileid, dir_attrs->info); + + /* add the file handle and attributes to the name cache */ + memcpy(&file_attrs->info->attrmask, &file_attrs->obj_attributes.attrmask, + sizeof(bitmap4)); +retry_cache_insert: + AcquireSRWLockShared(&file->path->lock); + status = nfs41_name_cache_insert(cache, file->path->path, &file->name, + &file->fh, file_attrs->info, changeinfo, + already_delegated ? OPEN_DELEGATE_NONE : delegation->type); + ReleaseSRWLockShared(&file->path->lock); + + if (status == ERROR_TOO_MANY_OPEN_FILES) { + /* the cache won't accept any more delegations; ask the client to + * return a delegation to free up a slot in the attribute cache */ + status = nfs41_client_delegation_return_lru(session->client); + if (status == NFS4_OK) + goto retry_cache_insert; + } + + if (status && delegation->type != OPEN_DELEGATE_NONE) { + /* if we can't make room in the cache, return this + * delegation immediately to free resources on the server */ + open_delegation_return(session, file, delegation, try_recovery); + goto retry_cache_insert; + } +} + +int nfs41_open( + IN nfs41_session *session, + IN nfs41_path_fh *parent, + IN nfs41_path_fh *file, + IN state_owner4 *owner, + IN open_claim4 *claim, + IN uint32_t allow, + IN uint32_t deny, + IN uint32_t create, + IN uint32_t how_mode, + IN OPTIONAL nfs41_file_info *createattrs, + IN bool_t try_recovery, + OUT stateid4 *stateid, + OUT open_delegation4 *delegation, + OUT OPTIONAL nfs41_file_info *info) +{ + int status; + nfs41_compound compound; + nfs_argop4 argops[8]; + nfs_resop4 resops[8]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + nfs41_putfh_args putfh_args[2]; + nfs41_putfh_res putfh_res[2]; + nfs41_op_open_args open_args; + nfs41_op_open_res open_res; + nfs41_getfh_res getfh_res; + bitmap4 attr_request; + nfs41_getattr_args getattr_args; + nfs41_getattr_res getattr_res, pgetattr_res; + nfs41_savefh_res savefh_res; + nfs41_restorefh_res restorefh_res; + nfs41_file_info tmp_info, dir_info; + bool_t current_fh_is_dir; + bool_t already_delegated = delegation->type == OPEN_DELEGATE_READ + || delegation->type == OPEN_DELEGATE_WRITE; + + /* depending on the claim type, OPEN expects CURRENT_FH set + * to either the parent directory, or to the file itself */ + switch (claim->claim) { + case CLAIM_NULL: + case CLAIM_DELEGATE_CUR: + case CLAIM_DELEGATE_PREV: + /* CURRENT_FH: directory */ + current_fh_is_dir = TRUE; + /* SEQUENCE; PUTFH(dir); SAVEFH; OPEN; + * GETFH(file); GETATTR(file); RESTOREFH(dir); GETATTR */ + nfs41_superblock_getattr_mask(parent->fh.superblock, &attr_request); + break; + case CLAIM_PREVIOUS: + case CLAIM_FH: + case CLAIM_DELEG_CUR_FH: + case CLAIM_DELEG_PREV_FH: + default: + /* CURRENT_FH: file being opened */ + current_fh_is_dir = FALSE; + /* SEQUENCE; PUTFH(file); OPEN; GETATTR(file); PUTFH(dir); GETATTR */ + nfs41_superblock_getattr_mask(file->fh.superblock, &attr_request); + break; + } + + if (info == NULL) + info = &tmp_info; + + attr_request.arr[0] |= FATTR4_WORD0_FSID; + + compound_init(&compound, argops, resops, "open"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 1); + + if (current_fh_is_dir) { + /* CURRENT_FH: directory */ + compound_add_op(&compound, OP_PUTFH, &putfh_args[0], &putfh_res[0]); + putfh_args[0].file = parent; + putfh_args[0].in_recovery = 0; + + compound_add_op(&compound, OP_SAVEFH, NULL, &savefh_res); + } else { + /* CURRENT_FH: file being opened */ + compound_add_op(&compound, OP_PUTFH, &putfh_args[0], &putfh_res[0]); + putfh_args[0].file = file; + putfh_args[0].in_recovery = 0; + } + + compound_add_op(&compound, OP_OPEN, &open_args, &open_res); + open_args.seqid = 0; +#ifdef DISABLE_FILE_DELEGATIONS + open_args.share_access = allow | OPEN4_SHARE_ACCESS_WANT_NO_DELEG; +#else + open_args.share_access = allow; +#endif + open_args.share_deny = deny; + open_args.owner = owner; + open_args.openhow.opentype = create; + open_args.openhow.how.mode = how_mode; + open_args.openhow.how.createattrs = createattrs; + if (how_mode == EXCLUSIVE4_1) { + DWORD tid = GetCurrentThreadId(); + time((time_t*)open_args.openhow.how.createverf); + memcpy(open_args.openhow.how.createverf+4, &tid, sizeof(tid)); + /* mask unsupported attributes */ + nfs41_superblock_supported_attrs_exclcreat( + parent->fh.superblock, &createattrs->attrmask); + } else if (createattrs) { + /* mask unsupported attributes */ + nfs41_superblock_supported_attrs( + parent->fh.superblock, &createattrs->attrmask); + } + open_args.claim = claim; + open_res.resok4.stateid = stateid; + open_res.resok4.delegation = delegation; + + if (current_fh_is_dir) { + compound_add_op(&compound, OP_GETFH, NULL, &getfh_res); + getfh_res.fh = &file->fh; + } + + compound_add_op(&compound, OP_GETATTR, &getattr_args, &getattr_res); + getattr_args.attr_request = &attr_request; + getattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT; + getattr_res.info = info; + + if (current_fh_is_dir) { + compound_add_op(&compound, OP_RESTOREFH, NULL, &restorefh_res); + } else { + compound_add_op(&compound, OP_PUTFH, &putfh_args[1], &putfh_res[1]); + putfh_args[1].file = parent; + putfh_args[1].in_recovery = 0; + } + + compound_add_op(&compound, OP_GETATTR, &getattr_args, &pgetattr_res); + getattr_args.attr_request = &attr_request; + pgetattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT; + pgetattr_res.info = &dir_info; + + status = compound_encode_send_decode(session, &compound, try_recovery); + if (status) + goto out; + + if (compound_error(status = compound.res.status)) + goto out; + + if (dir_info.type == NF4ATTRDIR) { + file->fh.superblock = parent->fh.superblock; + goto out; + } + + /* fill in the file handle's fileid and superblock */ + file->fh.fileid = info->fileid; + status = nfs41_superblock_for_fh(session, &info->fsid, &parent->fh, file); + if (status) + goto out; + + if (create == OPEN4_CREATE) + nfs41_superblock_space_changed(file->fh.superblock); + + /* update the name/attr cache with the results */ + open_update_cache(session, parent, file, try_recovery, delegation, + already_delegated, &open_res.resok4.cinfo, &pgetattr_res, &getattr_res); +out: + return status; +} + +int nfs41_create( + IN nfs41_session *session, + IN uint32_t type, + IN nfs41_file_info *createattrs, + IN OPTIONAL const char *symlink, + IN nfs41_path_fh *parent, + OUT nfs41_path_fh *file, + OUT nfs41_file_info *info) +{ + int status; + nfs41_compound compound; + nfs_argop4 argops[8]; + nfs_resop4 resops[8]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + nfs41_putfh_args putfh_args; + nfs41_putfh_res putfh_res; + nfs41_create_args create_args; + nfs41_create_res create_res; + nfs41_getfh_res getfh_res; + nfs41_getattr_args getattr_args; + nfs41_getattr_res getattr_res, pgetattr_res; + bitmap4 attr_request; + nfs41_file_info dir_info; + nfs41_savefh_res savefh_res; + nfs41_restorefh_res restorefh_res; + + nfs41_superblock_getattr_mask(parent->fh.superblock, &attr_request); + + compound_init(&compound, argops, resops, "create"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 1); + + compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res); + putfh_args.file = parent; + putfh_args.in_recovery = 0; + + compound_add_op(&compound, OP_SAVEFH, NULL, &savefh_res); + + compound_add_op(&compound, OP_CREATE, &create_args, &create_res); + create_args.objtype.type = type; + if (type == NF4LNK) { + create_args.objtype.u.lnk.linkdata = symlink; + create_args.objtype.u.lnk.linkdata_len = (uint32_t)strlen(symlink); + } + create_args.name = &file->name; + create_args.createattrs = createattrs; + nfs41_superblock_supported_attrs( + parent->fh.superblock, &createattrs->attrmask); + + compound_add_op(&compound, OP_GETFH, NULL, &getfh_res); + getfh_res.fh = &file->fh; + + compound_add_op(&compound, OP_GETATTR, &getattr_args, &getattr_res); + getattr_args.attr_request = &attr_request; + getattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT; + getattr_res.info = info; + + compound_add_op(&compound, OP_RESTOREFH, NULL, &restorefh_res); + + compound_add_op(&compound, OP_GETATTR, &getattr_args, &pgetattr_res); + getattr_args.attr_request = &attr_request; + pgetattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT; + pgetattr_res.info = &dir_info; + + status = compound_encode_send_decode(session, &compound, TRUE); + if (status) + goto out; + + if (compound_error(status = compound.res.status)) + goto out; + + /* fill in the file handle's fileid and superblock */ + file->fh.fileid = info->fileid; + file->fh.superblock = parent->fh.superblock; + + /* update the attributes of the parent directory */ + memcpy(&dir_info.attrmask, &pgetattr_res.obj_attributes.attrmask, + sizeof(bitmap4)); + nfs41_attr_cache_update(session_name_cache(session), + parent->fh.fileid, &dir_info); + + /* add the new file handle and attributes to the name cache */ + memcpy(&info->attrmask, &getattr_res.obj_attributes.attrmask, + sizeof(bitmap4)); + AcquireSRWLockShared(&file->path->lock); + nfs41_name_cache_insert(session_name_cache(session), + file->path->path, &file->name, &file->fh, + info, &create_res.cinfo, OPEN_DELEGATE_NONE); + ReleaseSRWLockShared(&file->path->lock); + + nfs41_superblock_space_changed(file->fh.superblock); +out: + return status; +} + +int nfs41_close( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN stateid_arg *stateid) +{ + int status; + nfs41_compound compound; + nfs_argop4 argops[4]; + nfs_resop4 resops[4]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + nfs41_putfh_args putfh_args; + nfs41_putfh_res putfh_res; + nfs41_op_close_args close_args; + nfs41_op_close_res close_res; + nfs41_getattr_args getattr_args; + nfs41_getattr_res getattr_res; + bitmap4 attr_request; + nfs41_file_info info; + + nfs41_superblock_getattr_mask(file->fh.superblock, &attr_request); + + compound_init(&compound, argops, resops, "close"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 1); + + compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res); + putfh_args.file = file; + putfh_args.in_recovery = 0; + + compound_add_op(&compound, OP_CLOSE, &close_args, &close_res); + close_args.stateid = stateid; + + compound_add_op(&compound, OP_GETATTR, &getattr_args, &getattr_res); + getattr_args.attr_request = &attr_request; + getattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT; + getattr_res.info = &info; + + status = compound_encode_send_decode(session, &compound, TRUE); + if (status) + goto out; + + if (compound_error(status = compound.res.status)) + goto out; + + if (info.type == NF4NAMEDATTR) + goto out; + + /* update the attributes of the parent directory */ + memcpy(&info.attrmask, &getattr_res.obj_attributes.attrmask, + sizeof(bitmap4)); + nfs41_attr_cache_update(session_name_cache(session), + file->fh.fileid, &info); +out: + return status; +} + +int nfs41_write( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN stateid_arg *stateid, + IN unsigned char *data, + IN uint32_t data_len, + IN uint64_t offset, + IN enum stable_how4 stable, + OUT uint32_t *bytes_written, + OUT nfs41_write_verf *verf, + OUT nfs41_file_info *cinfo) +{ + int status; + nfs41_compound compound; + nfs_argop4 argops[4]; + nfs_resop4 resops[4]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + nfs41_putfh_args putfh_args; + nfs41_putfh_res putfh_res; + nfs41_write_args write_args; + nfs41_write_res write_res; + nfs41_getattr_args getattr_args; + nfs41_getattr_res getattr_res = {0}; + bitmap4 attr_request; + nfs41_file_info info = { 0 }, *pinfo; + + nfs41_superblock_getattr_mask(file->fh.superblock, &attr_request); + + compound_init(&compound, argops, resops, + stateid->stateid.seqid == 0 ? "ds write" : "write"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 0); + + compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res); + putfh_args.file = file; + putfh_args.in_recovery = 0; + + compound_add_op(&compound, OP_WRITE, &write_args, &write_res); + write_args.stateid = stateid; + write_args.offset = offset; + write_args.stable = stable; + write_args.data_len = data_len; + write_args.data = data; + write_res.resok4.verf = verf; + + if (cinfo) pinfo = cinfo; + else pinfo = &info; + + if (stable != UNSTABLE4) { + /* if the write is stable, we can't rely on COMMIT to update + * the attribute cache, so we do the GETATTR here */ + compound_add_op(&compound, OP_GETATTR, &getattr_args, &getattr_res); + getattr_args.attr_request = &attr_request; + getattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT; + getattr_res.info = pinfo; + } + + status = compound_encode_send_decode(session, &compound, TRUE); + if (status) + goto out; + + if (compound_error(status = compound.res.status)) + goto out; + + if (stable != UNSTABLE4 && pinfo->type != NF4NAMEDATTR) { + /* update the attribute cache */ + memcpy(&pinfo->attrmask, &getattr_res.obj_attributes.attrmask, + sizeof(bitmap4)); + nfs41_attr_cache_update(session_name_cache(session), + file->fh.fileid, pinfo); + } + + *bytes_written = write_res.resok4.count; + + /* we shouldn't ever see this, but a buggy server could + * send us into an infinite loop. return NFS4ERR_IO */ + if (!write_res.resok4.count) { + status = NFS4ERR_IO; + eprintf("WRITE succeeded with count=0; returning %s\n", + nfs_error_string(status)); + } + + nfs41_superblock_space_changed(file->fh.superblock); +out: + return status; +} + +int nfs41_read( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN stateid_arg *stateid, + IN uint64_t offset, + IN uint32_t count, + OUT unsigned char *data_out, + OUT uint32_t *data_len_out, + OUT bool_t *eof_out) +{ + int status; + nfs41_compound compound; + nfs_argop4 argops[4]; + nfs_resop4 resops[4]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + nfs41_putfh_args putfh_args; + nfs41_putfh_res putfh_res; + nfs41_read_args read_args; + nfs41_read_res read_res; + + compound_init(&compound, argops, resops, + stateid->stateid.seqid == 0 ? "ds read" : "read"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 0); + + compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res); + putfh_args.file = file; + putfh_args.in_recovery = 0; + + compound_add_op(&compound, OP_READ, &read_args, &read_res); + read_args.stateid = stateid; + read_args.offset = offset; + read_args.count = count; + read_res.resok4.data_len = count; + read_res.resok4.data = data_out; + + status = compound_encode_send_decode(session, &compound, TRUE); + if (status) + goto out; + + if (compound_error(status = compound.res.status)) + goto out; + + *data_len_out = read_res.resok4.data_len; + *eof_out = read_res.resok4.eof; + + /* we shouldn't ever see this, but a buggy server could + * send us into an infinite loop. return NFS4ERR_IO */ + if (!read_res.resok4.data_len && !read_res.resok4.eof) { + status = NFS4ERR_IO; + eprintf("READ succeeded with len=0 and eof=0; returning %s\n", + nfs_error_string(status)); + } +out: + return status; +} + +int nfs41_commit( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN uint64_t offset, + IN uint32_t count, + IN bool_t do_getattr, + OUT nfs41_write_verf *verf, + OUT nfs41_file_info *cinfo) +{ + int status; + nfs41_compound compound; + nfs_argop4 argops[4]; + nfs_resop4 resops[4]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + nfs41_putfh_args putfh_args; + nfs41_putfh_res putfh_res; + nfs41_commit_args commit_args; + nfs41_commit_res commit_res; + nfs41_getattr_args getattr_args; + nfs41_getattr_res getattr_res = {0}; + bitmap4 attr_request; + nfs41_file_info info, *pinfo; + + compound_init(&compound, argops, resops, + do_getattr ? "commit" : "ds commit"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 1); + + compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res); + putfh_args.file = file; + putfh_args.in_recovery = 0; + + compound_add_op(&compound, OP_COMMIT, &commit_args, &commit_res); + commit_args.offset = offset; + commit_args.count = count; + commit_res.verf = verf; + + /* send a GETATTR request to update the attribute cache, + * but not if we're talking to a data server! */ + if (cinfo) pinfo = cinfo; + else pinfo = &info; + if (do_getattr) { + nfs41_superblock_getattr_mask(file->fh.superblock, &attr_request); + + compound_add_op(&compound, OP_GETATTR, &getattr_args, &getattr_res); + getattr_args.attr_request = &attr_request; + getattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT; + getattr_res.info = pinfo; + } + + status = compound_encode_send_decode(session, &compound, TRUE); + if (status) + goto out; + + if (compound_error(status = compound.res.status)) + goto out; + + if (do_getattr) { + /* update the attribute cache */ + memcpy(&pinfo->attrmask, &getattr_res.obj_attributes.attrmask, + sizeof(bitmap4)); + nfs41_attr_cache_update(session_name_cache(session), + file->fh.fileid, pinfo); + } + nfs41_superblock_space_changed(file->fh.superblock); +out: + return status; +} + +int nfs41_lock( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN state_owner4 *owner, + IN uint32_t type, + IN uint64_t offset, + IN uint64_t length, + IN bool_t reclaim, + IN bool_t try_recovery, + IN OUT stateid_arg *stateid) +{ + int status; + nfs41_compound compound; + nfs_argop4 argops[3]; + nfs_resop4 resops[3]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + nfs41_putfh_args putfh_args; + nfs41_putfh_res putfh_res; + nfs41_lock_args lock_args; + nfs41_lock_res lock_res; + + compound_init(&compound, argops, resops, "lock"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 0); + + compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res); + putfh_args.file = file; + putfh_args.in_recovery = 0; + + compound_add_op(&compound, OP_LOCK, &lock_args, &lock_res); + lock_args.locktype = type; + lock_args.reclaim = reclaim; + lock_args.offset = offset; + lock_args.length = length; + if (stateid->type == STATEID_LOCK) { + lock_args.locker.new_lock_owner = 0; + lock_args.locker.u.lock_owner.lock_stateid = stateid; + lock_args.locker.u.lock_owner.lock_seqid = 0; /* ignored */ + } else { + lock_args.locker.new_lock_owner = 1; + lock_args.locker.u.open_owner.open_seqid = 0; /* ignored */ + lock_args.locker.u.open_owner.open_stateid = stateid; + lock_args.locker.u.open_owner.lock_seqid = 0; /* ignored */ + lock_args.locker.u.open_owner.lock_owner = owner; + } + lock_res.u.resok4.lock_stateid = &stateid->stateid; + lock_res.u.denied.owner.owner_len = NFS4_OPAQUE_LIMIT; + + status = compound_encode_send_decode(session, &compound, try_recovery); + if (status) + goto out; + + if (compound_error(status = compound.res.status)) + goto out; + + stateid->type = STATEID_LOCK; /* returning a lock stateid */ +out: + return status; +} + +int nfs41_unlock( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN uint64_t offset, + IN uint64_t length, + IN OUT stateid_arg *stateid) +{ + int status; + nfs41_compound compound; + nfs_argop4 argops[3]; + nfs_resop4 resops[3]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + nfs41_putfh_args putfh_args; + nfs41_putfh_res putfh_res; + nfs41_locku_args locku_args; + nfs41_locku_res locku_res; + + compound_init(&compound, argops, resops, "unlock"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 0); + + compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res); + putfh_args.file = file; + putfh_args.in_recovery = 0; + + compound_add_op(&compound, OP_LOCKU, &locku_args, &locku_res); + /* 18.12.3: the server MUST accept any legal value for locktype */ + locku_args.locktype = READ_LT; + locku_args.offset = offset; + locku_args.length = length; + locku_args.lock_stateid = stateid; + locku_res.lock_stateid = &stateid->stateid; + + status = compound_encode_send_decode(session, &compound, TRUE); + if (status) + goto out; + + compound_error(status = compound.res.status); +out: + return status; +} + +int nfs41_readdir( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN bitmap4 *attr_request, + IN nfs41_readdir_cookie *cookie, + OUT unsigned char *entries, + IN OUT uint32_t *entries_len, + OUT bool_t *eof_out) +{ + int status; + nfs41_compound compound; + nfs_argop4 argops[3]; + nfs_resop4 resops[3]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + nfs41_putfh_args putfh_args; + nfs41_putfh_res putfh_res; + nfs41_readdir_args readdir_args; + nfs41_readdir_res readdir_res; + + compound_init(&compound, argops, resops, "readdir"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 0); + + compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res); + putfh_args.file = file; + putfh_args.in_recovery = 0; + + compound_add_op(&compound, OP_READDIR, &readdir_args, &readdir_res); + readdir_args.cookie.cookie = cookie->cookie; + memcpy(readdir_args.cookie.verf, cookie->verf, NFS4_VERIFIER_SIZE); + readdir_args.dircount = *entries_len; + readdir_args.maxcount = *entries_len + sizeof(nfs41_readdir_res); + readdir_args.attr_request = attr_request; + readdir_res.reply.entries_len = *entries_len; + readdir_res.reply.entries = entries; + ZeroMemory(entries, readdir_args.dircount); + + status = compound_encode_send_decode(session, &compound, TRUE); + if (status) + goto out; + + if (compound_error(status = compound.res.status)) + goto out; + + *entries_len = readdir_res.reply.entries_len; + *eof_out = readdir_res.reply.eof; + memcpy(cookie->verf, readdir_res.cookieverf, NFS4_VERIFIER_SIZE); +out: + return status; +} + +int nfs41_getattr( + IN nfs41_session *session, + IN OPTIONAL nfs41_path_fh *file, + IN bitmap4 *attr_request, + OUT nfs41_file_info *info) +{ + int status; + nfs41_compound compound; + nfs_argop4 argops[3]; + nfs_resop4 resops[3]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + nfs41_putfh_args putfh_args; + nfs41_putfh_res putfh_res; + nfs41_getattr_args getattr_args; + nfs41_getattr_res getattr_res; + + compound_init(&compound, argops, resops, "getattr"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 0); + + if (file) { + compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res); + putfh_args.file = file; + putfh_args.in_recovery = 0; + } else { + compound_add_op(&compound, OP_PUTROOTFH, NULL, &putfh_res); + } + + compound_add_op(&compound, OP_GETATTR, &getattr_args, &getattr_res); + getattr_args.attr_request = attr_request; + getattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT; + getattr_res.info = info; + + status = compound_encode_send_decode(session, &compound, TRUE); + if (status) + goto out; + + if (compound_error(status = compound.res.status)) + goto out; + + if (file) { + /* update the name cache with whatever attributes we got */ + memcpy(&info->attrmask, &getattr_res.obj_attributes.attrmask, + sizeof(bitmap4)); + nfs41_attr_cache_update(session_name_cache(session), + file->fh.fileid, info); + } +out: + return status; +} + +int nfs41_superblock_getattr( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN bitmap4 *attr_request, + OUT nfs41_file_info *info, + OUT bool_t *supports_named_attrs) +{ + int status; + nfs41_compound compound; + nfs_argop4 argops[4]; + nfs_resop4 resops[4]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + nfs41_putfh_args putfh_args; + nfs41_putfh_res putfh_res; + nfs41_getattr_args getattr_args; + nfs41_getattr_res getattr_res; + nfs41_openattr_args openattr_args; + nfs41_openattr_res openattr_res; + + compound_init(&compound, argops, resops, "getfsattr"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 0); + + compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res); + putfh_args.file = file; + putfh_args.in_recovery = 0; + + compound_add_op(&compound, OP_GETATTR, &getattr_args, &getattr_res); + getattr_args.attr_request = attr_request; + getattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT; + getattr_res.info = info; + + compound_add_op(&compound, OP_OPENATTR, &openattr_args, &openattr_res); + openattr_args.createdir = 0; + + status = compound_encode_send_decode(session, &compound, TRUE); + if (status) + goto out; + + status = sequence_res.sr_status; + if (status) goto out; + status = putfh_res.status; + if (status) goto out; + status = getattr_res.status; + if (status) goto out; + + switch (status = openattr_res.status) { + case NFS4ERR_NOTSUPP: + *supports_named_attrs = 0; + status = NFS4_OK; + break; + + case NFS4ERR_NOENT: + case NFS4_OK: + *supports_named_attrs = 1; + status = NFS4_OK; + break; + } +out: + return status; +} + +int nfs41_remove( + IN nfs41_session *session, + IN nfs41_path_fh *parent, + IN const nfs41_component *target, + IN uint64_t fileid) +{ + int status; + nfs41_compound compound; + nfs_argop4 argops[4]; + nfs_resop4 resops[4]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + nfs41_putfh_args putfh_args; + nfs41_putfh_res putfh_res; + nfs41_remove_args remove_args; + nfs41_remove_res remove_res; + nfs41_getattr_args getattr_args; + nfs41_getattr_res getattr_res; + bitmap4 attr_request; + nfs41_file_info info; + + nfs41_superblock_getattr_mask(parent->fh.superblock, &attr_request); + + compound_init(&compound, argops, resops, "remove"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 1); + + compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res); + putfh_args.file = parent; + putfh_args.in_recovery = 0; + + compound_add_op(&compound, OP_REMOVE, &remove_args, &remove_res); + remove_args.target = target; + + compound_add_op(&compound, OP_GETATTR, &getattr_args, &getattr_res); + getattr_args.attr_request = &attr_request; + getattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT; + getattr_res.info = &info; + + status = compound_encode_send_decode(session, &compound, TRUE); + if (status) + goto out; + + if (compound_error(status = compound.res.status)) + goto out; + + if (info.type == NF4ATTRDIR) + goto out; + + /* update the attributes of the parent directory */ + memcpy(&info.attrmask, &getattr_res.obj_attributes.attrmask, + sizeof(bitmap4)); + nfs41_attr_cache_update(session_name_cache(session), + parent->fh.fileid, &info); + + /* remove the target file from the cache */ + AcquireSRWLockShared(&parent->path->lock); + nfs41_name_cache_remove(session_name_cache(session), + parent->path->path, target, fileid, &remove_res.cinfo); + ReleaseSRWLockShared(&parent->path->lock); + + nfs41_superblock_space_changed(parent->fh.superblock); +out: + return status; +} + +int nfs41_rename( + IN nfs41_session *session, + IN nfs41_path_fh *src_dir, + IN const nfs41_component *src_name, + IN nfs41_path_fh *dst_dir, + IN const nfs41_component *dst_name) +{ + int status; + nfs41_compound compound; + nfs_argop4 argops[8]; + nfs_resop4 resops[8]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + nfs41_putfh_args src_putfh_args; + nfs41_putfh_res src_putfh_res; + nfs41_savefh_res savefh_res; + nfs41_putfh_args dst_putfh_args; + nfs41_putfh_res dst_putfh_res; + nfs41_rename_args rename_args; + nfs41_rename_res rename_res; + nfs41_getattr_args getattr_args; + nfs41_getattr_res src_getattr_res, dst_getattr_res; + nfs41_file_info src_info, dst_info; + bitmap4 attr_request; + nfs41_restorefh_res restorefh_res; + + nfs41_superblock_getattr_mask(src_dir->fh.superblock, &attr_request); + + compound_init(&compound, argops, resops, "rename"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 1); + + compound_add_op(&compound, OP_PUTFH, &src_putfh_args, &src_putfh_res); + src_putfh_args.file = src_dir; + src_putfh_args.in_recovery = 0; + + compound_add_op(&compound, OP_SAVEFH, NULL, &savefh_res); + + compound_add_op(&compound, OP_PUTFH, &dst_putfh_args, &dst_putfh_res); + dst_putfh_args.file = dst_dir; + dst_putfh_args.in_recovery = 0; + + compound_add_op(&compound, OP_RENAME, &rename_args, &rename_res); + rename_args.oldname = src_name; + rename_args.newname = dst_name; + + compound_add_op(&compound, OP_GETATTR, &getattr_args, &dst_getattr_res); + getattr_args.attr_request = &attr_request; + dst_getattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT; + dst_getattr_res.info = &dst_info; + + compound_add_op(&compound, OP_RESTOREFH, NULL, &restorefh_res); + + compound_add_op(&compound, OP_GETATTR, &getattr_args, &src_getattr_res); + src_getattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT; + src_getattr_res.info = &src_info; + + status = compound_encode_send_decode(session, &compound, TRUE); + if (status) + goto out; + + if (compound_error(status = compound.res.status)) + goto out; + + /* update the attributes of the source directory */ + memcpy(&src_info.attrmask, &src_getattr_res.obj_attributes.attrmask, + sizeof(bitmap4)); + nfs41_attr_cache_update(session_name_cache(session), + src_dir->fh.fileid, &src_info); + + /* update the attributes of the destination directory */ + memcpy(&dst_info.attrmask, &dst_getattr_res.obj_attributes.attrmask, + sizeof(bitmap4)); + nfs41_attr_cache_update(session_name_cache(session), + dst_dir->fh.fileid, &dst_info); + + if (src_dir->path == dst_dir->path) { + /* source and destination are the same, only lock it once */ + AcquireSRWLockShared(&src_dir->path->lock); + } else if (src_dir->path < dst_dir->path) { + /* lock the lowest memory address first */ + AcquireSRWLockShared(&src_dir->path->lock); + AcquireSRWLockShared(&dst_dir->path->lock); + } else { + AcquireSRWLockShared(&dst_dir->path->lock); + AcquireSRWLockShared(&src_dir->path->lock); + } + + /* move/rename the target file's name cache entry */ + nfs41_name_cache_rename(session_name_cache(session), + src_dir->path->path, src_name, &rename_res.source_cinfo, + dst_dir->path->path, dst_name, &rename_res.target_cinfo); + + if (src_dir->path == dst_dir->path) { + ReleaseSRWLockShared(&src_dir->path->lock); + } else { + ReleaseSRWLockShared(&src_dir->path->lock); + ReleaseSRWLockShared(&dst_dir->path->lock); + } +out: + return status; +} + +int nfs41_setattr( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN stateid_arg *stateid, + IN nfs41_file_info *info) +{ + int status; + nfs41_compound compound; + nfs_argop4 argops[4]; + nfs_resop4 resops[4]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + nfs41_putfh_args putfh_args; + nfs41_putfh_res putfh_res; + nfs41_setattr_args setattr_args; + nfs41_setattr_res setattr_res; + nfs41_getattr_args getattr_args; + nfs41_getattr_res getattr_res; + bitmap4 attr_request; + + compound_init(&compound, argops, resops, "setattr"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 0); + + compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res); + putfh_args.file = file; + putfh_args.in_recovery = 0; + + compound_add_op(&compound, OP_SETATTR, &setattr_args, &setattr_res); + setattr_args.stateid = stateid; + setattr_args.info = info; + + nfs41_superblock_getattr_mask(file->fh.superblock, &attr_request); + compound_add_op(&compound, OP_GETATTR, &getattr_args, &getattr_res); + getattr_args.attr_request = &attr_request; + getattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT; + getattr_res.info = info; + + status = compound_encode_send_decode(session, &compound, TRUE); + if (status) + goto out; + + if (compound_error(status = compound.res.status)) + goto out; + + memcpy(&info->attrmask, &attr_request, sizeof(bitmap4)); + nfs41_attr_cache_update(session_name_cache(session), + file->fh.fileid, info); + + if (setattr_res.attrsset.arr[0] & FATTR4_WORD0_SIZE) + nfs41_superblock_space_changed(file->fh.superblock); +out: + return status; +} + +int nfs41_link( + IN nfs41_session *session, + IN nfs41_path_fh *src, + IN nfs41_path_fh *dst_dir, + IN const nfs41_component *target, + OUT nfs41_file_info *cinfo) +{ + int status; + nfs41_compound compound; + nfs_argop4 argops[9]; + nfs_resop4 resops[9]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + nfs41_putfh_args putfh_args[2]; + nfs41_putfh_res putfh_res[2]; + nfs41_savefh_res savefh_res; + nfs41_link_args link_args; + nfs41_link_res link_res; + nfs41_lookup_args lookup_args; + nfs41_lookup_res lookup_res; + nfs41_getfh_res getfh_res; + nfs41_getattr_args getattr_args[2]; + nfs41_getattr_res getattr_res[2]; + nfs41_file_info info = { 0 }; + nfs41_path_fh file; + + nfs41_superblock_getattr_mask(src->fh.superblock, &info.attrmask); + nfs41_superblock_getattr_mask(dst_dir->fh.superblock, &cinfo->attrmask); + cinfo->attrmask.arr[0] |= FATTR4_WORD0_FSID; + + compound_init(&compound, argops, resops, "link"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 1); + + /* PUTFH(src) */ + compound_add_op(&compound, OP_PUTFH, &putfh_args[0], &putfh_res[0]); + putfh_args[0].file = src; + putfh_args[0].in_recovery = 0; + + compound_add_op(&compound, OP_SAVEFH, NULL, &savefh_res); + + /* PUTFH(dst_dir) */ + compound_add_op(&compound, OP_PUTFH, &putfh_args[1], &putfh_res[1]); + putfh_args[1].file = dst_dir; + putfh_args[1].in_recovery = 0; + + compound_add_op(&compound, OP_LINK, &link_args, &link_res); + link_args.newname = target; + + /* GETATTR(dst_dir) */ + compound_add_op(&compound, OP_GETATTR, &getattr_args[0], &getattr_res[0]); + getattr_args[0].attr_request = &info.attrmask; + getattr_res[0].obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT; + getattr_res[0].info = &info; + + /* LOOKUP(target) */ + compound_add_op(&compound, OP_LOOKUP, &lookup_args, &lookup_res); + lookup_args.name = target; + + /* GETATTR(target) */ + compound_add_op(&compound, OP_GETATTR, &getattr_args[1], &getattr_res[1]); + getattr_args[1].attr_request = &cinfo->attrmask; + getattr_res[1].obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT; + getattr_res[1].info = cinfo; + + /* GETFH(target) */ + compound_add_op(&compound, OP_GETFH, NULL, &getfh_res); + getfh_res.fh = &file.fh; + + status = compound_encode_send_decode(session, &compound, TRUE); + if (status) + goto out; + + if (compound_error(status = compound.res.status)) + goto out; + + /* fill in the file handle's fileid and superblock */ + file.fh.fileid = cinfo->fileid; + status = nfs41_superblock_for_fh(session, + &cinfo->fsid, &dst_dir->fh, &file); + if (status) + goto out; + + /* update the attributes of the destination directory */ + memcpy(&info.attrmask, &getattr_res[0].obj_attributes.attrmask, + sizeof(bitmap4)); + nfs41_attr_cache_update(session_name_cache(session), + info.fileid, &info); + + /* add the new file handle and attributes to the name cache */ + memcpy(&cinfo->attrmask, &getattr_res[1].obj_attributes.attrmask, + sizeof(bitmap4)); + AcquireSRWLockShared(&dst_dir->path->lock); + nfs41_name_cache_insert(session_name_cache(session), + dst_dir->path->path, target, &file.fh, + cinfo, &link_res.cinfo, OPEN_DELEGATE_NONE); + ReleaseSRWLockShared(&dst_dir->path->lock); + + nfs41_superblock_space_changed(dst_dir->fh.superblock); +out: + return status; +} + +int nfs41_readlink( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN uint32_t max_len, + OUT char *link_out, + OUT uint32_t *len_out) +{ + int status; + nfs41_compound compound; + nfs_argop4 argops[3]; + nfs_resop4 resops[3]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + nfs41_putfh_args putfh_args; + nfs41_putfh_res putfh_res; + nfs41_readlink_res readlink_res; + + compound_init(&compound, argops, resops, "readlink"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 0); + + compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res); + putfh_args.file = file; + putfh_args.in_recovery = 0; + + compound_add_op(&compound, OP_READLINK, NULL, &readlink_res); + readlink_res.link_len = max_len - 1; + readlink_res.link = link_out; + + status = compound_encode_send_decode(session, &compound, TRUE); + if (status) + goto out; + + if (compound_error(status = compound.res.status)) + goto out; + + link_out[readlink_res.link_len] = '\0'; + *len_out = readlink_res.link_len; +out: + return status; +} + +int nfs41_access( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN uint32_t requested, + OUT uint32_t *supported OPTIONAL, + OUT uint32_t *access OPTIONAL) +{ + int status; + nfs41_compound compound; + nfs_argop4 argops[3]; + nfs_resop4 resops[3]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + nfs41_putfh_args putfh_args; + nfs41_putfh_res putfh_res; + nfs41_access_args access_args; + nfs41_access_res access_res; + + compound_init(&compound, argops, resops, "access"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 0); + + compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res); + putfh_args.file = file; + putfh_args.in_recovery = 0; + + compound_add_op(&compound, OP_ACCESS, &access_args, &access_res); + access_args.access = requested; + + status = compound_encode_send_decode(session, &compound, TRUE); + if (status) + goto out; + + if (compound_error(status = compound.res.status)) + goto out; + + if (supported) + *supported = access_res.supported; + if (access) + *access = access_res.access; +out: + return status; +} + +int nfs41_send_sequence( + IN nfs41_session *session) +{ + int status; + nfs41_compound compound; + nfs_argop4 argops[1]; + nfs_resop4 resops[1]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + + compound_init(&compound, argops, resops, "sequence"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 0); + + status = compound_encode_send_decode(session, &compound, TRUE); + if (status) + goto out; + + if (compound_error(status = compound.res.status)) + goto out; +out: + return status; +} + +enum nfsstat4 nfs41_want_delegation( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN deleg_claim4 *claim, + IN uint32_t want, + IN bool_t try_recovery, + OUT open_delegation4 *delegation) +{ + enum nfsstat4 status; + nfs41_compound compound; + nfs_argop4 argops[3]; + nfs_resop4 resops[3]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + nfs41_putfh_args putfh_args; + nfs41_putfh_res putfh_res; + nfs41_want_delegation_args wd_args; + nfs41_want_delegation_res wd_res; + + compound_init(&compound, argops, resops, "want_delegation"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 0); + + compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res); + putfh_args.file = file; + putfh_args.in_recovery = 0; + + compound_add_op(&compound, OP_WANT_DELEGATION, &wd_args, &wd_res); + wd_args.claim = claim; + wd_args.want = want; + wd_res.delegation = delegation; + + status = compound_encode_send_decode(session, &compound, try_recovery); + if (status) + goto out; + + compound_error(status = compound.res.status); +out: + return status; +} + +int nfs41_delegpurge( + IN nfs41_session *session) +{ + int status; + nfs41_compound compound; + nfs_argop4 argops[2]; + nfs_resop4 resops[2]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + nfs41_delegpurge_res dp_res; + + compound_init(&compound, argops, resops, "delegpurge"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 0); + + compound_add_op(&compound, OP_DELEGPURGE, NULL, &dp_res); + + status = compound_encode_send_decode(session, &compound, TRUE); + if (status) + goto out; + + compound_error(status = compound.res.status); +out: + return status; +} + +int nfs41_delegreturn( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN stateid_arg *stateid, + IN bool_t try_recovery) +{ + int status; + nfs41_compound compound; + nfs_argop4 argops[3]; + nfs_resop4 resops[3]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + nfs41_putfh_args putfh_args; + nfs41_putfh_res putfh_res; + nfs41_delegreturn_args dr_args; + nfs41_delegreturn_res dr_res; + + compound_init(&compound, argops, resops, "delegreturn"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 0); + + compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res); + putfh_args.file = file; + putfh_args.in_recovery = 0; + + compound_add_op(&compound, OP_DELEGRETURN, &dr_args, &dr_res); + dr_args.stateid = stateid; + + status = compound_encode_send_decode(session, &compound, try_recovery); + if (status) + goto out; + + if (compound_error(status = compound.res.status)) + goto out; + + AcquireSRWLockShared(&file->path->lock); + nfs41_name_cache_delegreturn(session_name_cache(session), + file->fh.fileid, file->path->path, &file->name); + ReleaseSRWLockShared(&file->path->lock); +out: + return status; +} + +enum nfsstat4 nfs41_fs_locations( + IN nfs41_session *session, + IN nfs41_path_fh *parent, + IN const nfs41_component *name, + OUT fs_locations4 *locations) +{ + enum nfsstat4 status; + nfs41_compound compound; + nfs_argop4 argops[4]; + nfs_resop4 resops[4]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + nfs41_putfh_args putfh_args; + nfs41_putfh_res putfh_res; + nfs41_lookup_args lookup_args; + nfs41_lookup_res lookup_res; + nfs41_getattr_args getattr_args; + nfs41_getattr_res getattr_res; + bitmap4 attr_request = { 1, { FATTR4_WORD0_FS_LOCATIONS } }; + nfs41_file_info info; + + compound_init(&compound, argops, resops, "fs_locations"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 0); + + compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res); + putfh_args.file = parent; + putfh_args.in_recovery = 0; + + compound_add_op(&compound, OP_LOOKUP, &lookup_args, &lookup_res); + lookup_args.name = name; + + compound_add_op(&compound, OP_GETATTR, &getattr_args, &getattr_res); + getattr_args.attr_request = &attr_request; + info.fs_locations = locations; + getattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT; + getattr_res.info = &info; + + status = compound_encode_send_decode(session, &compound, TRUE); + if (status) + goto out; + + compound_error(status = compound.res.status); +out: + return status; +} + +int nfs41_secinfo( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN const nfs41_component *name, + OUT nfs41_secinfo_info *secinfo) +{ + int status; + nfs41_compound compound; + nfs_argop4 argops[3]; + nfs_resop4 resops[3]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + nfs41_putfh_args putfh_args; + nfs41_putfh_res putfh_res; + nfs41_secinfo_args secinfo_args; + nfs41_secinfo_noname_res secinfo_res; + + compound_init(&compound, argops, resops, "secinfo"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 0); + + if (file == NULL) + compound_add_op(&compound, OP_PUTROOTFH, NULL, &putfh_res); + else { + compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res); + putfh_args.file = file; + putfh_args.in_recovery = 0; + } + + compound_add_op(&compound, OP_SECINFO, &secinfo_args, &secinfo_res); + secinfo_args.name = name; + secinfo_res.secinfo = secinfo; + + status = compound_encode_send_decode(session, &compound, FALSE); + if (status) + goto out; + + compound_error(status = compound.res.status); +out: + return status; +} + +int nfs41_secinfo_noname( + IN nfs41_session *session, + IN nfs41_path_fh *file, + OUT nfs41_secinfo_info *secinfo) +{ + int status; + nfs41_compound compound; + nfs_argop4 argops[3]; + nfs_resop4 resops[3]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + nfs41_putfh_args putfh_args; + nfs41_putfh_res putfh_res; + nfs41_secinfo_noname_args noname_args; + nfs41_secinfo_noname_res noname_res; + + compound_init(&compound, argops, resops, "secinfo_no_name"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 0); + + if (file == NULL) + compound_add_op(&compound, OP_PUTROOTFH, NULL, &putfh_res); + else { + compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res); + putfh_args.file = file; + putfh_args.in_recovery = 0; + } + + compound_add_op(&compound, OP_SECINFO_NO_NAME, &noname_args, &noname_res); + noname_args.type = SECINFO_STYLE4_CURRENT_FH; + noname_res.secinfo = secinfo; + + status = compound_encode_send_decode(session, &compound, FALSE); + if (status) + goto out; + + compound_error(status = compound.res.status); +out: + return status; +} + +enum nfsstat4 nfs41_free_stateid( + IN nfs41_session *session, + IN stateid4 *stateid) +{ + enum nfsstat4 status; + nfs41_compound compound; + nfs_argop4 argops[2]; + nfs_resop4 resops[2]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + nfs41_free_stateid_args freestateid_args; + nfs41_free_stateid_res freestateid_res; + + compound_init(&compound, argops, resops, "free_stateid"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 0); + + compound_add_op(&compound, OP_FREE_STATEID, &freestateid_args, &freestateid_res); + freestateid_args.stateid = stateid; + + status = compound_encode_send_decode(session, &compound, FALSE); + if (status) + goto out; + + compound_error(status = compound.res.status); +out: + return status; +} + +enum nfsstat4 nfs41_test_stateid( + IN nfs41_session *session, + IN stateid_arg *stateid_array, + IN uint32_t count, + OUT uint32_t *status_array) +{ + enum nfsstat4 status; + nfs41_compound compound; + nfs_argop4 argops[2]; + nfs_resop4 resops[2]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + nfs41_test_stateid_args teststateid_args; + nfs41_test_stateid_res teststateid_res; + + compound_init(&compound, argops, resops, "test_stateid"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 0); + + compound_add_op(&compound, OP_TEST_STATEID, &teststateid_args, &teststateid_res); + teststateid_args.stateids = stateid_array; + teststateid_args.count = count; + teststateid_res.resok.status = status_array; + teststateid_res.resok.count = count; + + status = compound_encode_send_decode(session, &compound, FALSE); + if (status) + goto out; + + compound_error(status = compound.res.status); +out: + return status; +} + +enum nfsstat4 pnfs_rpc_layoutget( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN stateid_arg *stateid, + IN enum pnfs_iomode iomode, + IN uint64_t offset, + IN uint64_t minlength, + IN uint64_t length, + OUT pnfs_layoutget_res_ok *layoutget_res_ok) +{ + enum nfsstat4 status; + nfs41_compound compound; + nfs_argop4 argops[3]; + nfs_resop4 resops[3]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + nfs41_putfh_args putfh_args; + nfs41_putfh_res putfh_res; + pnfs_layoutget_args layoutget_args; + pnfs_layoutget_res layoutget_res = { 0 }; + uint32_t i; + struct list_entry *entry; + + compound_init(&compound, argops, resops, "layoutget"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 0); + + compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res); + putfh_args.file = file; + putfh_args.in_recovery = 0; + + compound_add_op(&compound, OP_LAYOUTGET, &layoutget_args, &layoutget_res); + layoutget_args.signal_layout_avail = 0; + layoutget_args.layout_type = PNFS_LAYOUTTYPE_FILE; + layoutget_args.iomode = iomode; + layoutget_args.offset = offset; + layoutget_args.minlength = minlength; + layoutget_args.length = length; + layoutget_args.stateid = stateid; + layoutget_args.maxcount = session->fore_chan_attrs.ca_maxresponsesize - READ_OVERHEAD; + + layoutget_res.u.res_ok = layoutget_res_ok; + + status = compound_encode_send_decode(session, &compound, TRUE); + if (status) + goto out; + + if (compound_error(status = compound.res.status)) + goto out; + + /* point each file handle to the meta server's superblock */ + list_for_each(entry, &layoutget_res_ok->layouts) { + pnfs_layout *base = list_container(entry, pnfs_layout, entry); + if (base->type == PNFS_LAYOUTTYPE_FILE) { + pnfs_file_layout *layout = (pnfs_file_layout*)base; + for (i = 0; i < layout->filehandles.count; i++) + layout->filehandles.arr[i].fh.superblock = file->fh.superblock; + } + } +out: + return status; +} + +enum nfsstat4 pnfs_rpc_layoutcommit( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN stateid4 *stateid, + IN uint64_t offset, + IN uint64_t length, + IN OPTIONAL uint64_t *new_last_offset, + IN OPTIONAL nfstime4 *new_time_modify, + OUT nfs41_file_info *info) +{ + enum nfsstat4 status; + nfs41_compound compound; + nfs_argop4 argops[4]; + nfs_resop4 resops[4]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + nfs41_putfh_args putfh_args; + nfs41_putfh_res putfh_res; + pnfs_layoutcommit_args lc_args; + pnfs_layoutcommit_res lc_res; + nfs41_getattr_args getattr_args; + nfs41_getattr_res getattr_res; + bitmap4 attr_request; + + nfs41_superblock_getattr_mask(file->fh.superblock, &attr_request); + + compound_init(&compound, argops, resops, "layoutcommit"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 0); + + compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res); + putfh_args.file = file; + putfh_args.in_recovery = 0; + + compound_add_op(&compound, OP_LAYOUTCOMMIT, &lc_args, &lc_res); + lc_args.offset = offset; + lc_args.length = length; + lc_args.stateid = stateid; + lc_args.new_time = new_time_modify; + lc_args.new_offset = new_last_offset; + + compound_add_op(&compound, OP_GETATTR, &getattr_args, &getattr_res); + getattr_args.attr_request = &attr_request; + getattr_res.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT; + getattr_res.info = info; + + status = compound_encode_send_decode(session, &compound, TRUE); + if (status) + goto out; + + if (compound_error(status = compound.res.status)) + goto out; + + /* update the attribute cache */ + memcpy(&info->attrmask, &getattr_res.obj_attributes.attrmask, + sizeof(bitmap4)); + nfs41_attr_cache_update(session_name_cache(session), + file->fh.fileid, info); +out: + return status; +} + +enum nfsstat4 pnfs_rpc_layoutreturn( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN enum pnfs_layout_type type, + IN enum pnfs_iomode iomode, + IN uint64_t offset, + IN uint64_t length, + IN stateid4 *stateid, + OUT pnfs_layoutreturn_res *layoutreturn_res) +{ + enum nfsstat4 status; + nfs41_compound compound; + nfs_argop4 argops[3]; + nfs_resop4 resops[3]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + nfs41_putfh_args putfh_args; + nfs41_putfh_res putfh_res; + pnfs_layoutreturn_args layoutreturn_args; + + compound_init(&compound, argops, resops, "layoutreturn"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 0); + + compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res); + putfh_args.file = file; + putfh_args.in_recovery = 0; + + compound_add_op(&compound, OP_LAYOUTRETURN, &layoutreturn_args, layoutreturn_res); + layoutreturn_args.reclaim = 0; + layoutreturn_args.type = type; + layoutreturn_args.iomode = iomode; + layoutreturn_args.return_type = PNFS_RETURN_FILE; + layoutreturn_args.offset = offset; + layoutreturn_args.length = length; + layoutreturn_args.stateid = stateid; + + status = compound_encode_send_decode(session, &compound, TRUE); + if (status) + goto out; + + compound_error(status = compound.res.status); +out: + return status; +} + +enum nfsstat4 pnfs_rpc_getdeviceinfo( + IN nfs41_session *session, + IN unsigned char *deviceid, + OUT pnfs_file_device *device) +{ + enum nfsstat4 status; + nfs41_compound compound; + nfs_argop4 argops[2]; + nfs_resop4 resops[2]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + pnfs_getdeviceinfo_args getdeviceinfo_args; + pnfs_getdeviceinfo_res getdeviceinfo_res; + + compound_init(&compound, argops, resops, "get_deviceinfo"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 0); + + compound_add_op(&compound, OP_GETDEVICEINFO, + &getdeviceinfo_args, &getdeviceinfo_res); + getdeviceinfo_args.deviceid = deviceid; + getdeviceinfo_args.layout_type = PNFS_LAYOUTTYPE_FILE; + getdeviceinfo_args.maxcount = NFS41_MAX_SERVER_CACHE; /* XXX */ + getdeviceinfo_args.notify_types.count = 0; + getdeviceinfo_res.u.res_ok.device = device; + + status = compound_encode_send_decode(session, &compound, TRUE); + if (status) + goto out; + + compound_error(status = compound.res.status); +out: + return status; +} + +enum nfsstat4 nfs41_rpc_openattr( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN bool_t createdir, + OUT nfs41_fh *fh_out) +{ + enum nfsstat4 status; + nfs41_compound compound; + nfs_argop4 argops[4]; + nfs_resop4 resops[4]; + nfs41_sequence_args sequence_args; + nfs41_sequence_res sequence_res; + nfs41_putfh_args putfh_args; + nfs41_putfh_res putfh_res; + nfs41_openattr_args openattr_args; + nfs41_openattr_res openattr_res; + nfs41_getfh_res getfh_res; + + compound_init(&compound, argops, resops, "openattr"); + + compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res); + nfs41_session_sequence(&sequence_args, session, 0); + + compound_add_op(&compound, OP_PUTFH, &putfh_args, &putfh_res); + putfh_args.file = file; + putfh_args.in_recovery = FALSE; + + compound_add_op(&compound, OP_OPENATTR, &openattr_args, &openattr_res); + openattr_args.createdir = createdir; + + compound_add_op(&compound, OP_GETFH, NULL, &getfh_res); + getfh_res.fh = fh_out; + + status = compound_encode_send_decode(session, &compound, TRUE); + if (status) + goto out; + + compound_error(status = compound.res.status); + + fh_out->superblock = file->fh.superblock; +out: + return status; +} diff --git a/reactos/base/services/nfsd/nfs41_ops.h b/reactos/base/services/nfsd/nfs41_ops.h new file mode 100644 index 00000000000..3b04a9158bb --- /dev/null +++ b/reactos/base/services/nfsd/nfs41_ops.h @@ -0,0 +1,1286 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#ifndef __NFS41_NFS_OPS_H__ +#define __NFS41_NFS_OPS_H__ + +#include "nfs41.h" +#include "pnfs.h" + + +/* Operation arrays */ +enum nfs_opnum4 { + OP_ACCESS = 3, + OP_CLOSE = 4, + OP_COMMIT = 5, + OP_CREATE = 6, + OP_DELEGPURGE = 7, + OP_DELEGRETURN = 8, + OP_GETATTR = 9, + OP_GETFH = 10, + OP_LINK = 11, + OP_LOCK = 12, + OP_LOCKT = 13, + OP_LOCKU = 14, + OP_LOOKUP = 15, + OP_LOOKUPP = 16, + OP_NVERIFY = 17, + OP_OPEN = 18, + OP_OPENATTR = 19, + OP_OPEN_CONFIRM = 20, /* Mandatory not-to-implement */ + OP_OPEN_DOWNGRADE = 21, + OP_PUTFH = 22, + OP_PUTPUBFH = 23, + OP_PUTROOTFH = 24, + OP_READ = 25, + OP_READDIR = 26, + OP_READLINK = 27, + OP_REMOVE = 28, + OP_RENAME = 29, + OP_RENEW = 30, /* Mandatory not-to-implement */ + OP_RESTOREFH = 31, + OP_SAVEFH = 32, + OP_SECINFO = 33, + OP_SETATTR = 34, + OP_SETCLIENTID = 35, /* Mandatory not-to-implement */ + OP_SETCLIENTID_CONFIRM = 36, /* Mandatory not-to-implement */ + OP_VERIFY = 37, + OP_WRITE = 38, + OP_RELEASE_LOCKOWNER = 39, /* Mandatory not-to-implement */ + + /* new operations for NFSv4.1 */ + OP_BACKCHANNEL_CTL = 40, + OP_BIND_CONN_TO_SESSION = 41, + OP_EXCHANGE_ID = 42, + OP_CREATE_SESSION = 43, + OP_DESTROY_SESSION = 44, + OP_FREE_STATEID = 45, + OP_GET_DIR_DELEGATION = 46, + OP_GETDEVICEINFO = 47, + OP_GETDEVICELIST = 48, + OP_LAYOUTCOMMIT = 49, + OP_LAYOUTGET = 50, + OP_LAYOUTRETURN = 51, + OP_SECINFO_NO_NAME = 52, + OP_SEQUENCE = 53, + OP_SET_SSV = 54, + OP_TEST_STATEID = 55, + OP_WANT_DELEGATION = 56, + OP_DESTROY_CLIENTID = 57, + OP_RECLAIM_COMPLETE = 58, + OP_ILLEGAL = 10044 +}; + + +/* OP_EXCHANGE_ID */ +enum { + EXCHGID4_FLAG_SUPP_MOVED_REFER = 0x00000001, + EXCHGID4_FLAG_SUPP_MOVED_MIGR = 0x00000002, + + EXCHGID4_FLAG_BIND_PRINC_STATEID = 0x00000100, + + EXCHGID4_FLAG_USE_NON_PNFS = 0x00010000, + EXCHGID4_FLAG_USE_PNFS_MDS = 0x00020000, + EXCHGID4_FLAG_USE_PNFS_DS = 0x00040000, + + EXCHGID4_FLAG_MASK_PNFS = 0x00070000, + + EXCHGID4_FLAG_UPD_CONFIRMED_REC_A = 0x40000000, + EXCHGID4_FLAG_CONFIRMED_R = 0x80000000 +}; + +typedef enum { + SP4_NONE = 0, + SP4_MACH_CRED = 1, + SP4_SSV = 2 +} state_protect_how4; + +typedef struct __state_protect4_a { + state_protect_how4 spa_how; +} state_protect4_a; + +typedef struct __nfs41_exchange_id_args { + client_owner4 *eia_clientowner; + uint32_t eia_flags; + state_protect4_a eia_state_protect; + nfs_impl_id4 *eia_client_impl_id; /* <1> */ +} nfs41_exchange_id_args; + +typedef struct __state_protect4_r { + state_protect_how4 spr_how; +} state_protect4_r; + +typedef struct __nfs41_exchange_id_res { + uint32_t status; + uint64_t clientid; + uint32_t sequenceid; + uint32_t flags; + state_protect4_r state_protect; + server_owner4 server_owner; + uint32_t server_scope_len; + char server_scope[NFS4_OPAQUE_LIMIT]; +} nfs41_exchange_id_res; + +typedef struct __nfs41_callback_sec_parms { + uint32_t type; + union { + /* case AUTH_SYS */ + struct __authsys_parms { + uint32_t stamp; + char *machinename; + } auth_sys; + /* case RPCSEC_GSS */ + struct __rpcsec_gss_parms { + uint32_t gss_srv_type; + char *srv_gssctx_handle; + uint32_t srv_gssctx_hdle_len; + char *clnt_gssctx_handle; + uint32_t clnt_gssctx_hdle_len; + } rpcsec_gss; + } u; +} nfs41_callback_secparms; + +/* OP_CREATE_SESSION */ +typedef struct __nfs41_create_session_args { + uint64_t csa_clientid; + uint32_t csa_sequence; + uint32_t csa_flags; + nfs41_channel_attrs csa_fore_chan_attrs; + nfs41_channel_attrs csa_back_chan_attrs; + uint32_t csa_cb_program; + nfs41_callback_secparms csa_cb_secparams[2]; +} nfs41_create_session_args; + +typedef struct __nfs41_create_session_res { + unsigned char *csr_sessionid; + uint32_t csr_sequence; + uint32_t csr_flags; + nfs41_channel_attrs *csr_fore_chan_attrs; + nfs41_channel_attrs *csr_back_chan_attrs; +} nfs41_create_session_res; + + +/* OP_BIND_CONN_TO_SESSION */ +enum channel_dir_from_client4 { + CDFC4_FORE = 0x1, + CDFC4_BACK = 0x2, + CDFC4_FORE_OR_BOTH = 0x3, + CDFC4_BACK_OR_BOTH = 0x7 +}; + +enum channel_dir_from_server4 { + CDFS4_FORE = 0x1, + CDFS4_BACK = 0x2, + CDFS4_BOTH = 0x3 +}; + +typedef struct __nfs41_bind_conn_to_session_args { + unsigned char *sessionid; + enum channel_dir_from_client4 dir; +} nfs41_bind_conn_to_session_args; + +typedef struct __nfs41_bind_conn_to_session_res { + enum nfsstat4 status; + /* case NFS4_OK: */ + enum channel_dir_from_server4 dir; +} nfs41_bind_conn_to_session_res; + + +/* OP_DESTROY_SESSION */ +typedef struct __nfs41_destroy_session_args { + unsigned char *dsa_sessionid; +} nfs41_destroy_session_args; + +typedef struct __nfs41_destroy_session_res { + uint32_t dsr_status; +} nfs41_destroy_session_res; + + +/* OP_DESTROY_CLIENTID */ +typedef struct __nfs41_destroy_clientid_args { + uint64_t dca_clientid; +} nfs41_destroy_clientid_args; + +typedef struct __nfs41_destroy_clientid_res { + uint32_t dcr_status; +} nfs41_destroy_clientid_res; + + +/* OP_SEQUENCE */ +typedef struct __nfs41_sequence_args { + unsigned char *sa_sessionid; + uint32_t sa_sequenceid; + uint32_t sa_slotid; + uint32_t sa_highest_slotid; + bool_t sa_cachethis; +} nfs41_sequence_args; + +enum { + SEQ4_STATUS_CB_PATH_DOWN = 0x00000001, + SEQ4_STATUS_CB_GSS_CONTEXTS_EXPIRING = 0x00000002, + SEQ4_STATUS_CB_GSS_CONTEXTS_EXPIRED = 0x00000004, + SEQ4_STATUS_EXPIRED_ALL_STATE_REVOKED = 0x00000008, + SEQ4_STATUS_EXPIRED_SOME_STATE_REVOKED = 0x00000010, + SEQ4_STATUS_ADMIN_STATE_REVOKED = 0x00000020, + SEQ4_STATUS_RECALLABLE_STATE_REVOKED = 0x00000040, + SEQ4_STATUS_LEASE_MOVED = 0x00000080, + SEQ4_STATUS_RESTART_RECLAIM_NEEDED = 0x00000100, + SEQ4_STATUS_CB_PATH_DOWN_SESSION = 0x00000200, + SEQ4_STATUS_BACKCHANNEL_FAULT = 0x00000400, + SEQ4_STATUS_DEVID_CHANGED = 0x00000800, + SEQ4_STATUS_DEVID_DELETED = 0x00001000 +}; + +typedef struct __nfs41_sequence_res_ok { + unsigned char sr_sessionid[NFS4_SESSIONID_SIZE]; + uint32_t sr_sequenceid; + uint32_t sr_slotid; + uint32_t sr_highest_slotid; + uint32_t sr_target_highest_slotid; + uint32_t sr_status_flags; +} nfs41_sequence_res_ok; + +typedef struct __nfs41_sequence_res { + uint32_t sr_status; + /* case NFS4_OK: */ + nfs41_sequence_res_ok sr_resok4; +} nfs41_sequence_res; + + +/* OP_RECLAIM_COMPLETE */ +typedef struct __nfs41_reclaim_complete_res { + enum nfsstat4 status; +} nfs41_reclaim_complete_res; + + +/* recoverable stateid argument */ +enum stateid_type { + STATEID_OPEN, + STATEID_LOCK, + STATEID_DELEG_FILE, + STATEID_DELEG_DIR, + STATEID_LAYOUT, + STATEID_SPECIAL +}; +typedef struct __stateid_arg { + stateid4 stateid; + enum stateid_type type; + nfs41_open_state *open; + nfs41_delegation_state *delegation; +} stateid_arg; + + +/* OP_ACCESS */ +enum { + ACCESS4_READ = 0x00000001, + ACCESS4_LOOKUP = 0x00000002, + ACCESS4_MODIFY = 0x00000004, + ACCESS4_EXTEND = 0x00000008, + ACCESS4_DELETE = 0x00000010, + ACCESS4_EXECUTE = 0x00000020 +}; + +typedef struct __nfs41_access_args { + uint32_t access; +} nfs41_access_args; + +typedef struct __nfs41_access_res { + uint32_t status; + /* case NFS4_OK: */ + uint32_t supported; + uint32_t access; +} nfs41_access_res; + + +/* OP_CLOSE */ +typedef struct __nfs41_op_close_args { +// uint32_t seqid; // not used, always 0 + stateid_arg *stateid; +} nfs41_op_close_args; + +typedef struct __nfs41_op_close_res { + uint32_t status; +} nfs41_op_close_res; + + +/* OP_COMMIT */ +typedef struct __nfs41_commit_args { + uint64_t offset; + uint32_t count; +} nfs41_commit_args; + +typedef struct __nfs41_commit_res { + uint32_t status; + nfs41_write_verf *verf; +} nfs41_commit_res; + + +/* OP_CREATE */ +typedef struct __specdata4 { + uint32_t specdata1; + uint32_t specdata2; +} specdata4; + +typedef struct __createtype4 { + uint32_t type; + union { + /* case NF4LNK: */ + struct __create_type_lnk { + uint32_t linkdata_len; + const char *linkdata; + } lnk; + /* case NF4BLK, NF4CHR: */ + specdata4 devdata; + } u; +} createtype4; + +typedef struct __nfs41_create_args { + createtype4 objtype; + const nfs41_component *name; + nfs41_file_info *createattrs; +} nfs41_create_args; + +typedef struct __nfs41_create_res { + uint32_t status; + /* case NFS4_OK: */ + change_info4 cinfo; + bitmap4 attrset; +} nfs41_create_res; + + +/* OP_DELEGPURGE */ +typedef struct __nfs41_delegpurge_res { + uint32_t status; +} nfs41_delegpurge_res; + + +/* OP_DELEGRETURN */ +typedef struct __nfs41_delegreturn_args { + stateid_arg *stateid; +} nfs41_delegreturn_args; + +typedef struct __nfs41_delegreturn_res { + uint32_t status; +} nfs41_delegreturn_res; + + +/* OP_LINK */ +typedef struct __nfs41_link_args { + const nfs41_component *newname; +} nfs41_link_args; + +typedef struct __nfs41_link_res { + uint32_t status; + /* case NFS4_OK */ + change_info4 cinfo; +} nfs41_link_res; + + +/* OP_LOCK */ +enum { + READ_LT = 1, + WRITE_LT = 2, + READW_LT = 3, /* blocking read */ + WRITEW_LT = 4 /* blocking write */ +}; + +typedef struct __open_to_lock_owner4 { + uint32_t open_seqid; + stateid_arg *open_stateid; + uint32_t lock_seqid; + state_owner4 *lock_owner; +} open_to_lock_owner4; + +typedef struct __exist_lock_owner4 { + stateid_arg *lock_stateid; + uint32_t lock_seqid; +} exist_lock_owner4; + +typedef struct __locker4 { + bool_t new_lock_owner; + union { + /* case TRUE: */ + open_to_lock_owner4 open_owner; + /* case FALSE: */ + exist_lock_owner4 lock_owner; + } u; +} locker4; + +typedef struct __nfs41_lock_args { + uint32_t locktype; + bool_t reclaim; + uint64_t offset; + uint64_t length; + locker4 locker; +} nfs41_lock_args; + +typedef struct __lock_res_denied { + uint64_t offset; + uint64_t length; + uint32_t locktype; + state_owner4 owner; +} lock_res_denied; + +typedef struct __lock_res_ok { + stateid4 *lock_stateid; +} lock_res_ok; + +typedef struct __nfs41_lock_res { + uint32_t status; + union { + /* case NFS4_OK: */ + lock_res_ok resok4; + /* case NFS4ERR_DENIED: */ + lock_res_denied denied; + /* default: void; */ + } u; +} nfs41_lock_res; + + +/* OP_LOCKT */ +typedef struct __nfs41_lockt_args { + uint32_t locktype; + uint64_t offset; + uint64_t length; + state_owner4 *owner; +} nfs41_lockt_args; + +typedef struct __nfs41_lockt_res { + uint32_t status; + /* case NFS4ERR_DENIED: */ + lock_res_denied denied; + /* default: void; */ +} nfs41_lockt_res; + + +/* OP_LOCKU */ +typedef struct __nfs41_locku_args { + uint32_t locktype; + uint32_t seqid; + stateid_arg *lock_stateid; + uint64_t offset; + uint64_t length; +} nfs41_locku_args; + +typedef struct __nfs41_locku_res { + uint32_t status; + /* case NFS4_OK: */ + stateid4 *lock_stateid; +} nfs41_locku_res; + + +/* OP_LOOKUP */ +typedef struct __nfs41_lookup_args { + const nfs41_component *name; +} nfs41_lookup_args; + +typedef struct __nfs41_lookup_res { + uint32_t status; +} nfs41_lookup_res; + + +/* OP_GETFH */ +typedef struct __nfs41_getfh_res { + uint32_t status; + /* case NFS4_OK: */ + nfs41_fh *fh; +} nfs41_getfh_res; + + +/* OP_PUTFH */ +typedef struct __nfs41_putfh_args { + nfs41_path_fh *file; + bool_t in_recovery; +} nfs41_putfh_args; + +typedef struct __nfs41_putfh_res { + uint32_t status; +} nfs41_putfh_res; + + +/* OP_PUTROOTFH */ +typedef struct __nfs41_putrootfh_res { + uint32_t status; +} nfs41_putrootfh_res; + + +/* OP_GETATTR */ +typedef struct __nfs41_getattr_args { + bitmap4 *attr_request; +} nfs41_getattr_args; + +typedef struct __nfs41_getattr_res { + uint32_t status; + /* case NFS4_OK: */ + fattr4 obj_attributes; + nfs41_file_info *info; +} nfs41_getattr_res; + + +/* OP_OPEN */ +enum createmode4 { + UNCHECKED4 = 0, + GUARDED4 = 1, + EXCLUSIVE4 = 2, + EXCLUSIVE4_1 = 3 +}; + +typedef struct __createhow4 { + uint32_t mode; + nfs41_file_info *createattrs; + unsigned char createverf[NFS4_VERIFIER_SIZE]; +} createhow4; + +enum opentype4 { + OPEN4_NOCREATE = 0, + OPEN4_CREATE = 1 +}; + +typedef struct __openflag4 { + uint32_t opentype; + /* case OPEN4_CREATE: */ + createhow4 how; +} openflag4; + +enum { + OPEN4_SHARE_ACCESS_READ = 0x00000001, + OPEN4_SHARE_ACCESS_WRITE = 0x00000002, + OPEN4_SHARE_ACCESS_BOTH = 0x00000003, + + OPEN4_SHARE_DENY_NONE = 0x00000000, + OPEN4_SHARE_DENY_READ = 0x00000001, + OPEN4_SHARE_DENY_WRITE = 0x00000002, + OPEN4_SHARE_DENY_BOTH = 0x00000003, + + OPEN4_SHARE_ACCESS_WANT_DELEG_MASK = 0xFF00, + OPEN4_SHARE_ACCESS_WANT_NO_PREFERENCE = 0x0000, + OPEN4_SHARE_ACCESS_WANT_READ_DELEG = 0x0100, + OPEN4_SHARE_ACCESS_WANT_WRITE_DELEG = 0x0200, + OPEN4_SHARE_ACCESS_WANT_ANY_DELEG = 0x0300, + OPEN4_SHARE_ACCESS_WANT_NO_DELEG = 0x0400, + OPEN4_SHARE_ACCESS_WANT_CANCEL = 0x0500, + + OPEN4_SHARE_ACCESS_WANT_SIGNAL_DELEG_WHEN_RESRC_AVAIL = 0x10000, + OPEN4_SHARE_ACCESS_WANT_PUSH_DELEG_WHEN_UNCONTENDED = 0x20000 +}; + +enum open_delegation_type4 { + OPEN_DELEGATE_NONE = 0, + OPEN_DELEGATE_READ = 1, + OPEN_DELEGATE_WRITE = 2, + OPEN_DELEGATE_NONE_EXT = 3 +}; + +enum open_claim_type4 { + CLAIM_NULL = 0, + CLAIM_PREVIOUS = 1, + CLAIM_DELEGATE_CUR = 2, + CLAIM_DELEGATE_PREV = 3, + CLAIM_FH = 4, + CLAIM_DELEG_CUR_FH = 5, + CLAIM_DELEG_PREV_FH = 6 +}; + +enum why_no_delegation4 { + WND4_NOT_WANTED = 0, + WND4_CONTENTION = 1, + WND4_RESOURCE = 2, + WND4_NOT_SUPP_FTYPE = 3, + WND4_WRITE_DELEG_NOT_SUPP_FTYPE = 4, + WND4_NOT_SUPP_UPGRADE = 5, + WND4_NOT_SUPP_DOWNGRADE = 6, + WND4_CANCELED = 7, + WND4_IS_DIR = 8 +}; + +typedef struct __open_claim4 { + uint32_t claim; + union { + /* case CLAIM_NULL: */ + struct __open_claim_null { + const nfs41_component *filename; + } null; + /* case CLAIM_PREVIOUS: */ + struct __open_claim_prev { + uint32_t delegate_type; + } prev; + /* case CLAIM_DELEGATE_CUR: */ + struct __open_claim_deleg_cur { + stateid_arg *delegate_stateid; + nfs41_component *name; + } deleg_cur; + /* case CLAIM_DELEG_CUR_FH: */ + struct __open_claim_deleg_cur_fh { + stateid_arg *delegate_stateid; + } deleg_cur_fh; + /* case CLAIM_DELEGATE_PREV: */ + struct __open_claim_deleg_prev { + const nfs41_component *filename; + } deleg_prev; + } u; +} open_claim4; + +typedef struct __nfs41_op_open_args { + uint32_t seqid; + uint32_t share_access; + uint32_t share_deny; + state_owner4 *owner; + openflag4 openhow; + open_claim4 *claim; +} nfs41_op_open_args; + +enum { + OPEN4_RESULT_CONFIRM = 0x00000002, + OPEN4_RESULT_LOCKTYPE_POSIX = 0x00000004, + OPEN4_RESULT_PRESERVE_UNLINKED = 0x00000008, + OPEN4_RESULT_MAY_NOTIFY_LOCK = 0x00000020 +}; + +typedef struct __nfs41_op_open_res_ok { + stateid4 *stateid; + change_info4 cinfo; + uint32_t rflags; + bitmap4 attrset; + open_delegation4 *delegation; +} nfs41_op_open_res_ok; + +typedef struct __nfs41_op_open_res { + uint32_t status; + /* case NFS4_OK: */ + nfs41_op_open_res_ok resok4; +} nfs41_op_open_res; + + +/* OP_OPENATTR */ +typedef struct __nfs41_openattr_args { + bool_t createdir; +} nfs41_openattr_args; + +typedef struct __nfs41_openattr_res { + uint32_t status; +} nfs41_openattr_res; + + +/* OP_READ */ +typedef struct __nfs41_read_args { + stateid_arg *stateid; /* -> nfs41_op_open_res_ok.stateid */ + uint64_t offset; + uint32_t count; +} nfs41_read_args; + +typedef struct __nfs41_read_res_ok { + bool_t eof; + uint32_t data_len; + unsigned char *data; /* caller-allocated */ +} nfs41_read_res_ok; + +typedef struct __nfs41_read_res { + uint32_t status; + /* case NFS4_OK: */ + nfs41_read_res_ok resok4; +} nfs41_read_res; + + +/* OP_READDIR */ +typedef struct __nfs41_readdir_args { + nfs41_readdir_cookie cookie; + uint32_t dircount; + uint32_t maxcount; + bitmap4 *attr_request; +} nfs41_readdir_args; + +typedef struct __nfs41_readdir_entry { + uint64_t cookie; + uint32_t name_len; + uint32_t next_entry_offset; + nfs41_file_info attr_info; + char name[1]; +} nfs41_readdir_entry; + +typedef struct __nfs41_readdir_list { + bool_t has_entries; + uint32_t entries_len; + unsigned char *entries; + bool_t eof; +} nfs41_readdir_list; + +typedef struct __nfs41_readdir_res { + uint32_t status; + /* case NFS4_OK: */ + unsigned char cookieverf[NFS4_VERIFIER_SIZE]; + nfs41_readdir_list reply; +} nfs41_readdir_res; + + +/* OP_READLINK */ +typedef struct __nfs41_readlink_res { + uint32_t status; + /* case NFS4_OK: */ + uint32_t link_len; + char *link; +} nfs41_readlink_res; + + +/* OP_REMOVE */ +typedef struct __nfs41_remove_args { + const nfs41_component *target; +} nfs41_remove_args; + +typedef struct __nfs41_remove_res { + uint32_t status; + /* case NFS4_OK: */ + change_info4 cinfo; +} nfs41_remove_res; + + +/* OP_RENAME */ +typedef struct __nfs41_rename_args { + const nfs41_component *oldname; + const nfs41_component *newname; +} nfs41_rename_args; + +typedef struct __nfs41_rename_res { + uint32_t status; + /* case NFS4_OK: */ + change_info4 source_cinfo; + change_info4 target_cinfo; +} nfs41_rename_res; + + +/* OP_RESTOREFH */ +/* OP_SAVEFH */ +typedef struct __nfs41_restorefh_savefh_res { + uint32_t status; +} nfs41_restorefh_res, nfs41_savefh_res; + + +/* OP_SETATTR */ +enum time_how4 { + SET_TO_SERVER_TIME4 = 0, + SET_TO_CLIENT_TIME4 = 1 +}; + +typedef struct __nfs41_setattr_args { + stateid_arg *stateid; + nfs41_file_info *info; +} nfs41_setattr_args; + +typedef struct __nfs41_setattr_res { + uint32_t status; + bitmap4 attrsset; +} nfs41_setattr_res; + + +/* OP_WANT_DELEGATION */ +typedef struct __deleg_claim4 { + uint32_t claim; + /* case CLAIM_PREVIOUS: */ + uint32_t prev_delegate_type; +} deleg_claim4; + +typedef struct __nfs41_want_delegation_args { + uint32_t want; + deleg_claim4 *claim; +} nfs41_want_delegation_args; + +typedef struct __nfs41_want_delegation_res { + uint32_t status; + /* case NFS4_OK: */ + open_delegation4 *delegation; +} nfs41_want_delegation_res; +/* OP_FREE_STATEID */ +typedef struct __nfs41_free_stateid_args { + stateid4 *stateid; +} nfs41_free_stateid_args; + +typedef struct __nfs41_free_stateid_res { + uint32_t status; +} nfs41_free_stateid_res; + + +/* OP_TEST_STATEID */ +typedef struct __nfs41_test_stateid_args { + uint32_t count; + stateid_arg *stateids; // caller-allocated array +} nfs41_test_stateid_args; + +typedef struct __nfs41_test_stateid_res { + uint32_t status; + struct { + uint32_t count; + uint32_t *status; // caller-allocated array + } resok; +} nfs41_test_stateid_res; + + +/* OP_WRITE */ +enum stable_how4 { + UNSTABLE4 = 0, + DATA_SYNC4 = 1, + FILE_SYNC4 = 2 +}; + +typedef struct __nfs41_write_args { + stateid_arg *stateid; /* -> nfs41_op_open_res_ok.stateid */ + uint64_t offset; + uint32_t stable; /* stable_how4 */ + uint32_t data_len; + unsigned char *data; /* caller-allocated */ +} nfs41_write_args; + +typedef struct __nfs41_write_res_ok { + uint32_t count; + nfs41_write_verf *verf; +} nfs41_write_res_ok; + +typedef struct __nfs41_write_res { + uint32_t status; + /* case NFS4_OK: */ + nfs41_write_res_ok resok4; +} nfs41_write_res; + +/* OP_SECINFO */ +enum sec_flavor { + RPC_GSS_SVC_NONE = 1, + RPC_GSS_SVC_INTEGRITY = 2, + RPC_GSS_SVC_PRIVACY = 3, +}; + +#define RPCSEC_GSS 6 +#define MAX_OID_LEN 128 +typedef struct __nfs41_secinfo_info { + char oid[MAX_OID_LEN]; + uint32_t oid_len; + uint32_t sec_flavor; + uint32_t qop; + enum sec_flavor type; +} nfs41_secinfo_info; + +typedef struct __nfs41_secinfo_args { + const nfs41_component *name; +} nfs41_secinfo_args; + +#define MAX_SECINFOS 6 + +/* OP_SECINFO_NO_NAME */ +enum secinfo_no_name_type { + SECINFO_STYLE4_CURRENT_FH = 0, + SECINFO_STYLE4_PARENT = 1 +}; + +typedef struct __nfs41_secinfo_noname_args { +#ifdef __REACTOS__ + uint32_t type; +#else + enum secinfo_noname_type type; +#endif +} nfs41_secinfo_noname_args; + +typedef struct __nfs41_secinfo_noname_res { + uint32_t status; + /* case NFS4_OK: */ + nfs41_secinfo_info *secinfo; + uint32_t count; +} nfs41_secinfo_noname_res; + +/* LAYOUTGET */ +typedef struct __pnfs_layoutget_args { + bool_t signal_layout_avail; + enum pnfs_layout_type layout_type; + enum pnfs_iomode iomode; + uint64_t offset; + uint64_t length; + uint64_t minlength; + stateid_arg *stateid; + uint32_t maxcount; +} pnfs_layoutget_args; + +typedef struct __pnfs_layoutget_res_ok { + bool_t return_on_close; + stateid4 stateid; + uint32_t count; + struct list_entry layouts; /* list of pnfs_layouts */ +} pnfs_layoutget_res_ok; + +typedef struct __pnfs_layoutget_res { + enum nfsstat4 status; + union { + /* case NFS4_OK: */ + pnfs_layoutget_res_ok *res_ok; + /* case NFS4ERR_LAYOUTTRYLATER: */ + bool_t will_signal_layout_avail; + /* default: void; */ + } u; +} pnfs_layoutget_res; + + +/* LAYOUTCOMMIT */ +typedef struct __pnfs_layoutcommit_args { + uint64_t offset; + uint64_t length; + stateid4 *stateid; + nfstime4 *new_time; + uint64_t *new_offset; +} pnfs_layoutcommit_args; + +typedef struct __pnfs_layoutcommit_res { + uint32_t status; + /* case NFS4_OK */ + bool_t has_new_size; + /* case TRUE */ + uint64_t new_size; +} pnfs_layoutcommit_res; + + +/* LAYOUTRETURN */ +typedef struct __pnfs_layoutreturn_args { + bool_t reclaim; + enum pnfs_layout_type type; + enum pnfs_iomode iomode; + enum pnfs_return_type return_type; + /* case LAYOUTRETURN4_FILE: */ + uint64_t offset; + uint64_t length; + stateid4 *stateid; +} pnfs_layoutreturn_args; + +typedef struct __pnfs_layoutreturn_res { + enum nfsstat4 status; + /* case NFS4_OK: */ + bool_t stateid_present; + /* case true: */ + stateid4 stateid; +} pnfs_layoutreturn_res; + + +/* GETDEVICEINFO */ +typedef struct __pnfs_getdeviceinfo_args { + unsigned char *deviceid; + enum pnfs_layout_type layout_type; + uint32_t maxcount; + bitmap4 notify_types; +} pnfs_getdeviceinfo_args; + +typedef struct __pnfs_getdeviceinfo_res_ok { + pnfs_file_device *device; + bitmap4 notification; +} pnfs_getdeviceinfo_res_ok; + +typedef struct __pnfs_getdeviceinfo_res { + enum nfsstat4 status; + union { + /* case NFS4_OK: */ + pnfs_getdeviceinfo_res_ok res_ok; + /* case NFS4ERR_TOOSMALL: */ + uint32_t mincount; + /* default: void; */ + } u; +} pnfs_getdeviceinfo_res; + + +/* nfs41_ops.c */ +int nfs41_exchange_id( + IN nfs41_rpc_clnt *rpc, + IN client_owner4 *owner, + IN uint32_t flags_in, + OUT nfs41_exchange_id_res *res_out); + +int nfs41_create_session( + IN nfs41_client *clnt, + OUT nfs41_session *session, + IN bool_t try_recovery); + +enum nfsstat4 nfs41_bind_conn_to_session( + IN nfs41_rpc_clnt *rpc, + IN const unsigned char *sessionid, + IN enum channel_dir_from_client4 dir); + +int nfs41_destroy_session( + IN nfs41_session *session); + +int nfs41_destroy_clientid( + IN nfs41_rpc_clnt *rpc, + IN uint64_t clientid); + +int nfs41_send_sequence( + IN nfs41_session *session); + +enum nfsstat4 nfs41_reclaim_complete( + IN nfs41_session *session); + +int nfs41_lookup( + IN nfs41_root *root, + IN nfs41_session *session, + IN OUT nfs41_abs_path *path, + OUT OPTIONAL nfs41_path_fh *parent_out, + OUT OPTIONAL nfs41_path_fh *target_out, + OUT OPTIONAL nfs41_file_info *info_out, + OUT nfs41_session **session_out); + +int nfs41_open( + IN nfs41_session *session, + IN nfs41_path_fh *parent, + IN nfs41_path_fh *file, + IN state_owner4 *owner, + IN open_claim4 *claim, + IN uint32_t allow, + IN uint32_t deny, + IN uint32_t create, + IN uint32_t how_mode, + IN OPTIONAL nfs41_file_info *createattrs, + IN bool_t try_recovery, + OUT stateid4 *stateid, + OUT open_delegation4 *delegation, + OUT OPTIONAL nfs41_file_info *info); + +int nfs41_create( + IN nfs41_session *session, + IN uint32_t type, + IN nfs41_file_info *createattrs, + IN OPTIONAL const char *symlink, + IN nfs41_path_fh *parent, + OUT nfs41_path_fh *file, + OUT nfs41_file_info *info); + +int nfs41_close( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN stateid_arg *stateid); + +int nfs41_write( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN stateid_arg *stateid, + IN unsigned char *data, + IN uint32_t data_len, + IN uint64_t offset, + IN enum stable_how4 stable, + OUT uint32_t *bytes_written, + OUT nfs41_write_verf *verf, + OUT nfs41_file_info *cinfo); + +int nfs41_read( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN stateid_arg *stateid, + IN uint64_t offset, + IN uint32_t count, + OUT unsigned char *data_out, + OUT uint32_t *data_len_out, + OUT bool_t *eof_out); + +int nfs41_commit( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN uint64_t offset, + IN uint32_t count, + IN bool_t do_getattr, + OUT nfs41_write_verf *verf, + OUT nfs41_file_info *cinfo); + +int nfs41_lock( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN state_owner4 *owner, + IN uint32_t type, + IN uint64_t offset, + IN uint64_t length, + IN bool_t reclaim, + IN bool_t try_recovery, + IN OUT stateid_arg *stateid); + +int nfs41_unlock( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN uint64_t offset, + IN uint64_t length, + IN OUT stateid_arg *stateid); + +stateid4* nfs41_lock_stateid_copy( + IN nfs41_lock_state *lock_state, + IN OUT stateid4 *dest); + +int nfs41_readdir( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN bitmap4 *attr_request, + IN nfs41_readdir_cookie *cookie, + OUT unsigned char *entries, + IN OUT uint32_t *entries_len, + OUT bool_t *eof_out); + +int nfs41_getattr( + IN nfs41_session *session, + IN OPTIONAL nfs41_path_fh *file, + IN bitmap4 *attr_request, + OUT nfs41_file_info *info); + +int nfs41_superblock_getattr( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN bitmap4 *attr_request, + OUT nfs41_file_info *info, + OUT bool_t *supports_named_attrs); + +/* getattr.c */ +int nfs41_cached_getattr( + IN nfs41_session *session, + IN nfs41_path_fh *file, + OUT nfs41_file_info *info); + +int nfs41_remove( + IN nfs41_session *session, + IN nfs41_path_fh *parent, + IN const nfs41_component *target, + IN uint64_t fileid); + +int nfs41_rename( + IN nfs41_session *session, + IN nfs41_path_fh *src_dir, + IN const nfs41_component *src_name, + IN nfs41_path_fh *dst_dir, + IN const nfs41_component *dst_name); + +int nfs41_setattr( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN stateid_arg *stateid, + IN nfs41_file_info *info); + +int nfs41_link( + IN nfs41_session *session, + IN nfs41_path_fh *src, + IN nfs41_path_fh *dst_dir, + IN const nfs41_component *target, + OUT nfs41_file_info *cinfo); + +/* symlink.c */ +int nfs41_symlink_target( + IN nfs41_session *session, + IN nfs41_path_fh *file, + OUT nfs41_abs_path *target); + +int nfs41_symlink_follow( + IN nfs41_root *root, + IN nfs41_session *session, + IN nfs41_path_fh *symlink, + OUT nfs41_file_info *info); + +int nfs41_readlink( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN uint32_t max_len, + OUT char *link_out, + OUT uint32_t *len_out); + +int nfs41_access( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN uint32_t requested, + OUT uint32_t *supported OPTIONAL, + OUT uint32_t *access OPTIONAL); + +enum nfsstat4 nfs41_want_delegation( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN deleg_claim4 *claim, + IN uint32_t want, + IN bool_t try_recovery, + OUT open_delegation4 *delegation); + +int nfs41_delegpurge( + IN nfs41_session *session); + +int nfs41_delegreturn( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN stateid_arg *stateid, + IN bool_t try_recovery); + +enum nfsstat4 nfs41_fs_locations( + IN nfs41_session *session, + IN nfs41_path_fh *parent, + IN const nfs41_component *name, + OUT fs_locations4 *locations); + +int nfs41_secinfo( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN const nfs41_component *name, + OUT nfs41_secinfo_info *secinfo); + +int nfs41_secinfo_noname( + IN nfs41_session *session, + IN nfs41_path_fh *file, + OUT nfs41_secinfo_info *secinfo); + +enum nfsstat4 nfs41_free_stateid( + IN nfs41_session *session, + IN stateid4 *stateid); + +enum nfsstat4 nfs41_test_stateid( + IN nfs41_session *session, + IN stateid_arg *stateid_array, + IN uint32_t count, + OUT uint32_t *status_array); + +enum nfsstat4 pnfs_rpc_layoutget( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN stateid_arg *stateid, + IN enum pnfs_iomode iomode, + IN uint64_t offset, + IN uint64_t minlength, + IN uint64_t length, + OUT pnfs_layoutget_res_ok *layoutget_res); + +enum nfsstat4 pnfs_rpc_layoutcommit( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN stateid4 *stateid, + IN uint64_t offset, + IN uint64_t length, + IN OPTIONAL uint64_t *new_last_offset, + IN OPTIONAL nfstime4 *new_time_modify, + OUT nfs41_file_info *info); + +enum nfsstat4 pnfs_rpc_layoutreturn( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN enum pnfs_layout_type type, + IN enum pnfs_iomode iomode, + IN uint64_t offset, + IN uint64_t length, + IN stateid4 *stateid, + OUT pnfs_layoutreturn_res *layoutreturn_res); + +enum nfsstat4 pnfs_rpc_getdeviceinfo( + IN nfs41_session *session, + IN unsigned char *deviceid, + OUT pnfs_file_device *device); + +enum nfsstat4 nfs41_rpc_openattr( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN bool_t createdir, + OUT nfs41_fh *fh_out); + +#endif /* !__NFS41_NFS_OPS_H__ */ diff --git a/reactos/base/services/nfsd/nfs41_rpc.c b/reactos/base/services/nfsd/nfs41_rpc.c new file mode 100644 index 00000000000..a82628ccf73 --- /dev/null +++ b/reactos/base/services/nfsd/nfs41_rpc.c @@ -0,0 +1,409 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include "nfs41_ops.h" +#include "daemon_debug.h" +#include "nfs41_xdr.h" +#include "nfs41_callback.h" +#include "nfs41_driver.h" /* for AUTH_SYS, AUTHGSS_KRB5s defines */ + +#include "rpc/rpc.h" +#define SECURITY_WIN32 +#include +#include "rpc/auth_sspi.h" + +static enum clnt_stat send_null(CLIENT *client) +{ + struct timeval timeout = {10, 100}; + + return clnt_call(client, 0, + (xdrproc_t)xdr_void, NULL, + (xdrproc_t)xdr_void, NULL, timeout); +} + +static int get_client_for_netaddr( + IN const netaddr4 *netaddr, + IN uint32_t wsize, + IN uint32_t rsize, + IN nfs41_rpc_clnt *rpc, + OUT OPTIONAL char *server_name, + OUT CLIENT **client_out) +{ + int status = ERROR_NETWORK_UNREACHABLE; + struct netconfig *nconf; + struct netbuf *addr; + CLIENT *client; + + nconf = getnetconfigent(netaddr->netid); + if (nconf == NULL) + goto out; + + addr = uaddr2taddr(nconf, netaddr->uaddr); + if (addr == NULL) + goto out_free_conf; + + if (server_name) { + getnameinfo(addr->buf, addr->len, server_name, NI_MAXHOST, NULL, 0, 0); + dprintf(1, "servername is %s\n", server_name); + } + dprintf(1, "callback function %p args %p\n", nfs41_handle_callback, rpc); + client = clnt_tli_create(RPC_ANYFD, nconf, addr, NFS41_RPC_PROGRAM, + NFS41_RPC_VERSION, wsize, rsize, rpc ? proc_cb_compound_res : NULL, + rpc ? nfs41_handle_callback : NULL, rpc ? rpc : NULL); + if (client) { + *client_out = client; + status = NO_ERROR; + } + + freenetbuf(addr); +out_free_conf: + freenetconfigent(nconf); +out: + return status; +} + +static int get_client_for_multi_addr( + IN const multi_addr4 *addrs, + IN uint32_t wsize, + IN uint32_t rsize, + IN nfs41_rpc_clnt *rpc, + OUT OPTIONAL char *server_name, + OUT CLIENT **client_out, + OUT uint32_t *addr_index) +{ + int status = ERROR_NETWORK_UNREACHABLE; + uint32_t i; + for (i = 0; i < addrs->count; i++) { + status = get_client_for_netaddr(&addrs->arr[i], + wsize, rsize, rpc, server_name, client_out); + if (status == NO_ERROR) { + *addr_index = i; + break; + } + } + return status; +} + +int create_rpcsec_auth_client( + IN uint32_t sec_flavor, + IN char *server_name, + CLIENT *client + ) +{ + int status = ERROR_NETWORK_UNREACHABLE; + + switch (sec_flavor) { + case RPCSEC_AUTHGSS_KRB5: + client->cl_auth = authsspi_create_default(client, server_name, + RPCSEC_SSPI_SVC_NONE); + break; + case RPCSEC_AUTHGSS_KRB5I: + client->cl_auth = authsspi_create_default(client, server_name, + RPCSEC_SSPI_SVC_INTEGRITY); + break; + case RPCSEC_AUTHGSS_KRB5P: + client->cl_auth = authsspi_create_default(client, server_name, + RPCSEC_SSPI_SVC_PRIVACY); + break; + default: + eprintf("create_rpc_auth_client: unknown rpcsec flavor %d\n", + sec_flavor); + client->cl_auth = NULL; + } + + if (client->cl_auth == NULL) { + eprintf("nfs41_rpc_clnt_create: failed to create %s\n", + secflavorop2name(sec_flavor)); + goto out; + } else + dprintf(1, "nfs41_rpc_clnt_create: successfully created %s\n", + secflavorop2name(sec_flavor)); + status = 0; +out: + return status; +} + +/* Returns a client structure and an associated lock */ +int nfs41_rpc_clnt_create( + IN const multi_addr4 *addrs, + IN uint32_t wsize, + IN uint32_t rsize, + IN uint32_t uid, + IN uint32_t gid, + IN uint32_t sec_flavor, + OUT nfs41_rpc_clnt **rpc_out) +{ + CLIENT *client; + nfs41_rpc_clnt *rpc; + uint32_t addr_index; + int status; + char machname[MAXHOSTNAMELEN + 1]; + gid_t gids[1]; + bool_t needcb = 1; + + rpc = calloc(1, sizeof(nfs41_rpc_clnt)); + if (rpc == NULL) { + status = GetLastError(); + goto out; + } +#ifdef NO_CB_4_KRB5P + if (sec_flavor == RPCSEC_AUTHGSS_KRB5P) + needcb = 0; +#endif + rpc->needcb = needcb; + rpc->cond = CreateEvent(NULL, TRUE, FALSE, NULL); + if (rpc->cond == NULL) { + status = GetLastError(); + eprintf("CreateEvent failed %d\n", status); + goto out_free_rpc_clnt; + } + status = get_client_for_multi_addr(addrs, wsize, rsize, needcb?rpc:NULL, + rpc->server_name, &client, &addr_index); + if (status) { + clnt_pcreateerror("connecting failed"); + goto out_free_rpc_cond; + } + if (send_null(client) != RPC_SUCCESS) { + // XXX Do what here? + eprintf("nfs41_rpc_clnt_create: send_null failed\n"); + status = ERROR_NETWORK_UNREACHABLE; + goto out_err_client; + } + + rpc->sec_flavor = sec_flavor; + if (sec_flavor == RPCSEC_AUTH_SYS) { + if (gethostname(machname, sizeof(machname)) == -1) { + eprintf("nfs41_rpc_clnt_create: gethostname failed\n"); + goto out_err_client; + } + machname[sizeof(machname) - 1] = '\0'; + client->cl_auth = authsys_create(machname, uid, gid, 0, gids); + if (client->cl_auth == NULL) { + eprintf("nfs41_rpc_clnt_create: failed to create rpc authsys\n"); + status = ERROR_NETWORK_UNREACHABLE; + goto out_err_client; + } + } else { + status = create_rpcsec_auth_client(sec_flavor, rpc->server_name, client); + if (status) { + eprintf("nfs41_rpc_clnt_create: failed to establish security " + "context with %s\n", rpc->server_name); + status = ERROR_NETWORK_UNREACHABLE; + goto out_err_client; + } else + dprintf(1, "nfs41_rpc_clnt_create: successfully created %s\n", + secflavorop2name(sec_flavor)); + } + rpc->rpc = client; + + /* keep a copy of the address and buffer sizes for reconnect */ + memcpy(&rpc->addrs, addrs, sizeof(multi_addr4)); + /* save the index of the address we connected to */ + rpc->addr_index = addr_index; + rpc->wsize = wsize; + rpc->rsize = rsize; + rpc->is_valid_session = TRUE; + rpc->uid = uid; + rpc->gid = gid; + + //initialize rpc client lock + InitializeSRWLock(&rpc->lock); + + *rpc_out = rpc; +out: + return status; +out_err_client: + clnt_destroy(client); +out_free_rpc_cond: + CloseHandle(rpc->cond); +out_free_rpc_clnt: + free(rpc); + goto out; +} + +/* Frees resources allocated in clnt_create */ +void nfs41_rpc_clnt_free( + IN nfs41_rpc_clnt *rpc) +{ + auth_destroy(rpc->rpc->cl_auth); + clnt_destroy(rpc->rpc); + CloseHandle(rpc->cond); + free(rpc); +} + +static bool_t rpc_renew_in_progress(nfs41_rpc_clnt *rpc, int *value) +{ + bool_t status = FALSE; + AcquireSRWLockExclusive(&rpc->lock); + if (value) { + dprintf(1, "nfs41_rpc_renew_in_progress: setting value %d\n", *value); + rpc->in_recovery = *value; + if (!rpc->in_recovery) + SetEvent(rpc->cond); + } else { + status = rpc->in_recovery; + dprintf(1, "nfs41_rpc_renew_in_progress: returning value %d\n", status); + } + ReleaseSRWLockExclusive(&rpc->lock); + return status; +} + +static bool_t rpc_should_retry(nfs41_rpc_clnt *rpc, uint32_t version) +{ + bool_t status = 0; + AcquireSRWLockExclusive(&rpc->lock); + if (rpc->version > version) + status = 1; + ReleaseSRWLockExclusive(&rpc->lock); + return status; +} + +static int rpc_reconnect( + IN nfs41_rpc_clnt *rpc) +{ + CLIENT *client = NULL; + uint32_t addr_index; + int status; + + AcquireSRWLockExclusive(&rpc->lock); + + status = get_client_for_multi_addr(&rpc->addrs, rpc->wsize, rpc->rsize, + rpc->needcb?rpc:NULL, NULL, &client, &addr_index); + if (status) + goto out_unlock; + + if(rpc->sec_flavor == RPCSEC_AUTH_SYS) + client->cl_auth = rpc->rpc->cl_auth; + else { + auth_destroy(rpc->rpc->cl_auth); + status = create_rpcsec_auth_client(rpc->sec_flavor, rpc->server_name, client); + if (status) { + eprintf("Failed to reestablish security context\n"); + status = ERROR_NETWORK_UNREACHABLE; + goto out_err_client; + } + } + if (send_null(client) != RPC_SUCCESS) { + eprintf("rpc_reconnect: send_null failed\n"); + status = ERROR_NETWORK_UNREACHABLE; + goto out_err_client; + } + + clnt_destroy(rpc->rpc); + rpc->rpc = client; + rpc->addr_index = addr_index; + rpc->version++; + dprintf(1, "nfs41_send_compound: reestablished RPC connection\n"); + +out_unlock: + ReleaseSRWLockExclusive(&rpc->lock); + + /* after releasing the rpc lock, send a BIND_CONN_TO_SESSION if + * we need to associate the connection with the backchannel */ + if (status == NO_ERROR && rpc->needcb && + rpc->client && rpc->client->session) { + status = nfs41_bind_conn_to_session(rpc, + rpc->client->session->session_id, CDFC4_BACK_OR_BOTH); + if (status) + eprintf("nfs41_bind_conn_to_session() failed with %s\n", + nfs_error_string(status)); + status = NFS4_OK; + } + return status; + +out_err_client: + clnt_destroy(client); + goto out_unlock; +} + +int nfs41_send_compound( + IN nfs41_rpc_clnt *rpc, + IN char *inbuf, + OUT char *outbuf) +{ + struct timeval timeout = {90, 100}; + enum clnt_stat rpc_status; + int status, count = 0, one = 1, zero = 0; + uint32_t version; + + try_again: + AcquireSRWLockShared(&rpc->lock); + version = rpc->version; + rpc_status = clnt_call(rpc->rpc, 1, + (xdrproc_t)nfs_encode_compound, inbuf, + (xdrproc_t)nfs_decode_compound, outbuf, + timeout); + ReleaseSRWLockShared(&rpc->lock); + + if (rpc_status != RPC_SUCCESS) { + eprintf("clnt_call returned rpc_status = %s\n", + rpc_error_string(rpc_status)); + switch(rpc_status) { + case RPC_CANTRECV: + case RPC_CANTSEND: + case RPC_TIMEDOUT: + case RPC_AUTHERROR: + if (++count > 3 || !rpc->is_valid_session) { + status = ERROR_NETWORK_UNREACHABLE; + break; + } + if (rpc_should_retry(rpc, version)) + goto try_again; + while (rpc_renew_in_progress(rpc, NULL)) { + status = WaitForSingleObject(rpc->cond, INFINITE); + if (status != WAIT_OBJECT_0) { + dprintf(1, "rpc_renew_in_progress: WaitForSingleObject failed\n"); + print_condwait_status(1, status); + status = ERROR_LOCK_VIOLATION; + goto out; + } + rpc_renew_in_progress(rpc, &zero); + goto try_again; + } + rpc_renew_in_progress(rpc, &one); + if (rpc_status == RPC_AUTHERROR && rpc->sec_flavor != RPCSEC_AUTH_SYS) { + AcquireSRWLockExclusive(&rpc->lock); + auth_destroy(rpc->rpc->cl_auth); + status = create_rpcsec_auth_client(rpc->sec_flavor, + rpc->server_name, rpc->rpc); + ReleaseSRWLockExclusive(&rpc->lock); + if (status) { + eprintf("Failed to reestablish security context\n"); + status = ERROR_NETWORK_UNREACHABLE; + goto out; + } + } else + if (rpc_reconnect(rpc)) + eprintf("rpc_reconnect: Failed to reconnect!\n"); + rpc_renew_in_progress(rpc, &zero); + goto try_again; + default: + eprintf("UNHANDLED RPC_ERROR: %d\n", rpc_status); + status = ERROR_NETWORK_UNREACHABLE; + goto out; + } + goto out; + } + + status = 0; +out: + return status; +} diff --git a/reactos/base/services/nfsd/nfs41_server.c b/reactos/base/services/nfsd/nfs41_server.c new file mode 100644 index 00000000000..844e44026fa --- /dev/null +++ b/reactos/base/services/nfsd/nfs41_server.c @@ -0,0 +1,343 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include +#include +#include + +#include "wintirpc.h" +#include "rpc/rpc.h" + +#include "name_cache.h" +#include "daemon_debug.h" +#include "nfs41.h" +#include "util.h" + + +#define SRVLVL 2 /* dprintf level for server logging */ + + +/* nfs41_server_list */ +struct server_list { + struct list_entry head; + CRITICAL_SECTION lock; +}; +static struct server_list g_server_list; + +#define server_entry(pos) list_container(pos, nfs41_server, entry) + + +void nfs41_server_list_init() +{ + list_init(&g_server_list.head); + InitializeCriticalSection(&g_server_list.lock); +} + +/* http://tools.ietf.org/html/rfc5661#section-1.6 + * 1.6. General Definitions: Server Owner: + * "When the client has two connections each to a peer with the same major + * identifier, the client assumes that both peers are the same server (the + * server namespace is the same via each connection)" */ + +/* http://tools.ietf.org/html/rfc5661#section-2.10.4 + * 2.10.4. Server Scope + * "When the server scope values are the same, server owner value may be + * validly compared. In cases where the server scope values are different, + * server owner values are treated as different even if they contain all + * identical bytes." */ + +/* given these definitions, we require that both the server_owner.major_id + * and server_scope are identical when matching instances of nfs41_server */ + +struct server_info { + const char *scope; + const char *owner; +}; + +static int server_compare( + const struct list_entry *entry, + const void *value) +{ + const nfs41_server *server = server_entry(entry); + const struct server_info *info = (const struct server_info*)value; + const int diff = strncmp(server->scope, info->scope, NFS4_OPAQUE_LIMIT); + return diff ? diff : strncmp(server->owner, info->owner, NFS4_OPAQUE_LIMIT); +} + +static int server_entry_find( + IN struct server_list *servers, + IN const struct server_info *info, + OUT struct list_entry **entry_out) +{ + *entry_out = list_search(&servers->head, info, server_compare); + return *entry_out ? NO_ERROR : ERROR_FILE_NOT_FOUND; +} + +static int server_create( + IN const struct server_info *info, + OUT nfs41_server **server_out) +{ + int status = NO_ERROR; + nfs41_server *server; + + server = calloc(1, sizeof(nfs41_server)); + if (server == NULL) { + status = GetLastError(); + eprintf("failed to allocate server %s\n", info->owner); + goto out; + } + + StringCchCopyA(server->scope, NFS4_OPAQUE_LIMIT, info->scope); + StringCchCopyA(server->owner, NFS4_OPAQUE_LIMIT, info->owner); + InitializeSRWLock(&server->addrs.lock); + nfs41_superblock_list_init(&server->superblocks); + + status = nfs41_name_cache_create(&server->name_cache); + if (status) { + eprintf("nfs41_name_cache_create() failed with %d\n", status); + goto out_free; + } +out: + *server_out = server; + return status; + +out_free: + free(server); + server = NULL; + goto out; +} + +static void server_free( + IN nfs41_server *server) +{ + dprintf(SRVLVL, "server_free(%s)\n", server->owner); + nfs41_superblock_list_free(&server->superblocks); + nfs41_name_cache_free(&server->name_cache); + free(server); +} + +static __inline void server_ref_locked( + IN nfs41_server *server) +{ + server->ref_count++; + dprintf(SRVLVL, "nfs41_server_ref(%s) count %d\n", + server->owner, server->ref_count); +} + +void nfs41_server_ref( + IN nfs41_server *server) +{ + EnterCriticalSection(&g_server_list.lock); + + server_ref_locked(server); + + LeaveCriticalSection(&g_server_list.lock); +} + +void nfs41_server_deref( + IN nfs41_server *server) +{ + EnterCriticalSection(&g_server_list.lock); + + server->ref_count--; + dprintf(SRVLVL, "nfs41_server_deref(%s) count %d\n", + server->owner, server->ref_count); + if (server->ref_count == 0) { + list_remove(&server->entry); + server_free(server); + } + + LeaveCriticalSection(&g_server_list.lock); +} + +static void server_addrs_add( + IN OUT struct server_addrs *addrs, + IN const netaddr4 *addr) +{ + /* we keep a list of addrs used to connect to each server. once it gets + * bigger than NFS41_ADDRS_PER_SERVER, overwrite the oldest addrs. use + * server_addrs.next_index to implement a circular array */ + + AcquireSRWLockExclusive(&addrs->lock); + + if (multi_addr_find(&addrs->addrs, addr, NULL)) { + dprintf(SRVLVL, "server_addrs_add() found existing addr '%s'.\n", + addr->uaddr); + } else { + /* overwrite the address at 'next_index' */ + StringCchCopyA(addrs->addrs.arr[addrs->next_index].netid, + NFS41_NETWORK_ID_LEN+1, addr->netid); + StringCchCopyA(addrs->addrs.arr[addrs->next_index].uaddr, + NFS41_UNIVERSAL_ADDR_LEN+1, addr->uaddr); + + /* increment/wrap next_index */ + addrs->next_index = (addrs->next_index + 1) % NFS41_ADDRS_PER_SERVER; + /* update addrs.count if necessary */ + if (addrs->addrs.count < addrs->next_index) + addrs->addrs.count = addrs->next_index; + + dprintf(SRVLVL, "server_addrs_add() added new addr '%s'.\n", + addr->uaddr); + } + ReleaseSRWLockExclusive(&addrs->lock); +} + +void nfs41_server_addrs( + IN nfs41_server *server, + OUT multi_addr4 *addrs) +{ + struct server_addrs *saddrs = &server->addrs; + uint32_t i, j; + + /* make a copy of the server's addrs, with most recent first */ + AcquireSRWLockShared(&saddrs->lock); + j = saddrs->next_index; + for (i = 0; i < saddrs->addrs.count; i++) { + /* decrement/wrap j */ + j = (NFS41_ADDRS_PER_SERVER + j - 1) % NFS41_ADDRS_PER_SERVER; + memcpy(&addrs->arr[i], &saddrs->addrs.arr[j], sizeof(netaddr4)); + } + ReleaseSRWLockShared(&saddrs->lock); +} + +int nfs41_server_find_or_create( + IN const char *server_owner_major_id, + IN const char *server_scope, + IN const netaddr4 *addr, + OUT nfs41_server **server_out) +{ + struct server_info info; + struct list_entry *entry; + nfs41_server *server; + int status; + + info.owner = server_owner_major_id; + info.scope = server_scope; + + dprintf(SRVLVL, "--> nfs41_server_find_or_create(%s)\n", info.owner); + + EnterCriticalSection(&g_server_list.lock); + + /* search for an existing server */ + entry = list_search(&g_server_list.head, &info, server_compare); + if (entry == NULL) { + /* create a new server */ + status = server_create(&info, &server); + if (status == NO_ERROR) { + /* add it to the list */ + list_add_tail(&g_server_list.head, &server->entry); + *server_out = server; + + dprintf(SRVLVL, "<-- nfs41_server_find_or_create() " + "returning new server %p\n", server); + } else { + dprintf(SRVLVL, "<-- nfs41_server_find_or_create() " + "returning %d\n", status); + } + } else { + server = server_entry(entry); + status = NO_ERROR; + + dprintf(SRVLVL, "<-- nfs41_server_find_or_create() " + "returning existing server %p\n", server); + } + + if (server) { + /* register the address used to connect */ + server_addrs_add(&server->addrs, addr); + + server_ref_locked(server); + } + + *server_out = server; + LeaveCriticalSection(&g_server_list.lock); + return status; +} + +int nfs41_server_resolve( + IN const char *hostname, + IN unsigned short port, + OUT multi_addr4 *addrs) +{ + int status = ERROR_BAD_NET_NAME; + char service[16]; + struct addrinfo hints = { 0 }, *res, *info; + struct netconfig *nconf; + struct netbuf addr; + char *netid, *uaddr; + + dprintf(SRVLVL, "--> nfs41_server_resolve(%s:%u)\n", + hostname, port); + + addrs->count = 0; + + StringCchPrintfA(service, 16, "%u", port); + + /* request a list of tcp addrs for the given hostname,port */ + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + if (getaddrinfo(hostname, service, &hints, &res) != 0) + goto out; + + for (info = res; info != NULL; info = info->ai_next) { + /* find the appropriate entry in /etc/netconfig */ + switch (info->ai_family) { + case AF_INET: netid = "tcp"; break; + case AF_INET6: netid = "tcp6"; break; + default: continue; + } + + nconf = getnetconfigent(netid); + if (nconf == NULL) + continue; + + /* convert to a transport-independent universal address */ + addr.buf = info->ai_addr; + addr.maxlen = addr.len = (unsigned int)info->ai_addrlen; + + uaddr = taddr2uaddr(nconf, &addr); + freenetconfigent(nconf); + + if (uaddr == NULL) + continue; + + StringCchCopyA(addrs->arr[addrs->count].netid, + NFS41_NETWORK_ID_LEN+1, netid); + StringCchCopyA(addrs->arr[addrs->count].uaddr, + NFS41_UNIVERSAL_ADDR_LEN+1, uaddr); + freeuaddr(uaddr); + + status = NO_ERROR; + if (++addrs->count >= NFS41_ADDRS_PER_SERVER) + break; + } + freeaddrinfo(res); +out: + if (status) + dprintf(SRVLVL, "<-- nfs41_server_resolve(%s:%u) returning " + "error %d\n", hostname, port, status); + else + dprintf(SRVLVL, "<-- nfs41_server_resolve(%s:%u) returning " + "%s\n", hostname, port, addrs->arr[0].uaddr); + return status; +} diff --git a/reactos/base/services/nfsd/nfs41_session.c b/reactos/base/services/nfsd/nfs41_session.c new file mode 100644 index 00000000000..8b2c3a8644e --- /dev/null +++ b/reactos/base/services/nfsd/nfs41_session.c @@ -0,0 +1,381 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include +#include +#include + +#include "nfs41_ops.h" +#include "nfs41_callback.h" +#include "util.h" +#include "daemon_debug.h" + + +/* after a CB_RECALL_SLOT or NFS4ERR_BADSLOT, wait a short time for the + * SEQUENCE.target_highest_slotid to catch up before updating max_slots again */ +#define MAX_SLOTS_DELAY 2000 /* in milliseconds */ + + +/* predicate for nfs41_slot_table.cond */ +static int slot_table_avail( + IN const nfs41_slot_table *table) +{ + return table->num_used < table->max_slots; +} + +/* session slot mechanism */ +static void init_slot_table(nfs41_slot_table *table) +{ + uint32_t i; + EnterCriticalSection(&table->lock); + table->max_slots = NFS41_MAX_NUM_SLOTS; + for (i = 0; i < NFS41_MAX_NUM_SLOTS; i++) { + table->seq_nums[i] = 1; + table->used_slots[i] = 0; + } + table->highest_used = table->num_used = 0; + table->target_delay = 0; + + /* wake any threads waiting on a slot */ + if (slot_table_avail(table)) + WakeAllConditionVariable(&table->cond); + LeaveCriticalSection(&table->lock); +} + +static void resize_slot_table( + IN nfs41_slot_table *table, + IN uint32_t target_highest_slotid) +{ + if (target_highest_slotid >= NFS41_MAX_NUM_SLOTS) + target_highest_slotid = NFS41_MAX_NUM_SLOTS - 1; + + if (table->max_slots != target_highest_slotid + 1) { + dprintf(2, "updated max_slots %u to %u\n", + table->max_slots, target_highest_slotid + 1); + table->max_slots = target_highest_slotid + 1; + + if (slot_table_avail(table)) + WakeAllConditionVariable(&table->cond); + } +} + +void nfs41_session_bump_seq( + IN nfs41_session *session, + IN uint32_t slotid, + IN uint32_t target_highest_slotid) +{ + nfs41_slot_table *table = &session->table; + + AcquireSRWLockShared(&session->client->session_lock); + EnterCriticalSection(&table->lock); + + if (slotid < NFS41_MAX_NUM_SLOTS) + table->seq_nums[slotid]++; + + /* adjust max_slots in response to changes in target_highest_slotid, + * but not immediately after a CB_RECALL_SLOT or NFS4ERR_BADSLOT error */ + if (table->target_delay <= GetTickCount64()) + resize_slot_table(table, target_highest_slotid); + + LeaveCriticalSection(&table->lock); + ReleaseSRWLockShared(&session->client->session_lock); +} + +void nfs41_session_free_slot( + IN nfs41_session *session, + IN uint32_t slotid) +{ + nfs41_slot_table *table = &session->table; + + AcquireSRWLockShared(&session->client->session_lock); + EnterCriticalSection(&table->lock); + + /* flag the slot as unused */ + if (slotid < NFS41_MAX_NUM_SLOTS && table->used_slots[slotid]) { + table->used_slots[slotid] = 0; + table->num_used--; + } + /* update highest_used if necessary */ + if (slotid == table->highest_used) { + while (table->highest_used && !table->used_slots[table->highest_used]) + table->highest_used--; + } + dprintf(3, "freeing slot#=%d used=%d highest=%d\n", + slotid, table->num_used, table->highest_used); + + /* wake any threads waiting on a slot */ + if (slot_table_avail(table)) + WakeAllConditionVariable(&table->cond); + + LeaveCriticalSection(&table->lock); + ReleaseSRWLockShared(&session->client->session_lock); +} + +void nfs41_session_get_slot( + IN nfs41_session *session, + OUT uint32_t *slot, + OUT uint32_t *seqid, + OUT uint32_t *highest) +{ + nfs41_slot_table *table = &session->table; + uint32_t i; + + AcquireSRWLockShared(&session->client->session_lock); + EnterCriticalSection(&table->lock); + + /* wait for an available slot */ + while (!slot_table_avail(table)) + SleepConditionVariableCS(&table->cond, &table->lock, INFINITE); + + for (i = 0; i < table->max_slots; i++) { + if (table->used_slots[i]) + continue; + + table->used_slots[i] = 1; + table->num_used++; + if (i > table->highest_used) + table->highest_used = i; + + *slot = i; + *seqid = table->seq_nums[i]; + *highest = table->highest_used; + break; + } + LeaveCriticalSection(&table->lock); + ReleaseSRWLockShared(&session->client->session_lock); + + dprintf(2, "session %p: using slot#=%d with seq#=%d highest=%d\n", + session, *slot, *seqid, *highest); +} + +int nfs41_session_recall_slot( + IN nfs41_session *session, + IN OUT uint32_t target_highest_slotid) +{ + nfs41_slot_table *table = &session->table; + + AcquireSRWLockShared(&session->client->session_lock); + EnterCriticalSection(&table->lock); + resize_slot_table(table, target_highest_slotid); + table->target_delay = GetTickCount64() + MAX_SLOTS_DELAY; + LeaveCriticalSection(&table->lock); + ReleaseSRWLockShared(&session->client->session_lock); + + return NFS4_OK; +} + +int nfs41_session_bad_slot( + IN nfs41_session *session, + IN OUT nfs41_sequence_args *args) +{ + nfs41_slot_table *table = &session->table; + int status = NFS4ERR_BADSLOT; + + if (args->sa_slotid == 0) { + eprintf("server bug detected: NFS4ERR_BADSLOT for slotid=0\n"); + goto out; + } + + /* avoid using any slots >= bad_slotid */ + EnterCriticalSection(&table->lock); + if (table->max_slots > args->sa_slotid) { + resize_slot_table(table, args->sa_slotid); + table->target_delay = GetTickCount64() + MAX_SLOTS_DELAY; + } + LeaveCriticalSection(&table->lock); + + /* get a new slot */ + nfs41_session_free_slot(session, args->sa_slotid); + nfs41_session_get_slot(session, &args->sa_slotid, + &args->sa_sequenceid, &args->sa_highest_slotid); + status = NFS4_OK; +out: + return status; +} + +void nfs41_session_sequence( + nfs41_sequence_args *args, + nfs41_session *session, + bool_t cachethis) +{ + nfs41_session_get_slot(session, &args->sa_slotid, + &args->sa_sequenceid, &args->sa_highest_slotid); + args->sa_sessionid = session->session_id; + args->sa_cachethis = cachethis; +} + + +/* session renewal */ +static unsigned int WINAPI renew_session(void *args) +{ + int status = NO_ERROR; + nfs41_session *session = (nfs41_session *)args; + /* sleep for 2/3 of lease_time */ + const uint32_t sleep_time = (2 * session->lease_time*1000)/3; + + dprintf(1, "Creating renew_session thread: %p\n", session->renew_thread); + while(1) { + dprintf(1, "Going to sleep for %dmsecs\n", sleep_time); + Sleep(sleep_time); + status = nfs41_send_sequence(session); + if (status) + dprintf(1, "renewal thread: nfs41_send_sequence failed %d\n", status); + } + return status; +} + +/* session creation */ +static int session_alloc( + IN nfs41_client *client, + OUT nfs41_session **session_out) +{ + nfs41_session *session; + int status = NO_ERROR; + + session = calloc(1, sizeof(nfs41_session)); + if (session == NULL) { + status = GetLastError(); + goto out; + } + session->client = client; + session->renew_thread = INVALID_HANDLE_VALUE; + session->isValidState = FALSE; + + InitializeCriticalSection(&session->table.lock); + InitializeConditionVariable(&session->table.cond); + + init_slot_table(&session->table); + + //initialize session lock + InitializeSRWLock(&client->session_lock); + + /* initialize the back channel */ + nfs41_callback_session_init(session); + + *session_out = session; +out: + return status; +} + +int nfs41_session_create( + IN nfs41_client *client, + IN nfs41_session **session_out) +{ + nfs41_session *session; + int status; + + status = session_alloc(client, &session); + if (status) { + eprintf("session_alloc() failed with %d\n", status); + goto out; + } + + AcquireSRWLockShared(&client->exid_lock); + if (client->rpc->needcb) + session->flags |= CREATE_SESSION4_FLAG_CONN_BACK_CHAN; + session->flags |= CREATE_SESSION4_FLAG_PERSIST; + ReleaseSRWLockShared(&client->exid_lock); + + status = nfs41_create_session(client, session, TRUE); + if (status) { + eprintf("nfs41_create_session failed %d\n", status); + status = ERROR_BAD_NET_RESP; + goto out_err; + } + + AcquireSRWLockExclusive(&session->client->session_lock); + client->session = session; + session->isValidState = TRUE; + ReleaseSRWLockExclusive(&session->client->session_lock); + *session_out = session; +out: + return status; + +out_err: + nfs41_session_free(session); + goto out; +} + +/* session renewal */ +int nfs41_session_renew( + IN nfs41_session *session) +{ + int status; + + AcquireSRWLockExclusive(&session->client->session_lock); + session->cb_session.cb_seqnum = 0; + init_slot_table(&session->table); + + status = nfs41_create_session(session->client, session, FALSE); + ReleaseSRWLockExclusive(&session->client->session_lock); + return status; +} + +int nfs41_session_set_lease( + IN nfs41_session *session, + IN uint32_t lease_time) +{ + int status = NO_ERROR; + uint32_t thread_id; + + if (valid_handle(session->renew_thread)) { + eprintf("nfs41_session_set_lease(): session " + "renewal thread already started!\n"); + goto out; + } + + if (lease_time == 0) { + eprintf("nfs41_session_set_lease(): invalid lease_time=0\n"); + status = ERROR_INVALID_PARAMETER; + goto out; + } + + session->lease_time = lease_time; + session->renew_thread = (HANDLE)_beginthreadex(NULL, + 0, renew_session, session, 0, &thread_id); + if (!valid_handle(session->renew_thread)) { + status = GetLastError(); + eprintf("_beginthreadex failed %d\n", status); + goto out; + } +out: + return status; +} + +void nfs41_session_free( + IN nfs41_session *session) +{ + AcquireSRWLockExclusive(&session->client->session_lock); + if (valid_handle(session->renew_thread)) { + dprintf(1, "nfs41_session_free: terminating session renewal thread\n"); + if (!TerminateThread(session->renew_thread, NO_ERROR)) + eprintf("failed to terminate renewal thread %p\n", + session->renew_thread); + } + + if (session->isValidState) { + session->client->rpc->is_valid_session = FALSE; + nfs41_destroy_session(session); + } + DeleteCriticalSection(&session->table.lock); + ReleaseSRWLockExclusive(&session->client->session_lock); + free(session); +} diff --git a/reactos/base/services/nfsd/nfs41_superblock.c b/reactos/base/services/nfsd/nfs41_superblock.c new file mode 100644 index 00000000000..3d8eec0ab93 --- /dev/null +++ b/reactos/base/services/nfsd/nfs41_superblock.c @@ -0,0 +1,302 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include +#include + +#include "daemon_debug.h" +#include "nfs41.h" +#include "nfs41_ops.h" +#include "from_kernel.h" +#include "util.h" + + +#define SBLVL 3 /* dprintf level for superblock logging */ + + +static __inline int compare_fsid( + IN const nfs41_fsid *lhs, + IN const nfs41_fsid *rhs) +{ + if (lhs->major > rhs->major) return 1; + if (lhs->major < rhs->major) return -1; + if (lhs->minor > rhs->minor) return 1; + if (lhs->minor < rhs->minor) return -1; + return 0; +} + + +/* nfs41_superblock */ +static int superblock_create( + IN const nfs41_fsid *fsid, + OUT nfs41_superblock **superblock_out) +{ + int status = NO_ERROR; + nfs41_superblock *superblock; + + dprintf(SBLVL, "creating superblock for fsid(%llu,%llu)\n", + fsid->major, fsid->minor); + + superblock = calloc(1, sizeof(nfs41_superblock)); + if (superblock == NULL) { + status = GetLastError(); + eprintf("failed to allocate superblock " + "for fsid(%llu,%llu)\n", fsid->major, fsid->minor); + goto out; + } + + memcpy(&superblock->fsid, fsid, sizeof(nfs41_fsid)); + InitializeSRWLock(&superblock->lock); + + *superblock_out = superblock; +out: + return status; +} + +static int get_superblock_attrs( + IN nfs41_session *session, + IN nfs41_superblock *superblock, + IN nfs41_path_fh *file) +{ + bool_t supports_named_attrs; + int status; + bitmap4 attr_request; + nfs41_file_info info = { 0 }; + + attr_request.arr[0] = FATTR4_WORD0_SUPPORTED_ATTRS | + FATTR4_WORD0_LINK_SUPPORT | FATTR4_WORD0_SYMLINK_SUPPORT | + FATTR4_WORD0_ACLSUPPORT | FATTR4_WORD0_CANSETTIME | + FATTR4_WORD0_CASE_INSENSITIVE | FATTR4_WORD0_CASE_PRESERVING | + FATTR4_WORD0_MAXREAD | (uint32_t)(FATTR4_WORD0_MAXWRITE); + attr_request.arr[1] = FATTR4_WORD1_FS_LAYOUT_TYPE | + FATTR4_WORD1_TIME_DELTA; + attr_request.arr[2] = FATTR4_WORD2_SUPPATTR_EXCLCREAT; + attr_request.count = 3; + + info.supported_attrs = &superblock->supported_attrs; + info.suppattr_exclcreat = &superblock->suppattr_exclcreat; + info.time_delta = &superblock->time_delta; + + status = nfs41_superblock_getattr(session, file, + &attr_request, &info, &supports_named_attrs); + if (status) { + eprintf("nfs41_superblock_getattr() failed with %s when fetching " + "attributes for fsid(%llu,%llu)\n", nfs_error_string(status), + superblock->fsid.major, superblock->fsid.minor); + goto out; + } + + if (info.maxread) + superblock->maxread = info.maxread; + else + superblock->maxread = session->fore_chan_attrs.ca_maxresponsesize; + + if (info.maxwrite) + superblock->maxwrite = info.maxwrite; + else + superblock->maxwrite = session->fore_chan_attrs.ca_maxrequestsize; + + superblock->layout_types = info.fs_layout_types; + superblock->aclsupport = info.aclsupport; + superblock->link_support = info.link_support; + superblock->symlink_support = info.symlink_support; + superblock->ea_support = supports_named_attrs; + superblock->case_preserving = info.case_preserving; + superblock->case_insensitive = info.case_insensitive; + + if (bitmap_isset(&info.attrmask, 0, FATTR4_WORD0_CANSETTIME)) + superblock->cansettime = info.cansettime; + else /* cansettime is not supported, try setting them anyway */ + superblock->cansettime = 1; + + /* if time_delta is not supported, default to 1s */ + if (!bitmap_isset(&info.attrmask, 1, FATTR4_WORD1_TIME_DELTA)) + superblock->time_delta.seconds = 1; + + /* initialize the default getattr mask */ + superblock->default_getattr.count = 2; + superblock->default_getattr.arr[0] = FATTR4_WORD0_TYPE + | FATTR4_WORD0_CHANGE | FATTR4_WORD0_SIZE + | FATTR4_WORD0_FILEID | FATTR4_WORD0_HIDDEN + | FATTR4_WORD0_ARCHIVE; + superblock->default_getattr.arr[1] = FATTR4_WORD1_MODE + | FATTR4_WORD1_NUMLINKS | FATTR4_WORD1_SYSTEM + | FATTR4_WORD1_TIME_ACCESS | FATTR4_WORD1_TIME_CREATE + | FATTR4_WORD1_TIME_MODIFY; + superblock->default_getattr.arr[2] = 0; + + nfs41_superblock_supported_attrs(superblock, &superblock->default_getattr); + + dprintf(SBLVL, "attributes for fsid(%llu,%llu): " + "maxread=%llu, maxwrite=%llu, layout_types: 0x%X, " + "cansettime=%u, time_delta={%llu,%u}, aclsupport=%u, " + "link_support=%u, symlink_support=%u, case_preserving=%u, " + "case_insensitive=%u\n", + superblock->fsid.major, superblock->fsid.minor, + superblock->maxread, superblock->maxwrite, + superblock->layout_types, superblock->cansettime, + superblock->time_delta.seconds, superblock->time_delta.nseconds, + superblock->aclsupport, superblock->link_support, + superblock->symlink_support, superblock->case_preserving, + superblock->case_insensitive); +out: + return status; +} + +void nfs41_superblock_fs_attributes( + IN const nfs41_superblock *superblock, + OUT PFILE_FS_ATTRIBUTE_INFORMATION FsAttrs) +{ + FsAttrs->FileSystemAttributes = FILE_SUPPORTS_REMOTE_STORAGE; + if (superblock->link_support) + FsAttrs->FileSystemAttributes |= FILE_SUPPORTS_HARD_LINKS; + if (superblock->symlink_support) + FsAttrs->FileSystemAttributes |= FILE_SUPPORTS_REPARSE_POINTS; + if (superblock->ea_support) + FsAttrs->FileSystemAttributes |= FILE_SUPPORTS_EXTENDED_ATTRIBUTES; + if (superblock->case_preserving) + FsAttrs->FileSystemAttributes |= FILE_CASE_PRESERVED_NAMES; + if (!superblock->case_insensitive) + FsAttrs->FileSystemAttributes |= FILE_CASE_SENSITIVE_SEARCH; + if (superblock->aclsupport) + FsAttrs->FileSystemAttributes |= FILE_PERSISTENT_ACLS; + + FsAttrs->MaximumComponentNameLength = NFS41_MAX_COMPONENT_LEN; + + /* let the driver fill in FileSystemName */ + FsAttrs->FileSystemNameLength = 0; + + dprintf(SBLVL, "FileFsAttributeInformation: case_preserving %u, " + "case_insensitive %u, max component %u\n", + superblock->case_preserving, superblock->case_insensitive, + FsAttrs->MaximumComponentNameLength); +} + + +/* nfs41_superblock_list */ +#define superblock_entry(pos) list_container(pos, nfs41_superblock, entry) + +static int superblock_compare( + const struct list_entry *entry, + const void *value) +{ + const nfs41_superblock *superblock = superblock_entry(entry); + return compare_fsid(&superblock->fsid, (const nfs41_fsid*)value); +} + +static nfs41_superblock* find_superblock( + IN nfs41_superblock_list *superblocks, + IN const nfs41_fsid *fsid) +{ + struct list_entry *entry; + entry = list_search(&superblocks->head, fsid, superblock_compare); + return entry ? superblock_entry(entry) : NULL; +} + +void nfs41_superblock_list_init( + IN nfs41_superblock_list *superblocks) +{ + list_init(&superblocks->head); + InitializeSRWLock(&superblocks->lock); +} + +void nfs41_superblock_list_free( + IN nfs41_superblock_list *superblocks) +{ + struct list_entry *entry, *tmp; + + dprintf(SBLVL, "nfs41_superblock_list_free()\n"); + + list_for_each_tmp(entry, tmp, &superblocks->head) + free(superblock_entry(entry)); +} + + +int nfs41_superblock_for_fh( + IN nfs41_session *session, + IN const nfs41_fsid *fsid, + IN const nfs41_fh *parent OPTIONAL, + OUT nfs41_path_fh *file) +{ + int status = NFS4_OK; + nfs41_server *server = client_server(session->client); + nfs41_superblock_list *superblocks = &server->superblocks; + nfs41_superblock *superblock; + + dprintf(SBLVL, "--> nfs41_superblock_for_fh(fsid(%llu,%llu))\n", + fsid->major, fsid->minor); + + /* compare with the parent's fsid, and use that if it matches */ + if (parent && parent->superblock && + compare_fsid(fsid, &parent->superblock->fsid) == 0) { + file->fh.superblock = parent->superblock; + dprintf(SBLVL, "using superblock from parent\n"); + goto out; + } + + /* using a shared lock, search for an existing superblock */ + AcquireSRWLockShared(&superblocks->lock); + superblock = find_superblock(superblocks, fsid); + ReleaseSRWLockShared(&superblocks->lock); + + if (superblock) { + dprintf(SBLVL, "found existing superblock in server list " + "[shared lock]\n"); + } else { + AcquireSRWLockExclusive(&superblocks->lock); + /* must search again under an exclusive lock, in case another thread + * created it after our first search */ + superblock = find_superblock(superblocks, fsid); + if (superblock) { + dprintf(SBLVL, "found newly created superblock in server list " + "[exclusive lock]\n"); + } else { + /* create the superblock */ + status = superblock_create(fsid, &superblock); + if (status == NO_ERROR) /* add it to the list */ + list_add_tail(&superblocks->head, &superblock->entry); + } + ReleaseSRWLockExclusive(&superblocks->lock); + } + + if (status == NO_ERROR && superblock->supported_attrs.count == 0) { + /* exclusive lock on the superblock while fetching attributes */ + AcquireSRWLockExclusive(&superblock->lock); + if (superblock->supported_attrs.count == 0) + status = get_superblock_attrs(session, superblock, file); + ReleaseSRWLockExclusive(&superblock->lock); + } + + file->fh.superblock = superblock; +out: + dprintf(SBLVL, "<-- nfs41_superblock_for_fh() returning %p, status %d\n", + file->fh.superblock, status); + return status; +} + +void nfs41_superblock_space_changed( + IN nfs41_superblock *superblock) +{ + /* invalidate cached volume size attributes */ + AcquireSRWLockExclusive(&superblock->lock); + superblock->cache_expiration = 0; + ReleaseSRWLockExclusive(&superblock->lock); +} diff --git a/reactos/base/services/nfsd/nfs41_types.h b/reactos/base/services/nfsd/nfs41_types.h new file mode 100644 index 00000000000..1b9c0999e4b --- /dev/null +++ b/reactos/base/services/nfsd/nfs41_types.h @@ -0,0 +1,249 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#ifndef __NFS41_DAEMON_TYPES_H__ +#define __NFS41_DAEMON_TYPES_H__ + +#include "wintirpc.h" +#include "rpc/xdr.h" +#include "nfs41_const.h" + +typedef char* caddr_t; + +static const int64_t NFS4_INT64_MAX = 0x7fffffffffffffff; +static const uint64_t NFS4_UINT64_MAX = 0xffffffffffffffff; +static const int32_t NFS4_INT32_MAX = 0x7fffffff; +static const uint32_t NFS4_UINT32_MAX = 0xffffffff; + +static const uint64_t NFS4_MAXFILELEN = 0xffffffffffffffff; +static const uint64_t NFS4_MAXFILEOFF = 0xfffffffffffffffe; + + +/* common nfs types */ +typedef struct __nfs41_abs_path { + char path[NFS41_MAX_PATH_LEN]; + unsigned short len; + SRWLOCK lock; +} nfs41_abs_path; + +typedef struct __nfs41_component { + const char *name; + unsigned short len; +} nfs41_component; + +typedef struct __nfs41_fh { + unsigned char fh[NFS4_FHSIZE]; + uint32_t len; + uint64_t fileid; + struct __nfs41_superblock *superblock; +} nfs41_fh; + +typedef struct __nfs41_path_fh { + nfs41_abs_path *path; + nfs41_component name; + nfs41_fh fh; +} nfs41_path_fh; + +typedef struct __nfs41_fsid { + uint64_t major; + uint64_t minor; +} nfs41_fsid; + +typedef struct __nfs41_readdir_cookie { + uint64_t cookie; + unsigned char verf[NFS4_VERIFIER_SIZE]; +} nfs41_readdir_cookie; + +typedef struct __nfs41_write_verf { + unsigned char verf[NFS4_VERIFIER_SIZE]; + unsigned char expected[NFS4_VERIFIER_SIZE]; +#ifdef __REACTOS__ + uint32_t committed; +#else + enum stable_how4 committed; +#endif +} nfs41_write_verf; + +typedef struct __netaddr4 { + char netid[NFS41_NETWORK_ID_LEN+1]; + char uaddr[NFS41_UNIVERSAL_ADDR_LEN+1]; +} netaddr4; + +typedef struct __multi_addr4 { + netaddr4 arr[NFS41_ADDRS_PER_SERVER]; + uint32_t count; +} multi_addr4; + +typedef struct __bitmap4 { + uint32_t count; + uint32_t arr[3]; +} bitmap4; + +typedef struct __nfstime4 { + int64_t seconds; + uint32_t nseconds; +} nfstime4; + +typedef struct __client_owner4 { + unsigned char co_verifier[NFS4_VERIFIER_SIZE]; + uint32_t co_ownerid_len; + unsigned char co_ownerid[NFS4_OPAQUE_LIMIT]; +} client_owner4; + +typedef struct __server_owner4 { + uint64_t so_minor_id; + uint32_t so_major_id_len; + char so_major_id[NFS4_OPAQUE_LIMIT]; +} server_owner4; + +typedef struct __state_owner4 { + uint32_t owner_len; + unsigned char owner[NFS4_OPAQUE_LIMIT]; +} state_owner4; + +typedef struct __nfs_impl_id4 { + uint32_t nii_domain_len; + unsigned char *nii_domain; + uint32_t nii_name_len; + unsigned char *nii_name; + nfstime4 nii_date; +} nfs_impl_id4; + +typedef struct __nfsace4 { + uint32_t acetype; + uint32_t aceflag; + uint32_t acemask; + char who[NFS4_OPAQUE_LIMIT]; +} nfsace4; + +typedef struct __nfsacl41 { + uint32_t flag; + nfsace4 *aces; + uint32_t count; +} nfsacl41; + +typedef struct __stateid4 { + uint32_t seqid; + unsigned char other[NFS4_STATEID_OTHER]; +} stateid4; + +typedef struct __open_delegation4 { + stateid4 stateid; + nfsace4 permissions; +#ifdef __REACTOS__ + uint32_t type; +#else + enum open_delegation_type4 type; +#endif + bool_t recalled; +} open_delegation4; + +typedef struct __fattr4 { + bitmap4 attrmask; + uint32_t attr_vals_len; + unsigned char attr_vals[NFS4_OPAQUE_LIMIT]; +} fattr4; + +typedef struct __change_info4 { + bool_t atomic; + uint64_t before; + uint64_t after; +} change_info4; + +typedef struct __fs_location_server { + /* 'address' represents one of a traditional DNS host name, + * IPv4 address, IPv6 address, or a zero-length string */ + char address[NFS41_HOSTNAME_LEN+1]; +} fs_location_server; + +typedef struct __fs_location4 { + nfs41_abs_path path; /* path to fs from referred server's root */ + fs_location_server *servers; + uint32_t server_count; +} fs_location4; + +typedef struct __fs_locations4 { + nfs41_abs_path path; /* path to fs from referring server's root */ + fs_location4 *locations; + uint32_t location_count; +} fs_locations4; + +enum { + MDSTHRESH_READ = 0, + MDSTHRESH_WRITE, + MDSTHRESH_READ_IO, + MDSTHRESH_WRITE_IO, + + MAX_MDSTHRESH_HINTS +}; +typedef struct __threshold_item4 { + uint32_t type; + uint64_t hints[MAX_MDSTHRESH_HINTS]; +} threshold_item4; + +#define MAX_MDSTHRESHOLD_ITEMS 1 +typedef struct __mdsthreshold4 { + uint32_t count; + threshold_item4 items[MAX_MDSTHRESHOLD_ITEMS]; +} mdsthreshold4; + +typedef struct __nfs41_file_info { + nfs41_fsid fsid; + mdsthreshold4 mdsthreshold; + nfstime4 time_access; + nfstime4 time_create; + nfstime4 time_modify; + nfsacl41 *acl; + nfstime4 *time_delta; /* XXX: per-fs */ + bitmap4 attrmask; + bitmap4 *supported_attrs; /* XXX: per-fs */ + bitmap4 *suppattr_exclcreat; /* XXX: per-fs */ + uint64_t maxread; /* XXX: per-fs */ + uint64_t maxwrite; /* XXX: per-fs */ + uint64_t change; + uint64_t size; + uint64_t fileid; + uint64_t space_avail; /* XXX: per-fs */ + uint64_t space_free; /* XXX: per-fs */ + uint64_t space_total; /* XXX: per-fs */ + uint32_t type; + uint32_t numlinks; + uint32_t rdattr_error; + uint32_t mode; + uint32_t mode_mask; + fs_locations4 *fs_locations; /* XXX: per-fs */ + uint32_t lease_time; /* XXX: per-server */ + uint32_t fs_layout_types; /* pnfs, XXX: per-fs */ + bool_t hidden; + bool_t system; + bool_t archive; + bool_t cansettime; /* XXX: per-fs */ + bool_t case_insensitive; + bool_t case_preserving; + bool_t symlink_dir; + bool_t symlink_support; + bool_t link_support; + char *owner; + char *owner_group; + uint32_t aclsupport; +} nfs41_file_info; + +#endif /* !__NFS41_DAEMON_TYPES_H__ */ diff --git a/reactos/base/services/nfsd/nfs41_xdr.c b/reactos/base/services/nfsd/nfs41_xdr.c new file mode 100644 index 00000000000..04dc962607a --- /dev/null +++ b/reactos/base/services/nfsd/nfs41_xdr.c @@ -0,0 +1,3679 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include +#include + +#include "nfs41_compound.h" +#include "nfs41_ops.h" +#include "nfs41_xdr.h" +#include "util.h" +#include "daemon_debug.h" +#include "rpc/rpc.h" + +static bool_t encode_file_attrs( + fattr4 *attrs, + nfs41_file_info *info); + +static __inline int unexpected_op(uint32_t op, uint32_t expected) +{ + if (op == expected) + return 0; + + eprintf("Op table mismatch. Got %s (%d), expected %s (%d).\n", + nfs_opnum_to_string(op), op, + nfs_opnum_to_string(expected), expected); + return 1; +} + +/* typedef uint32_t bitmap4<> */ +bool_t xdr_bitmap4( + XDR *xdr, + bitmap4 *bitmap) +{ + uint32_t i; + + if (xdr->x_op == XDR_ENCODE) { + if (bitmap->count > 3) { + eprintf("encode_bitmap4: count (%d) must be <= 3\n", + bitmap->count); + return FALSE; + } + if (!xdr_u_int32_t(xdr, &bitmap->count)) + return FALSE; + + for (i = 0; i < bitmap->count; i++) + if (!xdr_u_int32_t(xdr, &bitmap->arr[i])) + return FALSE; + + } else if (xdr->x_op == XDR_DECODE) { + if (!xdr_u_int32_t(xdr, &bitmap->count)) + return FALSE; + if (bitmap->count > 3) { + eprintf("decode_bitmap4: count (%d) must be <= 3\n", + bitmap->count); + return FALSE; + } + + for (i = 0; i < bitmap->count; i++) + if (!xdr_u_int32_t(xdr, &bitmap->arr[i])) + return FALSE; + } else + return FALSE; + + return TRUE; +} + +/* nfstime4 */ +static bool_t xdr_nfstime4( + XDR *xdr, + nfstime4 *nt) +{ + if (!xdr_hyper(xdr, &nt->seconds)) + return FALSE; + + return xdr_u_int32_t(xdr, &nt->nseconds); +} + + +/* settime4 */ +static uint32_t settime_how( + nfstime4 *newtime, + const nfstime4 *time_delta) +{ + nfstime4 current; + get_nfs_time(¤t); + /* get the absolute difference between current and newtime */ + nfstime_diff(¤t, newtime, ¤t); + nfstime_abs(¤t, ¤t); + /* compare the difference with time_delta */ + nfstime_diff(time_delta, ¤t, ¤t); + /* use client time if diff > delta (i.e. time_delta - current < 0) */ + return current.seconds < 0 ? SET_TO_CLIENT_TIME4 : SET_TO_SERVER_TIME4; +} + +static bool_t xdr_settime4( + XDR *xdr, + nfstime4 *nt, + const nfstime4 *time_delta) +{ + uint32_t how = settime_how(nt, time_delta); + + if (xdr->x_op != XDR_ENCODE) /* not used for decode */ + return FALSE; + + if (!xdr_u_int32_t(xdr, &how)) + return FALSE; + + if (how == SET_TO_CLIENT_TIME4) + return xdr_nfstime4(xdr, nt); + + return TRUE; +} + +/* stateid4 */ +static bool_t xdr_stateid4( + XDR *xdr, + stateid4 *si) +{ + if (!xdr_u_int32_t(xdr, &si->seqid)) + return FALSE; + + return xdr_opaque(xdr, (char *)si->other, NFS4_STATEID_OTHER); +} + +/* fattr4 */ +bool_t xdr_fattr4( + XDR *xdr, + fattr4 *fattr) +{ + unsigned char *attr_vals = fattr->attr_vals; + + if (!xdr_bitmap4(xdr, &fattr->attrmask)) + return FALSE; + + return xdr_bytes(xdr, (char **)&attr_vals, &fattr->attr_vals_len, NFS4_OPAQUE_LIMIT); +} + +/* nfs41_fh */ +static bool_t xdr_fh( + XDR *xdr, + nfs41_fh *fh) +{ + unsigned char *pfh = fh->fh; + return xdr_bytes(xdr, (char **)&pfh, &fh->len, NFS4_FHSIZE); +} + +/* nfs41_fsid */ +static bool_t xdr_fsid( + XDR *xdr, + nfs41_fsid *fsid) +{ + if (!xdr_u_hyper(xdr, &fsid->major)) + return FALSE; + + return xdr_u_hyper(xdr, &fsid->minor); +} + + +/* nfs41_component */ +static bool_t encode_component( + XDR *xdr, + const nfs41_component *component) +{ + uint32_t len = component->len; + return xdr_bytes(xdr, (char **)&component->name, &len, NFS4_OPAQUE_LIMIT); +} + +static bool_t decode_component( + XDR *xdr, + nfs41_component *component) +{ + bool_t result; + uint32_t len; + + result = xdr_bytes(xdr, (char **)&component->name, &len, NFS4_OPAQUE_LIMIT); + component->len = (result == FALSE) ? 0 : (unsigned short)len; + return result; +} + + +/* state_owner4 */ +static bool_t xdr_state_owner4( + XDR *xdr, + state_owner4 *so) +{ + u_quad_t clientid = 0; + unsigned char *owner = so->owner; + + /* 18.16.3. "The client can set the clientid field to any value and + * the server MUST ignore it. Instead the server MUST derive the + * client ID from the session ID of the SEQUENCE operation of the + * COMPOUND request. */ + if (xdr->x_op == XDR_ENCODE) { + if (!xdr_u_hyper(xdr, &clientid)) /* clientid = 0 */ + return FALSE; + } else if (xdr->x_op == XDR_DECODE) { + if (!xdr_u_hyper(xdr, &clientid)) + return FALSE; + } else return FALSE; + + return xdr_bytes(xdr, (char **)&owner, &so->owner_len, NFS4_OPAQUE_LIMIT); +} + +static bool_t xdr_layout_types( + XDR *xdr, + uint32_t *layout_type) +{ + u_int32_t i, count, type; + + if (xdr->x_op != XDR_DECODE) { + eprintf("xdr_layout_types: xdr->x_op is not XDR_DECODE! " + "x_op %d not supported.\n", xdr->x_op); + return FALSE; + } + + *layout_type = 0; + + if (!xdr_u_int32_t(xdr, &count)) + return FALSE; + + for (i = 0; i < count; i++) { + if (!xdr_u_int32_t(xdr, &type)) + return FALSE; + + *layout_type |= 1 << (type - 1); + } + return TRUE; +} + +static bool_t xdr_threshold_item( + XDR *xdr, + threshold_item4 *item) +{ + bitmap4 bitmap; + + if (!xdr_u_int32_t(xdr, &item->type)) + return FALSE; + + if (!xdr_bitmap4(xdr, &bitmap)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &bitmap.count)) + return FALSE; + + if (bitmap.count) { + if (bitmap.arr[0] & 0x1 && !xdr_u_hyper(xdr, &item->hints[0])) + return FALSE; + if (bitmap.arr[0] & 0x2 && !xdr_u_hyper(xdr, &item->hints[1])) + return FALSE; + if (bitmap.arr[0] & 0x4 && !xdr_u_hyper(xdr, &item->hints[2])) + return FALSE; + if (bitmap.arr[0] & 0x8 && !xdr_u_hyper(xdr, &item->hints[3])) + return FALSE; + } + return TRUE; +} + +static bool_t xdr_mdsthreshold( + XDR *xdr, + mdsthreshold4 *mdsthreshold) +{ + uint32_t i; + + if (!xdr_u_int32_t(xdr, &mdsthreshold->count)) + return FALSE; + + if (mdsthreshold->count > MAX_MDSTHRESHOLD_ITEMS) + return FALSE; + + for (i = 0; i < mdsthreshold->count; i++) + if (!xdr_threshold_item(xdr, &mdsthreshold->items[i])) + return FALSE; + return TRUE; +} + +static bool_t xdr_nfsace4( + XDR *xdr, + nfsace4 *ace) +{ + char *who = ace->who; + + if (!xdr_u_int32_t(xdr, &ace->acetype)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &ace->aceflag)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &ace->acemask)) + return FALSE; + + /* 'who' is a static array, so don't try to free it */ + if (xdr->x_op == XDR_FREE) + return TRUE; + + return xdr_string(xdr, &who, NFS4_OPAQUE_LIMIT); +} + +static bool_t xdr_nfsdacl41( + XDR *xdr, + nfsacl41 *acl) +{ + if (!xdr_u_int32_t(xdr, &acl->flag)) + return FALSE; + + return xdr_array(xdr, (char**)&acl->aces, &acl->count, + 32, sizeof(nfsace4), (xdrproc_t)xdr_nfsace4); +} + +static bool_t xdr_nfsacl41( + XDR *xdr, + nfsacl41 *acl) +{ + return xdr_array(xdr, (char**)&acl->aces, &acl->count, + 32, sizeof(nfsace4), (xdrproc_t)xdr_nfsace4); +} + +void nfsacl41_free(nfsacl41 *acl) +{ + XDR xdr = { XDR_FREE }; + xdr_nfsacl41(&xdr, acl); +} + +/* pathname4 + * decode a variable array of components into a nfs41_abs_path */ +static bool_t decode_pathname4( + XDR *xdr, + nfs41_abs_path *path) +{ + char *pos; + u_int32_t i, count, len, remaining; + + /* decode the number of components */ + if (!xdr_u_int32_t(xdr, &count)) + return FALSE; + + pos = (char *)path->path; + remaining = NFS41_MAX_PATH_LEN; + + /* decode each component */ + for (i = 0; i < count; i++) { + len = remaining; + if (!xdr_bytes(xdr, (char **)&pos, &len, NFS41_MAX_PATH_LEN)) + return FALSE; + remaining -= len; + pos += len; + + if (i < count-1) { /* add a \ between components */ + if (remaining < 1) + return FALSE; + *pos++ = '\\'; + remaining--; + } + } + path->len = (unsigned short)(NFS41_MAX_PATH_LEN - remaining); + return TRUE; +} + +/* fs_location4 */ +static bool_t decode_fs_location4( + XDR *xdr, + fs_location4 *location) +{ + fs_location_server *arr; + char *address; + u_int32_t i, count, len; + + /* decode the number of servers */ + if (!xdr_u_int32_t(xdr, &count)) + return FALSE; + + /* allocate the fs_location_server array */ + if (count == 0) { + free(location->servers); + arr = NULL; + } else if (count != location->server_count) { + arr = realloc(location->servers, count * sizeof(fs_location_server)); + if (arr == NULL) + return FALSE; + ZeroMemory(arr, count * sizeof(fs_location_server)); + } else { + arr = location->servers; + } + + location->servers = arr; + location->server_count = count; + + for (i = 0; i < count; i++) { + len = NFS41_HOSTNAME_LEN; + address = arr[i].address; + if (!xdr_bytes(xdr, &address, &len, NFS41_HOSTNAME_LEN)) { + free(arr); + return FALSE; + } + arr[i].address[len] = '\0'; + } + + return decode_pathname4(xdr, &location->path); +} + +/* fs_locations4 */ +static bool_t decode_fs_locations4( + XDR *xdr, + fs_locations4 *locations) +{ + u_int32_t i, count; + fs_location4 *arr; + + if (!decode_pathname4(xdr, &locations->path)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &count)) + return FALSE; + + /* allocate the fs_location array */ + if (count == 0) { + free(locations->locations); + arr = NULL; + } else if (count != locations->location_count) { + arr = realloc(locations->locations, count * sizeof(fs_location4)); + if (arr == NULL) + return FALSE; + ZeroMemory(arr, count * sizeof(fs_location4)); + } else { + arr = locations->locations; + } + + locations->locations = arr; + locations->location_count = count; + + for (i = 0; i < count; i++) { + if (!decode_fs_location4(xdr, &arr[i])) { + free(arr); + return FALSE; + } + } + return TRUE; +} + +/* + * OP_EXCHANGE_ID + */ +static bool_t xdr_client_owner4( + XDR *xdr, + client_owner4 *co) +{ + unsigned char *co_ownerid = co->co_ownerid; + if (!xdr_opaque(xdr, (char *)&co->co_verifier[0], NFS4_VERIFIER_SIZE)) + return FALSE; + + return xdr_bytes(xdr, (char **)&co_ownerid, &co->co_ownerid_len, NFS4_OPAQUE_LIMIT); +} + +#if 0 +static bool_t encode_state_protect_ops4( + XDR *xdr, + state_protect_ops4 *spo) +{ + if (!xdr_bitmap4(xdr, &spo->spo_must_enforce)) + return FALSE; + + return xdr_bitmap4(xdr, &spo->spo_must_allow); +} + +static bool_t encode_ssv_sp_parms4( + XDR *xdr, + ssv_sp_parms4 *spp) +{ + if (!encode_state_protect_ops4(xdr, &spp->ssp_ops)) + return FALSE; + + if (!xdr_bytes(xdr, &spp->ssp_hash_algs, + &spp->ssp_hash_algs_len, NFS4_OPAQUE_LIMIT)) + return FALSE; + + if (!xdr_bytes(xdr, &spp->ssp_encr_algs, + &spp->ssp_encr_algs_len, NFS4_OPAQUE_LIMIT)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &spp->ssp_window)) + return FALSE; + + return xdr_u_int32_t(xdr, &spp->ssp_num_gss_handles); +} +#endif + +static bool_t xdr_state_protect4_a( + XDR *xdr, + state_protect4_a *spa) +{ + bool_t result = TRUE; + + if (!xdr_u_int32_t(xdr, (u_int32_t *)&spa->spa_how)) + return FALSE; + + switch (spa->spa_how) + { + case SP4_NONE: + break; +#if 0 + case SP4_MACH_CRED: + result = xdr_state_protect_ops4(xdr, &spa->u.spa_mach_ops); + break; + case SP4_SSV: + result = xdr_ssv_sp_parms4(xdr, &spa->u.spa_ssv_parms); + break; +#endif + default: + eprintf("encode_state_protect4_a: state protect " + "type %d not supported.\n", spa->spa_how); + result = FALSE; + break; + } + return result; +} + +static bool_t xdr_nfs_impl_id4( + XDR *xdr, + nfs_impl_id4 *nii) +{ + unsigned char *nii_domain = nii->nii_domain; + unsigned char *nii_name = nii->nii_name; + + if (!xdr_bytes(xdr, (char **)&nii_domain, &nii->nii_domain_len, NFS4_OPAQUE_LIMIT)) + return FALSE; + + if (!xdr_bytes(xdr, (char **)&nii_name, &nii->nii_name_len, NFS4_OPAQUE_LIMIT)) + return FALSE; + + return xdr_nfstime4(xdr, &nii->nii_date); +} + + +static bool_t encode_op_exchange_id( + XDR *xdr, + nfs_argop4 *argop) +{ + uint32_t zero = 0; + uint32_t one = 1; + + nfs41_exchange_id_args *args = (nfs41_exchange_id_args*)argop->arg; + + if (unexpected_op(argop->op, OP_EXCHANGE_ID)) + return FALSE; + + if (!xdr_client_owner4(xdr, args->eia_clientowner)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &args->eia_flags)) + return FALSE; + + if (!xdr_state_protect4_a(xdr, &args->eia_state_protect)) + return FALSE; + + if (args->eia_client_impl_id) + { + if (!xdr_u_int32_t(xdr, &one)) + return FALSE; + return xdr_nfs_impl_id4(xdr, args->eia_client_impl_id); + } + else + return xdr_u_int32_t(xdr, &zero); +} + +#if 0 + +static bool_t decode_state_protect_ops4( + XDR *xdr, + state_protect_ops4 *spo) +{ + if (!xdr_bitmap4(xdr, &spo->spo_must_enforce)) + return FALSE; + + return xdr_bitmap4(xdr, &spo->spo_must_allow); +} + +static bool_t decode_ssv_prot_info4( + XDR *xdr, + ssv_prot_info4 *spi) +{ +/* uint32_t i; */ + + if (!decode_state_protect_ops4(xdr, &spi->spi_ops)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &spi->spi_hash_alg)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &spi->spi_encr_alg)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &spi->spi_ssv_len)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &spi->spi_window)) + return FALSE; + +/* TODO: spi->spi_handles */ + return xdr_u_int32_t(xdr, 0); + /* + if (!xdr_u_int32_t(xdr, &spi->spi_handles.count)) + return FALSE; + + for (i = 0; i < spi->spi_handles.count; i++) + if (!xdr_opaque(xdr, &spi->spi_handles.arr[i]) + return FALSE; +*/ + return TRUE; +} +#endif + +static bool_t xdr_state_protect4_r( + XDR *xdr, + state_protect4_r *spr) +{ + bool_t result = TRUE; + + if (!xdr_u_int32_t(xdr, (uint32_t *)&spr->spr_how)) + return FALSE; + + switch (spr->spr_how) + { + case SP4_NONE: + break; +#if 0 + case SP4_MACH_CRED: + result = decode_state_protect_ops4(xdr, &spr->u.spr_mach_ops); + break; + case SP4_SSV: + result = decode_ssv_prot_info4(xdr, &spr->u.spr_ssv_info); + break; +#endif + default: + eprintf("decode_state_protect4_r: state protect " + "type %d not supported.\n", spr->spr_how); + result = FALSE; + break; + } + return result; +} + +static bool_t xdr_server_owner4( + XDR *xdr, + server_owner4 *so) +{ + char *so_major_id = so->so_major_id; + + if (!xdr_u_hyper(xdr, &so->so_minor_id)) + return FALSE; + + return xdr_bytes(xdr, (char **)&so_major_id, + &so->so_major_id_len, NFS4_OPAQUE_LIMIT); +} + +static bool_t decode_op_exchange_id( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_exchange_id_res *res = (nfs41_exchange_id_res*)resop->res; + char *server_scope = (char *)res->server_scope; + + if (unexpected_op(resop->op, OP_EXCHANGE_ID)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->status)) + return FALSE; + + if (res->status != NFS4_OK) + return TRUE; + + if (!xdr_u_hyper(xdr, &res->clientid)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->sequenceid)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->flags)) + return FALSE; + + if (!xdr_state_protect4_r(xdr, &res->state_protect)) + return FALSE; + + if (!xdr_server_owner4(xdr, &res->server_owner)) + return FALSE; + + return xdr_bytes(xdr, &server_scope, + &res->server_scope_len, NFS4_OPAQUE_LIMIT); +} + +/* + * OP_CREATE_SESSION + */ +static bool_t xdr_channel_attrs4( + XDR *xdr, + nfs41_channel_attrs *attrs) +{ + uint32_t zero = 0; + uint32_t one = 1; + + /* count4 ca_headerpadsize */ + if (!xdr_u_int32_t(xdr, &attrs->ca_headerpadsize)) + return FALSE; + + /* count4 ca_maxrequestsize */ + if (!xdr_u_int32_t(xdr, &attrs->ca_maxrequestsize)) + return FALSE; + + /* count4 ca_maxresponsesize */ + if (!xdr_u_int32_t(xdr, &attrs->ca_maxresponsesize)) + return FALSE; + + /* count4 ca_maxresponsesize_cached */ + if (!xdr_u_int32_t(xdr, &attrs->ca_maxresponsesize_cached)) + return FALSE; + + /* count4 ca_maxoperations */ + if (!xdr_u_int32_t(xdr, &attrs->ca_maxoperations)) + return FALSE; + + /* count4 ca_maxrequests */ + if (!xdr_u_int32_t(xdr, &attrs->ca_maxrequests)) + return FALSE; + + if (xdr->x_op == XDR_ENCODE) { + /* uint32_t ca_rdma_ird<1> */ + if (attrs->ca_rdma_ird) + { + if (!xdr_u_int32_t(xdr, &one)) + return FALSE; + return xdr_u_int32_t(xdr, attrs->ca_rdma_ird); + } + else { + return xdr_u_int32_t(xdr, &zero); + } + } + else if (xdr->x_op == XDR_DECODE) { +#if 0 + u_int32_t count; + /* uint32_t ca_rdma_ird<1> */ + if (!xdr_u_int32_t(xdr, &count)) + return FALSE; + if (count > 1) + return FALSE; + if (count) + return xdr_u_int32_t(xdr, attrs->ca_rdma_ird); + else +#endif + return TRUE; + } + else { + eprintf("%s: xdr->x_op %d not supported.\n", + "xdr_channel_attrs4", xdr->x_op); + return FALSE; + } +} + +static bool_t encode_backchannel_sec_parms( + XDR *xdr, + nfs41_callback_secparms *args) +{ + uint32_t zero = 0; + + if (!xdr_u_int32_t(xdr, &args->type)) + return FALSE; + + switch (args->type) { + case AUTH_NONE: return TRUE; + case AUTH_SYS: + if (!xdr_u_int32_t(xdr, &args->u.auth_sys.stamp)) + return FALSE; + if (!xdr_string(xdr, &args->u.auth_sys.machinename, NI_MAXHOST)) + return FALSE; + return xdr_u_int32_t(xdr, &zero) && xdr_u_int32_t(xdr, &zero) && + xdr_u_int32_t(xdr, &zero); + case RPCSEC_GSS: + default: + return FALSE; + } +} + +static bool_t encode_op_create_session( + XDR *xdr, + nfs_argop4 *argop) +{ + nfs41_create_session_args *args = (nfs41_create_session_args*)argop->arg; + nfs41_callback_secparms *cb_secparams = args->csa_cb_secparams; + uint32_t cb_count = 2; + + if (unexpected_op(argop->op, OP_CREATE_SESSION)) + return FALSE; + + /* clientid4 csa_clientid */ + if (!xdr_u_hyper(xdr, &args->csa_clientid)) + return FALSE; + + /* sequenceid4 csa_sequence */ + if (!xdr_u_int32_t(xdr, &args->csa_sequence)) + return FALSE; + + /* TODO: uint32_t csa_flags = 0 */ + if (!xdr_u_int32_t(xdr, &args->csa_flags)) + return FALSE; + + /* channel_attrs4 csa_fore_chan_attrs */ + if (!xdr_channel_attrs4(xdr, &args->csa_fore_chan_attrs)) + return FALSE; + + /* channel_attrs4 csa_back_chan_attrs */ + if (!xdr_channel_attrs4(xdr, &args->csa_back_chan_attrs)) + return FALSE; + + /* TODO: uint32_t csa_cb_program = 1234 */ + if (!xdr_u_int32_t(xdr, &args->csa_cb_program)) + return FALSE; + + return xdr_array(xdr, (char **)&cb_secparams, &cb_count, + 3, sizeof(nfs41_callback_secparms), (xdrproc_t) encode_backchannel_sec_parms); +} + +static bool_t decode_op_create_session( + XDR *xdr, + nfs_resop4 *resop) +{ + uint32_t opstatus; + nfs41_create_session_res *res = (nfs41_create_session_res*)resop->res; + + if (unexpected_op(resop->op, OP_CREATE_SESSION)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &opstatus)) + return FALSE; + + if (opstatus != NFS4_OK) + return TRUE; + + if (!xdr_opaque(xdr, (char *)res->csr_sessionid, NFS4_SESSIONID_SIZE)) + return FALSE; + + /* sequenceid4 csr_sequence */ + if (!xdr_u_int32_t(xdr, &res->csr_sequence)) + return FALSE; + + /* uint32_t csr_flags */ + if (!xdr_u_int32_t(xdr, &res->csr_flags)) + return FALSE; + + /* channel_attrs4 csr_fore_chan_attrs */ + if (!xdr_channel_attrs4(xdr, res->csr_fore_chan_attrs)) + return FALSE; + + /* channel_attrs4 csr_back_chan_attrs */ + return xdr_channel_attrs4(xdr, res->csr_back_chan_attrs); +} + + +/* + * OP_BIND_CONN_TO_SESSION + */ +static bool_t encode_op_bind_conn_to_session( + XDR *xdr, + nfs_argop4 *argop) +{ + uint32_t zero = 0; + + nfs41_bind_conn_to_session_args *args = + (nfs41_bind_conn_to_session_args*)argop->arg; + + if (unexpected_op(argop->op, OP_BIND_CONN_TO_SESSION)) + return FALSE; + + if (!xdr_opaque(xdr, (char *)args->sessionid, NFS4_SESSIONID_SIZE)) + return FALSE; + + if (!xdr_enum(xdr, (enum_t *)&args->dir)) + return FALSE; + + return xdr_u_int32_t(xdr, &zero); /* bctsa_use_conn_in_rdma_mode = false */ +} + +static bool_t decode_op_bind_conn_to_session( + XDR *xdr, + nfs_resop4 *resop) +{ + unsigned char sessionid_ignored[NFS4_SESSIONID_SIZE]; + nfs41_bind_conn_to_session_res *res = + (nfs41_bind_conn_to_session_res*)resop->res; + bool_t use_rdma_ignored; + + if (unexpected_op(resop->op, OP_BIND_CONN_TO_SESSION)) + return FALSE; + + if (!xdr_enum(xdr, (enum_t *)&res->status)) + return FALSE; + + if (res->status == NFS4_OK) { + if (!xdr_opaque(xdr, (char *)&sessionid_ignored, NFS4_SESSIONID_SIZE)) + return FALSE; + + if (!xdr_enum(xdr, (enum_t *)&res->dir)) + return FALSE; + + return xdr_bool(xdr, &use_rdma_ignored); + } + return TRUE; +} + + +/* + * OP_DESTROY_SESSION + */ +static bool_t encode_op_destroy_session( + XDR *xdr, + nfs_argop4 *argop) +{ + nfs41_destroy_session_args *args = (nfs41_destroy_session_args*)argop->arg; + + if (unexpected_op(argop->op, OP_DESTROY_SESSION)) + return FALSE; + + return xdr_opaque(xdr, (char *)args->dsa_sessionid, NFS4_SESSIONID_SIZE); +} + +static bool_t decode_op_destroy_session( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_destroy_session_res *res = (nfs41_destroy_session_res*)resop->res; + + if (unexpected_op(resop->op, OP_DESTROY_SESSION)) + return FALSE; + + return xdr_u_int32_t(xdr, &res->dsr_status); +} + +/* + * OP_DESTROY_CLIENTID + */ +static bool_t encode_op_destroy_clientid( + XDR *xdr, + nfs_argop4 *argop) +{ + nfs41_destroy_clientid_args *args = (nfs41_destroy_clientid_args*)argop->arg; + + if (unexpected_op(argop->op, OP_DESTROY_CLIENTID)) + return FALSE; + + return xdr_u_hyper(xdr, &args->dca_clientid); +} + +static bool_t decode_op_destroy_clientid( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_destroy_clientid_res *res = (nfs41_destroy_clientid_res*)resop->res; + + if (unexpected_op(resop->op, OP_DESTROY_CLIENTID)) + return FALSE; + + return xdr_u_int32_t(xdr, &res->dcr_status); +} + + +/* + * OP_SEQUENCE + */ +static bool_t encode_op_sequence( + XDR *xdr, + nfs_argop4 *argop) +{ + nfs41_sequence_args *args = (nfs41_sequence_args*)argop->arg; + + if (unexpected_op(argop->op, OP_SEQUENCE)) + return FALSE; + + if (!xdr_opaque(xdr, (char *)args->sa_sessionid, NFS4_SESSIONID_SIZE)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &args->sa_sequenceid)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &args->sa_slotid)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &args->sa_highest_slotid)) + return FALSE; + + return xdr_bool(xdr, &args->sa_cachethis); +} + +static bool_t xdr_sequence_res_ok( + XDR *xdr, + nfs41_sequence_res_ok *res) +{ + if (!xdr_opaque(xdr, (char *)res->sr_sessionid, NFS4_SESSIONID_SIZE)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->sr_sequenceid)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->sr_slotid)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->sr_highest_slotid)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->sr_target_highest_slotid)) + return FALSE; + + return xdr_u_int32_t(xdr, &res->sr_status_flags); +} + +static bool_t decode_op_sequence( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_sequence_res *res = (nfs41_sequence_res*)resop->res; + + if (unexpected_op(resop->op, OP_SEQUENCE)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->sr_status)) + return FALSE; + + if (res->sr_status == NFS4_OK) + return xdr_sequence_res_ok(xdr, &res->sr_resok4); + + return TRUE; +} + + +/* + * OP_RECLAIM_COMPLETE + */ +static bool_t encode_op_reclaim_complete( + XDR *xdr, + nfs_argop4 *argop) +{ + bool_t zero = FALSE; + + if (unexpected_op(argop->op, OP_RECLAIM_COMPLETE)) + return FALSE; + + /* rca_one_fs = 0 indicates that the reclaim applies to all filesystems */ + return xdr_bool(xdr, &zero); +} + +static bool_t decode_op_reclaim_complete( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_reclaim_complete_res *res = (nfs41_reclaim_complete_res*)resop->res; + + if (unexpected_op(resop->op, OP_RECLAIM_COMPLETE)) + return FALSE; + + return xdr_enum(xdr, (enum_t *)&res->status); +} + + +/* + * OP_PUTFH + */ +static bool_t encode_op_putfh( + XDR *xdr, + nfs_argop4 *argop) +{ + nfs41_putfh_args *args = (nfs41_putfh_args*)argop->arg; + + if (unexpected_op(argop->op, OP_PUTFH)) + return FALSE; + + return xdr_fh(xdr, &args->file->fh); +} + +static bool_t decode_op_putfh( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_putfh_res *res = (nfs41_putfh_res*)resop->res; + + if (unexpected_op(resop->op, OP_PUTFH)) + return FALSE; + + return xdr_u_int32_t(xdr, &res->status); +} + + +/* + * OP_PUTROOTFH + */ +static bool_t encode_op_putrootfh( + XDR *xdr, + nfs_argop4* argop) +{ + if (unexpected_op(argop->op, OP_PUTROOTFH)) + return FALSE; + /* void */ + return TRUE; +} + +static bool_t decode_op_putrootfh( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_putrootfh_res *res = (nfs41_putrootfh_res*)resop->res; + + if (unexpected_op(resop->op, OP_PUTROOTFH)) + return FALSE; + + return xdr_u_int32_t(xdr, &res->status); +} + + +/* + * OP_GETFH + */ +static bool_t encode_op_getfh( + XDR *xdr, + nfs_argop4 *argop) +{ + if (unexpected_op(argop->op, OP_GETFH)) + return FALSE; + + /* void */ + return TRUE; +} + +static bool_t decode_op_getfh( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_getfh_res *res = (nfs41_getfh_res*)resop->res; + + if (unexpected_op(resop->op, OP_GETFH)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->status)) + return FALSE; + + if (res->status == NFS4_OK) + return xdr_fh(xdr, res->fh); + + return TRUE; +} + + +/* + * OP_LOOKUP + */ +static bool_t encode_op_lookup( + XDR *xdr, + nfs_argop4 *argop) +{ + nfs41_lookup_args *args = (nfs41_lookup_args*)argop->arg; + + if (unexpected_op(argop->op, OP_LOOKUP)) + return FALSE; + + return encode_component(xdr, args->name); +} + +static bool_t decode_op_lookup( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_lookup_res *res = (nfs41_lookup_res*)resop->res; + + if (unexpected_op(resop->op, OP_LOOKUP)) + return FALSE; + + return xdr_u_int32_t(xdr, &res->status); +} + + +/* + * OP_ACCESS + */ +static bool_t encode_op_access( + XDR *xdr, + nfs_argop4 *argop) +{ + nfs41_access_args *args = (nfs41_access_args*)argop->arg; + + if (unexpected_op(argop->op, OP_ACCESS)) + return FALSE; + + return xdr_u_int32_t(xdr, &args->access); +} + +static bool_t decode_op_access( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_access_res *res = (nfs41_access_res*)resop->res; + + if (unexpected_op(resop->op, OP_ACCESS)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->status)) + return FALSE; + + if (res->status == NFS4_OK) + { + if (!xdr_u_int32_t(xdr, &res->supported)) + return FALSE; + + return xdr_u_int32_t(xdr, &res->access); + } + return TRUE; +} + + +/* + * OP_CLOSE + */ +static bool_t encode_op_close( + XDR *xdr, + nfs_argop4 *argop) +{ + nfs41_op_close_args *args = (nfs41_op_close_args*)argop->arg; + uint32_t zero = 0; + + if (unexpected_op(argop->op, OP_CLOSE)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &zero)) // This should be ignored by server + return FALSE; + + return xdr_stateid4(xdr, &args->stateid->stateid); +} + +static bool_t decode_op_close( + XDR *xdr, + nfs_resop4 *resop) +{ + stateid4 ignored; + nfs41_op_close_res *res = (nfs41_op_close_res*)resop->res; + + if (unexpected_op(resop->op, OP_CLOSE)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->status)) + return FALSE; + + if (res->status == NFS4_OK) + return xdr_stateid4(xdr, &ignored); + + return TRUE; +} + + +/* + * OP_COMMIT + */ +static bool_t encode_op_commit( + XDR *xdr, + nfs_argop4 *argop) +{ + nfs41_commit_args *args = (nfs41_commit_args*)argop->arg; + + if (unexpected_op(argop->op, OP_COMMIT)) + return FALSE; + + if (!xdr_u_hyper(xdr, &args->offset)) + return FALSE; + + return xdr_u_int32_t(xdr, &args->count); +} + +static bool_t decode_op_commit( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_commit_res *res = (nfs41_commit_res*)resop->res; + + if (unexpected_op(resop->op, OP_COMMIT)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->status)) + return FALSE; + + if (res->status == NFS4_OK) + return xdr_opaque(xdr, (char *)res->verf->verf, NFS4_VERIFIER_SIZE); + + return TRUE; +} + + +/* + * OP_CREATE + */ +static bool_t encode_createtype4( + XDR *xdr, + createtype4 *ct) +{ + bool_t result = TRUE; + const char *linkdata; + + if (!xdr_u_int32_t(xdr, &ct->type)) + return FALSE; + + switch (ct->type) + { + case NF4LNK: + linkdata = ct->u.lnk.linkdata; + result = xdr_bytes(xdr, (char**)&linkdata, &ct->u.lnk.linkdata_len, + NFS4_OPAQUE_LIMIT); + break; + case NF4BLK: + case NF4CHR: + result = xdr_u_int32_t(xdr, &ct->u.devdata.specdata1); + if (result == TRUE) + result = xdr_u_int32_t(xdr, &ct->u.devdata.specdata2); + break; + default: + // Some types need no further action + break; + } + return result; +} + +static bool_t encode_createattrs4( + XDR *xdr, + nfs41_file_info* createattrs) +{ + fattr4 attrs; + + /* encode attribute values from createattrs->info into attrs.attr_vals */ + attrs.attr_vals_len = NFS4_OPAQUE_LIMIT; + if (!encode_file_attrs(&attrs, createattrs)) + return FALSE; + + return xdr_fattr4(xdr, &attrs); +} + +static bool_t encode_op_create( + XDR *xdr, + nfs_argop4 *argop) +{ + nfs41_create_args *args = (nfs41_create_args*)argop->arg; + + if (unexpected_op(argop->op, OP_CREATE)) + return FALSE; + + if (!encode_createtype4(xdr, &args->objtype)) + return FALSE; + + if (!encode_component(xdr, args->name)) + return FALSE; + + return encode_createattrs4(xdr, args->createattrs); +} + +static bool_t xdr_change_info4( + XDR *xdr, + change_info4 *cinfo) +{ + if (!xdr_bool(xdr, &cinfo->atomic)) + return FALSE; + + if (!xdr_u_hyper(xdr, &cinfo->before)) + return FALSE; + + return xdr_u_hyper(xdr, &cinfo->after); +} + +static bool_t decode_op_create( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_create_res *res = (nfs41_create_res*)resop->res; + + if (unexpected_op(resop->op, OP_CREATE)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->status)) + return FALSE; + + if (res->status == NFS4_OK) + { + if (!xdr_change_info4(xdr, &res->cinfo)) + return FALSE; + return xdr_bitmap4(xdr, &res->attrset); + } + return TRUE; +} + + +/* + * OP_LINK + */ +static bool_t encode_op_link( + XDR *xdr, + nfs_argop4 *argop) +{ + nfs41_link_args *args = (nfs41_link_args*)argop->arg; + + if (unexpected_op(argop->op, OP_LINK)) + return FALSE; + + return encode_component(xdr, args->newname); +} + +static bool_t decode_op_link( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_link_res *res = (nfs41_link_res*)resop->res; + + if (unexpected_op(resop->op, OP_LINK)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->status)) + return FALSE; + + if (res->status == NFS4_OK) + return xdr_change_info4(xdr, &res->cinfo); + + return TRUE; +} + + +/* + * OP_LOCK + */ +static bool_t xdr_locker4( + XDR *xdr, + locker4 *locker) +{ + if (xdr->x_op != XDR_ENCODE) { + eprintf("%s: xdr->x_op %d is not supported!\n", + "xdr_locker4", xdr->x_op); + return FALSE; + } + + if (!xdr_bool(xdr, &locker->new_lock_owner)) + return FALSE; + + if (locker->new_lock_owner) { + /* open_to_lock_owner4 open_owner */ + if (!xdr_u_int32_t(xdr, &locker->u.open_owner.open_seqid)) + return FALSE; + + if (!xdr_stateid4(xdr, &locker->u.open_owner.open_stateid->stateid)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &locker->u.open_owner.lock_seqid)) + return FALSE; + + return xdr_state_owner4(xdr, locker->u.open_owner.lock_owner); + } else { + /* exist_lock_owner4 lock_owner */ + if (!xdr_stateid4(xdr, &locker->u.lock_owner.lock_stateid->stateid)) + return FALSE; + + return xdr_u_int32_t(xdr, &locker->u.lock_owner.lock_seqid); + } +} + +static bool_t encode_op_lock( + XDR *xdr, + nfs_argop4 *argop) +{ + nfs41_lock_args *args = (nfs41_lock_args*)argop->arg; + + if (unexpected_op(argop->op, OP_LOCK)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &args->locktype)) + return FALSE; + + if (!xdr_bool(xdr, &args->reclaim)) + return FALSE; + + if (!xdr_u_hyper(xdr, &args->offset)) + return FALSE; + + if (!xdr_u_hyper(xdr, &args->length)) + return FALSE; + + return xdr_locker4(xdr, &args->locker); +} + +static bool_t decode_lock_res_denied( + XDR *xdr, + lock_res_denied *denied) +{ + if (!xdr_u_hyper(xdr, &denied->offset)) + return FALSE; + + if (!xdr_u_hyper(xdr, &denied->length)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &denied->locktype)) + return FALSE; + + return xdr_state_owner4(xdr, &denied->owner); +} + +static bool_t decode_op_lock( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_lock_res *res = (nfs41_lock_res*)resop->res; + + if (unexpected_op(resop->op, OP_LOCK)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->status)) + return FALSE; + + switch (res->status) { + case NFS4_OK: + return xdr_stateid4(xdr, res->u.resok4.lock_stateid); + break; + case NFS4ERR_DENIED: + return decode_lock_res_denied(xdr, &res->u.denied); + break; + default: + break; + } + + return TRUE; +} + + +/* + * OP_LOCKT + */ +static bool_t encode_op_lockt( + XDR *xdr, + nfs_argop4 *argop) +{ + nfs41_lockt_args *args = (nfs41_lockt_args*)argop->arg; + + if (unexpected_op(argop->op, OP_LOCKT)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &args->locktype)) + return FALSE; + + if (!xdr_u_hyper(xdr, &args->offset)) + return FALSE; + + if (!xdr_u_hyper(xdr, &args->length)) + return FALSE; + + return xdr_state_owner4(xdr, args->owner); +} + +static bool_t decode_op_lockt( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_lockt_res *res = (nfs41_lockt_res*)resop->res; + + if (unexpected_op(resop->op, OP_LOCKT)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->status)) + return FALSE; + + if (res->status == NFS4ERR_DENIED) + return decode_lock_res_denied(xdr, &res->denied); + + return TRUE; +} + + +/* + * OP_LOCKU + */ +static bool_t encode_op_locku( + XDR *xdr, + nfs_argop4 *argop) +{ + nfs41_locku_args *args = (nfs41_locku_args*)argop->arg; + + if (unexpected_op(argop->op, OP_LOCKU)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &args->locktype)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &args->seqid)) + return FALSE; + + if (!xdr_stateid4(xdr, &args->lock_stateid->stateid)) + return FALSE; + + if (!xdr_u_hyper(xdr, &args->offset)) + return FALSE; + + return xdr_u_hyper(xdr, &args->length); +} + +static bool_t decode_op_locku( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_locku_res *res = (nfs41_locku_res*)resop->res; + + if (unexpected_op(resop->op, OP_LOCKU)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->status)) + return FALSE; + + if (res->status == NFS4_OK) + return xdr_stateid4(xdr, res->lock_stateid); + + return TRUE; +} + + +/* + * OP_DELEGPURGE + */ +static bool_t encode_op_delegpurge( + XDR *xdr, + nfs_argop4 *argop) +{ + uint64_t zero = 0; + + if (unexpected_op(argop->op, OP_DELEGPURGE)) + return FALSE; + + /* The client SHOULD set the client field to zero, + * and the server MUST ignore the clientid field. */ + return xdr_u_int64_t(xdr, &zero); +} + +static bool_t decode_op_delegpurge( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_delegpurge_res *res = (nfs41_delegpurge_res*)resop->res; + + if (unexpected_op(resop->op, OP_DELEGPURGE)) + return FALSE; + + return xdr_u_int32_t(xdr, &res->status); +} + + +/* + * OP_DELEGRETURN + */ +static bool_t encode_op_delegreturn( + XDR *xdr, + nfs_argop4 *argop) +{ + nfs41_delegreturn_args *args = (nfs41_delegreturn_args*)argop->arg; + + if (unexpected_op(argop->op, OP_DELEGRETURN)) + return FALSE; + + return xdr_stateid4(xdr, &args->stateid->stateid); +} + +static bool_t decode_op_delegreturn( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_delegreturn_res *res = (nfs41_delegreturn_res*)resop->res; + + if (unexpected_op(resop->op, OP_DELEGRETURN)) + return FALSE; + + return xdr_u_int32_t(xdr, &res->status); +} + + +/* + * OP_GETATTR + */ +static bool_t encode_op_getattr( + XDR *xdr, + nfs_argop4 *argop) +{ + nfs41_getattr_args *args = (nfs41_getattr_args*)argop->arg; + + if (unexpected_op(argop->op, OP_GETATTR)) + return FALSE; + + return xdr_bitmap4(xdr, args->attr_request); +} + +static bool_t decode_file_attrs( + XDR *xdr, + fattr4 *attrs, + nfs41_file_info *info) +{ + if (attrs->attrmask.count >= 1) { + if (attrs->attrmask.arr[0] & FATTR4_WORD0_SUPPORTED_ATTRS) { + if (!xdr_bitmap4(xdr, info->supported_attrs)) + return FALSE; + } + if (attrs->attrmask.arr[0] & FATTR4_WORD0_TYPE) { + if (!xdr_u_int32_t(xdr, &info->type)) + return FALSE; + } + if (attrs->attrmask.arr[0] & FATTR4_WORD0_CHANGE) { + if (!xdr_u_hyper(xdr, &info->change)) + return FALSE; + } + if (attrs->attrmask.arr[0] & FATTR4_WORD0_SIZE) { + if (!xdr_u_hyper(xdr, &info->size)) + return FALSE; + } + if (attrs->attrmask.arr[0] & FATTR4_WORD0_LINK_SUPPORT) { + if (!xdr_bool(xdr, &info->link_support)) + return FALSE; + } + if (attrs->attrmask.arr[0] & FATTR4_WORD0_SYMLINK_SUPPORT) { + if (!xdr_bool(xdr, &info->symlink_support)) + return FALSE; + } + if (attrs->attrmask.arr[0] & FATTR4_WORD0_FSID) { + if (!xdr_fsid(xdr, &info->fsid)) + return FALSE; + } + if (attrs->attrmask.arr[0] & FATTR4_WORD0_LEASE_TIME) { + if (!xdr_u_int32_t(xdr, &info->lease_time)) + return FALSE; + } + if (attrs->attrmask.arr[0] & FATTR4_WORD0_RDATTR_ERROR) { + if (!xdr_u_int32_t(xdr, &info->rdattr_error)) + return FALSE; + } + if (attrs->attrmask.arr[0] & FATTR4_WORD0_ACL) { + nfsacl41 *acl = info->acl; + if (!xdr_array(xdr, (char**)&acl->aces, &acl->count, + 32, sizeof(nfsace4), (xdrproc_t)xdr_nfsace4)) + return FALSE; + } + if (attrs->attrmask.arr[0] & FATTR4_WORD0_ACLSUPPORT) { + if (!xdr_u_int32_t(xdr, &info->aclsupport)) + return FALSE; + } + if (attrs->attrmask.arr[0] & FATTR4_WORD0_ARCHIVE) { + if (!xdr_bool(xdr, &info->archive)) + return FALSE; + } + if (attrs->attrmask.arr[0] & FATTR4_WORD0_CANSETTIME) { + if (!xdr_bool(xdr, &info->cansettime)) + return FALSE; + } + if (attrs->attrmask.arr[0] & FATTR4_WORD0_CASE_INSENSITIVE) { + if (!xdr_bool(xdr, &info->case_insensitive)) + return FALSE; + } + if (attrs->attrmask.arr[0] & FATTR4_WORD0_CASE_PRESERVING) { + if (!xdr_bool(xdr, &info->case_preserving)) + return FALSE; + } + if (attrs->attrmask.arr[0] & FATTR4_WORD0_FILEID) { + if (!xdr_u_hyper(xdr, &info->fileid)) + return FALSE; + } + if (attrs->attrmask.arr[0] & FATTR4_WORD0_FS_LOCATIONS) { + if (!decode_fs_locations4(xdr, info->fs_locations)) + return FALSE; + } + if (attrs->attrmask.arr[0] & FATTR4_WORD0_HIDDEN) { + if (!xdr_bool(xdr, &info->hidden)) + return FALSE; + } + if (attrs->attrmask.arr[0] & FATTR4_WORD0_MAXREAD) { + if (!xdr_u_hyper(xdr, &info->maxread)) + return FALSE; + } + if (attrs->attrmask.arr[0] & FATTR4_WORD0_MAXWRITE) { + if (!xdr_u_hyper(xdr, &info->maxwrite)) + return FALSE; + } + } + if (attrs->attrmask.count >= 2) { + if (attrs->attrmask.arr[1] & FATTR4_WORD1_MODE) { + if (!xdr_u_int32_t(xdr, &info->mode)) + return FALSE; + } + if (attrs->attrmask.arr[1] & FATTR4_WORD1_NUMLINKS) { + if (!xdr_u_int32_t(xdr, &info->numlinks)) + return FALSE; + } + if (attrs->attrmask.arr[1] & FATTR4_WORD1_OWNER) { + char *ptr = &info->owner[0]; + uint32_t owner_len; + if (!xdr_bytes(xdr, &ptr, &owner_len, + NFS4_OPAQUE_LIMIT)) + return FALSE; + info->owner[owner_len] = '\0'; + } + if (attrs->attrmask.arr[1] & FATTR4_WORD1_OWNER_GROUP) { + char *ptr = &info->owner_group[0]; + uint32_t owner_group_len; + if (!xdr_bytes(xdr, &ptr, &owner_group_len, + NFS4_OPAQUE_LIMIT)) + return FALSE; + info->owner_group[owner_group_len] = '\0'; + } + if (attrs->attrmask.arr[1] & FATTR4_WORD1_SPACE_AVAIL) { + if (!xdr_u_hyper(xdr, &info->space_avail)) + return FALSE; + } + if (attrs->attrmask.arr[1] & FATTR4_WORD1_SPACE_FREE) { + if (!xdr_u_hyper(xdr, &info->space_free)) + return FALSE; + } + if (attrs->attrmask.arr[1] & FATTR4_WORD1_SPACE_TOTAL) { + if (!xdr_u_hyper(xdr, &info->space_total)) + return FALSE; + } + if (attrs->attrmask.arr[1] & FATTR4_WORD1_SYSTEM) { + if (!xdr_bool(xdr, &info->system)) + return FALSE; + } + if (attrs->attrmask.arr[1] & FATTR4_WORD1_TIME_ACCESS) { + if (!xdr_nfstime4(xdr, &info->time_access)) + return FALSE; + } + if (attrs->attrmask.arr[1] & FATTR4_WORD1_TIME_CREATE) { + if (!xdr_nfstime4(xdr, &info->time_create)) + return FALSE; + } + if (attrs->attrmask.arr[1] & FATTR4_WORD1_TIME_DELTA) { + if (!xdr_nfstime4(xdr, info->time_delta)) + return FALSE; + } + if (attrs->attrmask.arr[1] & FATTR4_WORD1_TIME_MODIFY) { + if (!xdr_nfstime4(xdr, &info->time_modify)) + return FALSE; + } + if (attrs->attrmask.arr[1] & FATTR4_WORD1_DACL) { + if (!xdr_nfsdacl41(xdr, info->acl)) + return FALSE; + } + if (attrs->attrmask.arr[1] & FATTR4_WORD1_FS_LAYOUT_TYPE) { + if (!xdr_layout_types(xdr, &info->fs_layout_types)) + return FALSE; + } + } + if (attrs->attrmask.count >= 3) { + if (attrs->attrmask.arr[2] & FATTR4_WORD2_MDSTHRESHOLD) { + if (!xdr_mdsthreshold(xdr, &info->mdsthreshold)) + return FALSE; + } + if (attrs->attrmask.arr[2] & FATTR4_WORD2_SUPPATTR_EXCLCREAT) { + if (!xdr_bitmap4(xdr, info->suppattr_exclcreat)) + return FALSE; + } + } + return TRUE; +} + +static bool_t decode_op_getattr( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_getattr_res *res = (nfs41_getattr_res*)resop->res; + + if (unexpected_op(resop->op, OP_GETATTR)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->status)) + return FALSE; + + if (res->status == NFS4_OK) + { + XDR attr_xdr; + + if (!xdr_fattr4(xdr, &res->obj_attributes)) + return FALSE; + xdrmem_create(&attr_xdr, (char *)res->obj_attributes.attr_vals, res->obj_attributes.attr_vals_len, XDR_DECODE); + return decode_file_attrs(&attr_xdr, &res->obj_attributes, res->info); + } + return TRUE; +} + + +/* + * OP_OPEN + */ +static bool_t encode_createhow4( + XDR *xdr, + createhow4 *ch) +{ + bool_t result = TRUE; + + if (!xdr_u_int32_t(xdr, &ch->mode)) + return FALSE; + + switch (ch->mode) + { + case UNCHECKED4: + case GUARDED4: + result = encode_createattrs4(xdr, ch->createattrs); + break; + case EXCLUSIVE4: + result = xdr_opaque(xdr, (char *)ch->createverf, NFS4_VERIFIER_SIZE); + break; + case EXCLUSIVE4_1: + if (!xdr_opaque(xdr, (char *)ch->createverf, NFS4_VERIFIER_SIZE)) + return FALSE; + if (!encode_createattrs4(xdr, ch->createattrs)) + return FALSE; + break; + } + return result; +} + +static bool_t encode_openflag4( + XDR *xdr, + openflag4 *of) +{ + bool_t result = TRUE; + + if (!xdr_u_int32_t(xdr, &of->opentype)) + return FALSE; + + switch (of->opentype) + { + case OPEN4_CREATE: + result = encode_createhow4(xdr, &of->how); + break; + default: + break; + } + return result; +} + +static bool_t encode_claim_deleg_cur( + XDR *xdr, + stateid4 *stateid, + nfs41_component *name) +{ + if (!xdr_stateid4(xdr, stateid)) + return FALSE; + return encode_component(xdr, name); +} + +static bool_t encode_open_claim4( + XDR *xdr, + open_claim4 *oc) +{ + if (!xdr_u_int32_t(xdr, &oc->claim)) + return FALSE; + + switch (oc->claim) + { + case CLAIM_NULL: + return encode_component(xdr, oc->u.null.filename); + case CLAIM_PREVIOUS: + return xdr_u_int32_t(xdr, &oc->u.prev.delegate_type); + case CLAIM_FH: + return TRUE; /* use current file handle */ + case CLAIM_DELEGATE_CUR: + return encode_claim_deleg_cur(xdr, + &oc->u.deleg_cur.delegate_stateid->stateid, + oc->u.deleg_cur.name); + case CLAIM_DELEG_CUR_FH: + return xdr_stateid4(xdr, + &oc->u.deleg_cur_fh.delegate_stateid->stateid); + case CLAIM_DELEGATE_PREV: + return encode_component(xdr, oc->u.deleg_prev.filename); + case CLAIM_DELEG_PREV_FH: + return TRUE; /* use current file handle */ + default: + eprintf("encode_open_claim4: unsupported claim %d.\n", + oc->claim); + return FALSE; + } +} + +static bool_t encode_op_open( + XDR *xdr, + nfs_argop4 *argop) +{ + nfs41_op_open_args *args = (nfs41_op_open_args*)argop->arg; + + if (unexpected_op(argop->op, OP_OPEN)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &args->seqid)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &args->share_access)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &args->share_deny)) + return FALSE; + + if (!xdr_state_owner4(xdr, args->owner)) + return FALSE; + + if (!encode_openflag4(xdr, &args->openhow)) + return FALSE; + + return encode_open_claim4(xdr, args->claim); +} + +static bool_t decode_open_none_delegation4( + XDR *xdr, + open_delegation4 *delegation) +{ + enum_t why_no_deleg; + bool_t will_signal; + + if (!xdr_enum(xdr, (enum_t*)&why_no_deleg)) + return FALSE; + + switch (why_no_deleg) + { + case WND4_CONTENTION: + case WND4_RESOURCE: + return xdr_bool(xdr, &will_signal); + default: + return TRUE; + } +} + +static bool_t decode_open_read_delegation4( + XDR *xdr, + open_delegation4 *delegation) +{ + if (!xdr_stateid4(xdr, &delegation->stateid)) + return FALSE; + + if (!xdr_bool(xdr, &delegation->recalled)) + return FALSE; + + return xdr_nfsace4(xdr, &delegation->permissions); +} + +static bool_t decode_modified_limit4( + XDR *xdr, + uint64_t *filesize) +{ + uint32_t blocks, bytes_per_block; + + if (!xdr_u_int32_t(xdr, &blocks)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &bytes_per_block)) + return FALSE; + + *filesize = blocks * bytes_per_block; + return TRUE; +} + +enum limit_by4 { + NFS_LIMIT_SIZE = 1, + NFS_LIMIT_BLOCKS = 2 +}; + +static bool_t decode_space_limit4( + XDR *xdr, + uint64_t *filesize) +{ + uint32_t limitby; + + if (!xdr_u_int32_t(xdr, &limitby)) + return FALSE; + + switch (limitby) + { + case NFS_LIMIT_SIZE: + return xdr_u_hyper(xdr, filesize); + case NFS_LIMIT_BLOCKS: + return decode_modified_limit4(xdr, filesize); + default: + eprintf("decode_space_limit4: limitby %d invalid\n", limitby); + return FALSE; + } +} + +static bool_t decode_open_write_delegation4( + XDR *xdr, + open_delegation4 *delegation) +{ + uint64_t size_limit; + + if (!xdr_stateid4(xdr, &delegation->stateid)) + return FALSE; + + if (!xdr_bool(xdr, &delegation->recalled)) + return FALSE; + + if (!decode_space_limit4(xdr, &size_limit)) + return FALSE; + + return xdr_nfsace4(xdr, &delegation->permissions); +} + +static bool_t decode_open_res_ok( + XDR *xdr, + nfs41_op_open_res_ok *res) +{ + if (!xdr_stateid4(xdr, res->stateid)) + return FALSE; + + if (!xdr_change_info4(xdr, &res->cinfo)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->rflags)) + return FALSE; + + if (!xdr_bitmap4(xdr, &res->attrset)) + return FALSE; + + if (!xdr_enum(xdr, (enum_t*)&res->delegation->type)) + return FALSE; + + switch (res->delegation->type) + { + case OPEN_DELEGATE_NONE: + return TRUE; + case OPEN_DELEGATE_NONE_EXT: + return decode_open_none_delegation4(xdr, res->delegation); + case OPEN_DELEGATE_READ: + return decode_open_read_delegation4(xdr, res->delegation); + case OPEN_DELEGATE_WRITE: + return decode_open_write_delegation4(xdr, res->delegation); + default: + eprintf("decode_open_res_ok: delegation type %d not " + "supported.\n", res->delegation->type); + return FALSE; + } +} + +static bool_t decode_op_open( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_op_open_res *res = (nfs41_op_open_res*)resop->res; + + if (unexpected_op(resop->op, OP_OPEN)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->status)) + return FALSE; + + if (res->status == NFS4_OK) + return decode_open_res_ok(xdr, &res->resok4); + + return TRUE; +} + + +/* + * OP_OPENATTR + */ +static bool_t encode_op_openattr( + XDR *xdr, + nfs_argop4 *argop) +{ + nfs41_openattr_args *args = (nfs41_openattr_args*)argop->arg; + + if (unexpected_op(argop->op, OP_OPENATTR)) + return FALSE; + + return xdr_bool(xdr, &args->createdir); +} + +static bool_t decode_op_openattr( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_openattr_res *res = (nfs41_openattr_res*)resop->res; + + if (unexpected_op(resop->op, OP_OPENATTR)) + return FALSE; + + return xdr_u_int32_t(xdr, &res->status); +} + + +/* + * OP_READ + */ +static bool_t encode_op_read( + XDR *xdr, + nfs_argop4 *argop) +{ + nfs41_read_args *args = (nfs41_read_args*)argop->arg; + + if (unexpected_op(argop->op, OP_READ)) + return FALSE; + + if (!xdr_stateid4(xdr, &args->stateid->stateid)) + return FALSE; + + if (!xdr_u_hyper(xdr, &args->offset)) + return FALSE; + + return xdr_u_int32_t(xdr, &args->count); +} + +static bool_t decode_read_res_ok( + XDR *xdr, + nfs41_read_res_ok *res) +{ + unsigned char *data = res->data; + + if (!xdr_bool(xdr, &res->eof)) + return FALSE; + + return xdr_bytes(xdr, (char **)&data, &res->data_len, NFS41_MAX_FILEIO_SIZE); +} + +static bool_t decode_op_read( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_read_res *res = (nfs41_read_res*)resop->res; + + if (unexpected_op(resop->op, OP_READ)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->status)) + return FALSE; + + if (res->status == NFS4_OK) + return decode_read_res_ok(xdr, &res->resok4); + + return TRUE; +} + + +/* + * OP_READDIR + */ +static bool_t encode_op_readdir( + XDR *xdr, + nfs_argop4 *argop) +{ + nfs41_readdir_args *args = (nfs41_readdir_args*)argop->arg; + + if (unexpected_op(argop->op, OP_READDIR)) + return FALSE; + + if (!xdr_u_hyper(xdr, &args->cookie.cookie)) + return FALSE; + + if (!xdr_opaque(xdr, (char *)args->cookie.verf, NFS4_VERIFIER_SIZE)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &args->dircount)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &args->maxcount)) + return FALSE; + + return xdr_bitmap4(xdr, args->attr_request); +} + +typedef struct __readdir_entry_iterator { + unsigned char *buf_pos; + uint32_t remaining_len; + uint32_t *last_entry_offset; + bool_t ignore_the_rest; + bool_t has_next_entry; +} readdir_entry_iterator; + +static bool_t decode_readdir_entry( + XDR *xdr, + readdir_entry_iterator *it) +{ + uint64_t cookie; + unsigned char name[NFS4_OPAQUE_LIMIT]; + unsigned char *nameptr = &name[0]; + uint32_t name_len, entry_len; + fattr4 attrs; + + /* decode into temporaries so we can determine if there's enough + * room in the buffer for this entry */ + ZeroMemory(name, NFS4_OPAQUE_LIMIT); + name_len = NFS4_OPAQUE_LIMIT; + entry_len = (uint32_t)FIELD_OFFSET(nfs41_readdir_entry, name); + attrs.attr_vals_len = NFS4_OPAQUE_LIMIT; + + if (!xdr_u_hyper(xdr, &cookie)) + return FALSE; + + if (!xdr_bytes(xdr, (char **)&nameptr, &name_len, NFS4_OPAQUE_LIMIT)) + return FALSE; + + if (!xdr_fattr4(xdr, &attrs)) + return FALSE; + + if (!xdr_bool(xdr, &it->has_next_entry)) + return FALSE; + + if (it->ignore_the_rest) + return TRUE; + + name_len += 1; /* account for null terminator */ + if (entry_len + name_len <= it->remaining_len) + { + XDR fattr_xdr; + nfs41_readdir_entry *entry = (nfs41_readdir_entry*)it->buf_pos; + entry->cookie = cookie; + entry->name_len = name_len; + + if (it->has_next_entry) + entry->next_entry_offset = entry_len + name_len; + else + entry->next_entry_offset = 0; + + xdrmem_create(&fattr_xdr, (char *)attrs.attr_vals, attrs.attr_vals_len, XDR_DECODE); + if (!(decode_file_attrs(&fattr_xdr, &attrs, &entry->attr_info))) + entry->attr_info.rdattr_error = NFS4ERR_BADXDR; + StringCchCopyA(entry->name, name_len, (STRSAFE_LPCSTR)name); + + it->buf_pos += entry_len + name_len; + it->remaining_len -= entry_len + name_len; + it->last_entry_offset = &entry->next_entry_offset; + } + else if (it->last_entry_offset) + { + *(it->last_entry_offset) = 0; + it->ignore_the_rest = 1; + } + + return TRUE; +} + +static bool_t decode_readdir_list( + XDR *xdr, + nfs41_readdir_list *dirs) +{ + readdir_entry_iterator iter; + iter.buf_pos = dirs->entries; + iter.remaining_len = dirs->entries_len; + iter.last_entry_offset = NULL; + iter.ignore_the_rest = 0; + iter.has_next_entry = 0; + + if (!xdr_bool(xdr, &dirs->has_entries)) + return FALSE; + + if (dirs->has_entries) + { + do { + if (!decode_readdir_entry(xdr, &iter)) + return FALSE; + + } while (iter.has_next_entry); + } + dirs->entries_len -= iter.remaining_len; + + if (!xdr_bool(xdr, &dirs->eof)) + return FALSE; + + /* reset eof if we couldn't fit everything in the buffer */ + if (iter.ignore_the_rest) + dirs->eof = 0; + return TRUE; +} + +static bool_t decode_op_readdir( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_readdir_res *res = (nfs41_readdir_res*)resop->res; + + if (unexpected_op(resop->op, OP_READDIR)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->status)) + return FALSE; + + if (res->status == NFS4_OK) { + if (!xdr_opaque(xdr, (char *)res->cookieverf, NFS4_VERIFIER_SIZE)) + return FALSE; + return decode_readdir_list(xdr, &res->reply); + } + return TRUE; +} + + +/* + * OP_READLINK + */ +static bool_t encode_op_readlink( + XDR *xdr, + nfs_argop4 *argop) +{ + if (unexpected_op(argop->op, OP_READLINK)) + return FALSE; + + /* void */ + return TRUE; +} + +static bool_t decode_op_readlink( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_readlink_res *res = (nfs41_readlink_res*)resop->res; + + if (unexpected_op(resop->op, OP_READLINK)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->status)) + return FALSE; + + if (res->status == NFS4_OK) { + char *link = res->link; + return xdr_bytes(xdr, &link, &res->link_len, res->link_len); + } + + return TRUE; +} + + +/* + * OP_REMOVE + */ +static bool_t encode_op_remove( + XDR *xdr, + nfs_argop4 *argop) +{ + nfs41_remove_args *args = (nfs41_remove_args*)argop->arg; + + if (unexpected_op(argop->op, OP_REMOVE)) + return FALSE; + + return encode_component(xdr, args->target); +} + +static bool_t decode_op_remove( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_remove_res *res = (nfs41_remove_res*)resop->res; + + if (unexpected_op(resop->op, OP_REMOVE)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->status)) + return FALSE; + + if (res->status == NFS4_OK) + return xdr_change_info4(xdr, &res->cinfo); + + return TRUE; +} + + +/* + * OP_RENAME + */ +static bool_t encode_op_rename( + XDR *xdr, + nfs_argop4 *argop) +{ + nfs41_rename_args *args = (nfs41_rename_args*)argop->arg; + + if (unexpected_op(argop->op, OP_RENAME)) + return FALSE; + + if (!encode_component(xdr, args->oldname)) + return FALSE; + + return encode_component(xdr, args->newname); +} + +static bool_t decode_op_rename( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_rename_res *res = (nfs41_rename_res*)resop->res; + + if (unexpected_op(resop->op, OP_RENAME)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->status)) + return FALSE; + + if (res->status == NFS4_OK) + { + if (!xdr_change_info4(xdr, &res->source_cinfo)) + return FALSE; + return xdr_change_info4(xdr, &res->target_cinfo); + } + return TRUE; +} + + +/* + * OP_RESTOREFH + */ +static bool_t encode_op_restorefh( + XDR *xdr, + nfs_argop4 *argop) +{ + if (unexpected_op(argop->op, OP_RESTOREFH)) + return FALSE; + + /* void */ + return TRUE; +} + +static bool_t decode_op_restorefh( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_restorefh_res *res = (nfs41_restorefh_res*)resop->res; + + if (unexpected_op(resop->op, OP_RESTOREFH)) + return FALSE; + + return xdr_u_int32_t(xdr, &res->status); +} + + +/* + * OP_SAVEFH + */ +static bool_t encode_op_savefh( + XDR *xdr, + nfs_argop4 *argop) +{ + if (unexpected_op(argop->op, OP_SAVEFH)) + return FALSE; + + /* void */ + return TRUE; +} + +static bool_t decode_op_savefh( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_savefh_res *res = (nfs41_savefh_res*)resop->res; + + if (unexpected_op(resop->op, OP_SAVEFH)) + return FALSE; + + return xdr_u_int32_t(xdr, &res->status); +} + + +/* + * OP_SETATTR + */ +static bool_t encode_file_attrs( + fattr4 *attrs, + nfs41_file_info *info) +{ + uint32_t i; + XDR localxdr; + + xdrmem_create(&localxdr, (char *)attrs->attr_vals, NFS4_OPAQUE_LIMIT, XDR_ENCODE); + + attrs->attr_vals_len = 0; + ZeroMemory(&attrs->attrmask, sizeof(bitmap4)); + attrs->attrmask.count = info->attrmask.count; + + if (info->attrmask.count > 0) { + if (info->attrmask.arr[0] & FATTR4_WORD0_SIZE) { + if (!xdr_u_hyper(&localxdr, &info->size)) + return FALSE; + attrs->attrmask.arr[0] |= FATTR4_WORD0_SIZE; + } + if (info->attrmask.arr[0] & FATTR4_WORD0_ACL) { + if (!xdr_nfsacl41(&localxdr, info->acl)) + return FALSE; + attrs->attrmask.arr[0] |= FATTR4_WORD0_ACL; + } + if (info->attrmask.arr[0] & FATTR4_WORD0_ARCHIVE) { + if (!xdr_bool(&localxdr, &info->archive)) + return FALSE; + attrs->attrmask.arr[0] |= FATTR4_WORD0_ARCHIVE; + } + if (info->attrmask.arr[0] & FATTR4_WORD0_HIDDEN) { + if (!xdr_bool(&localxdr, &info->hidden)) + return FALSE; + attrs->attrmask.arr[0] |= FATTR4_WORD0_HIDDEN; + } + } + if (info->attrmask.count > 1) { + if (info->attrmask.arr[1] & FATTR4_WORD1_MODE) { + if (!xdr_u_int32_t(&localxdr, &info->mode)) + return FALSE; + attrs->attrmask.arr[1] |= FATTR4_WORD1_MODE; + } + if (info->attrmask.arr[1] & FATTR4_WORD1_SYSTEM) { + if (!xdr_bool(&localxdr, &info->system)) + return FALSE; + attrs->attrmask.arr[1] |= FATTR4_WORD1_SYSTEM; + } + if (info->attrmask.arr[1] & FATTR4_WORD1_TIME_ACCESS_SET) { + if (!xdr_settime4(&localxdr, &info->time_access, info->time_delta)) + return FALSE; + attrs->attrmask.arr[1] |= FATTR4_WORD1_TIME_ACCESS_SET; + } + if (info->attrmask.arr[1] & FATTR4_WORD1_TIME_CREATE) { + if (!xdr_nfstime4(&localxdr, &info->time_create)) + return FALSE; + attrs->attrmask.arr[1] |= FATTR4_WORD1_TIME_CREATE; + } + if (info->attrmask.arr[1] & FATTR4_WORD1_TIME_MODIFY_SET) { + if (!xdr_settime4(&localxdr, &info->time_modify, info->time_delta)) + return FALSE; + attrs->attrmask.arr[1] |= FATTR4_WORD1_TIME_MODIFY_SET; + } + if (info->attrmask.arr[1] & FATTR4_WORD1_OWNER) { + char *ptr = &info->owner[0]; + uint32_t owner_len = (uint32_t)strlen(info->owner); + if (!xdr_bytes(&localxdr, &ptr, &owner_len, + NFS4_OPAQUE_LIMIT)) + return FALSE; + attrs->attrmask.arr[1] |= FATTR4_WORD1_OWNER; + } + if (info->attrmask.arr[1] & FATTR4_WORD1_OWNER_GROUP) { + char *ptr = &info->owner_group[0]; + uint32_t owner_group_len = (uint32_t)strlen(info->owner_group); + if (!xdr_bytes(&localxdr, &ptr, &owner_group_len, + NFS4_OPAQUE_LIMIT)) + return FALSE; + attrs->attrmask.arr[1] |= FATTR4_WORD1_OWNER_GROUP; + } + } + if (info->attrmask.count > 2) { + if (info->attrmask.arr[2] & FATTR4_WORD2_MODE_SET_MASKED) { + if (!xdr_u_int32_t(&localxdr, &info->mode)) + return FALSE; + if (!xdr_u_int32_t(&localxdr, &info->mode_mask)) + return FALSE; + attrs->attrmask.arr[2] |= FATTR4_WORD2_MODE_SET_MASKED; + } + } + + /* warn if we try to set attributes that aren't handled */ + for (i = 0; i < info->attrmask.count; i++) + if (attrs->attrmask.arr[i] != info->attrmask.arr[i]) + eprintf("encode_file_attrs() attempted to encode extra " + "attributes in arr[%d]: encoded %d, but wanted %d.\n", + i, attrs->attrmask.arr[i], info->attrmask.arr[i]); + + attrs->attr_vals_len = xdr_getpos(&localxdr); + return TRUE; +} + +static bool_t encode_op_setattr( + XDR *xdr, + nfs_argop4 *argop) +{ + nfs41_setattr_args *args = (nfs41_setattr_args*)argop->arg; + fattr4 attrs; + + if (unexpected_op(argop->op, OP_SETATTR)) + return FALSE; + + if (!xdr_stateid4(xdr, &args->stateid->stateid)) + return FALSE; + + /* encode attribute values from args->info into attrs.attr_vals */ + attrs.attr_vals_len = NFS4_OPAQUE_LIMIT; + if (!encode_file_attrs(&attrs, args->info)) + return FALSE; + + return xdr_fattr4(xdr, &attrs); +} + +static bool_t decode_op_setattr( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_setattr_res *res = (nfs41_setattr_res*)resop->res; + + if (unexpected_op(resop->op, OP_SETATTR)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->status)) + return FALSE; + + if (res->status == NFS4_OK) + return xdr_bitmap4(xdr, &res->attrsset); + + return TRUE; +} + + +/* + * OP_WANT_DELEGATION + */ +static bool_t encode_op_want_delegation( + XDR *xdr, + nfs_argop4 *argop) +{ + nfs41_want_delegation_args *args = (nfs41_want_delegation_args*)argop->arg; + + if (unexpected_op(argop->op, OP_WANT_DELEGATION)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &args->want)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &args->claim->claim)) + return FALSE; + + return args->claim->claim != CLAIM_PREVIOUS || + xdr_u_int32_t(xdr, &args->claim->prev_delegate_type); +} + +static bool_t decode_op_want_delegation( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_want_delegation_res *res = (nfs41_want_delegation_res*)resop->res; + + if (unexpected_op(resop->op, OP_WANT_DELEGATION)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->status)) + return FALSE; + + if (res->status) + return TRUE; + + if (!xdr_enum(xdr, (enum_t*)&res->delegation->type)) + return FALSE; + + switch (res->delegation->type) + { + case OPEN_DELEGATE_NONE: + return TRUE; + case OPEN_DELEGATE_NONE_EXT: + return decode_open_none_delegation4(xdr, res->delegation); + case OPEN_DELEGATE_READ: + return decode_open_read_delegation4(xdr, res->delegation); + case OPEN_DELEGATE_WRITE: + return decode_open_write_delegation4(xdr, res->delegation); + default: + eprintf("decode_open_res_ok: delegation type %d not " + "supported.\n", res->delegation->type); + return FALSE; + } +} + + +/* + * OP_FREE_STATEID + */ +static bool_t encode_op_free_stateid( + XDR *xdr, + nfs_argop4 *argop) +{ + nfs41_free_stateid_args *args = (nfs41_free_stateid_args*)argop->arg; + + if (unexpected_op(argop->op, OP_FREE_STATEID)) + return FALSE; + + return xdr_stateid4(xdr, args->stateid); +} + +static bool_t decode_op_free_stateid( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_free_stateid_res *res = (nfs41_free_stateid_res*)resop->res; + + if (unexpected_op(resop->op, OP_FREE_STATEID)) + return FALSE; + + return xdr_u_int32_t(xdr, &res->status); +} + + +/* + * OP_TEST_STATEID + */ +static bool_t encode_op_test_stateid( + XDR *xdr, + nfs_argop4 *argop) +{ + nfs41_test_stateid_args *args = (nfs41_test_stateid_args*)argop->arg; + + if (unexpected_op(argop->op, OP_TEST_STATEID)) + return FALSE; + + return xdr_array(xdr, (char**)&args->stateids, &args->count, + args->count, sizeof(stateid_arg), (xdrproc_t)xdr_stateid4); +} + +static bool_t decode_op_test_stateid( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_test_stateid_res *res = (nfs41_test_stateid_res*)resop->res; + + if (unexpected_op(resop->op, OP_TEST_STATEID)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->status)) + return FALSE; + + if (res->status == NFS4_OK) { + return xdr_array(xdr, (char**)&res->resok.status, &res->resok.count, + res->resok.count, sizeof(uint32_t), (xdrproc_t)xdr_u_int32_t); + } + return TRUE; +} + + +/* + * OP_WRITE + */ +static bool_t encode_op_write( + XDR *xdr, + nfs_argop4 *argop) +{ + nfs41_write_args *args = (nfs41_write_args*)argop->arg; + unsigned char *data = args->data; + + if (unexpected_op(argop->op, OP_WRITE)) + return FALSE; + + if (!xdr_stateid4(xdr, &args->stateid->stateid)) + return FALSE; + + if (!xdr_u_hyper(xdr, &args->offset)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &args->stable)) + return FALSE; + + return xdr_bytes(xdr, (char **)&data, &args->data_len, NFS41_MAX_FILEIO_SIZE); +} + +static bool_t xdr_write_verf( + XDR *xdr, + nfs41_write_verf *verf) +{ + if (!xdr_enum(xdr, (enum_t *)&verf->committed)) + return FALSE; + + return xdr_opaque(xdr, (char *)verf->verf, NFS4_VERIFIER_SIZE); +} + +static bool_t xdr_write_res_ok( + XDR *xdr, + nfs41_write_res_ok *res) +{ + if (!xdr_u_int32_t(xdr, &res->count)) + return FALSE; + + return xdr_write_verf(xdr, res->verf); +} + +static bool_t decode_op_write( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_write_res *res = (nfs41_write_res*)resop->res; + + if (unexpected_op(resop->op, OP_WRITE)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->status)) + return FALSE; + + if (res->status == NFS4_OK) + return xdr_write_res_ok(xdr, &res->resok4); + + return TRUE; +} + +/* + * OP_SECINFO_NO_NAME + */ +static bool_t xdr_secinfo( + XDR *xdr, + nfs41_secinfo_info *secinfo) +{ + if (!xdr_u_int32_t(xdr, &secinfo->sec_flavor)) + return FALSE; + if (secinfo->sec_flavor == RPCSEC_GSS) { + char *p = secinfo->oid; + if (!xdr_bytes(xdr, (char **)&p, &secinfo->oid_len, MAX_OID_LEN)) + return FALSE; + if (!xdr_u_int32_t(xdr, &secinfo->qop)) + return FALSE; + if (!xdr_enum(xdr, (enum_t *)&secinfo->type)) + return FALSE; + } + return TRUE; +} + +static bool_t encode_op_secinfo_noname( + XDR *xdr, + nfs_argop4 *argop) +{ + nfs41_secinfo_noname_args *args = (nfs41_secinfo_noname_args *)argop->arg; + + if (unexpected_op(argop->op, OP_SECINFO_NO_NAME)) + return FALSE; + + if (!xdr_enum(xdr, (enum_t *)&args->type)) + return FALSE; + + return TRUE; +} + +static bool_t decode_op_secinfo_noname( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_secinfo_noname_res *res = (nfs41_secinfo_noname_res *)resop->res; + nfs41_secinfo_info *secinfo = res->secinfo; + if (unexpected_op(resop->op, OP_SECINFO_NO_NAME)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->status)) + return FALSE; + + if (res->status == NFS4_OK) + return xdr_array(xdr, (char**)&secinfo, &res->count, + MAX_SECINFOS, sizeof(nfs41_secinfo_info), (xdrproc_t)xdr_secinfo); + + return TRUE; +} + +/* + * OP_SECINFO + */ +static bool_t encode_op_secinfo( + XDR *xdr, + nfs_argop4 *argop) +{ + nfs41_secinfo_args *args = (nfs41_secinfo_args *)argop->arg; + + if (unexpected_op(argop->op, OP_SECINFO)) + return FALSE; + + if (!encode_component(xdr, args->name)) + return FALSE; + + return TRUE; +} + +static bool_t decode_op_secinfo( + XDR *xdr, + nfs_resop4 *resop) +{ + nfs41_secinfo_noname_res *res = (nfs41_secinfo_noname_res *)resop->res; + nfs41_secinfo_info *secinfo = res->secinfo; + + if (unexpected_op(resop->op, OP_SECINFO)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->status)) + return FALSE; + + if (res->status == NFS4_OK) + return xdr_array(xdr, (char**)&secinfo, &res->count, + MAX_SECINFOS, sizeof(nfs41_secinfo_info), (xdrproc_t)xdr_secinfo); + + return TRUE; +} +/* + * OP_GETDEVICEINFO + */ +static bool_t encode_op_getdeviceinfo( + XDR *xdr, + nfs_argop4 *argop) +{ + pnfs_getdeviceinfo_args *args = (pnfs_getdeviceinfo_args*)argop->arg; + + if (unexpected_op(argop->op, OP_GETDEVICEINFO)) + return FALSE; + + if (!xdr_opaque(xdr, (char *)args->deviceid, PNFS_DEVICEID_SIZE)) + return FALSE; + + if (!xdr_enum(xdr, (enum_t *)&args->layout_type)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &args->maxcount)) + return FALSE; + + return xdr_bitmap4(xdr, &args->notify_types); +} + +static bool_t xdr_stripe_indices( + XDR *xdr, + pnfs_stripe_indices *indices) +{ + uint32_t i, count; + + if (!xdr_u_int32_t(xdr, &count)) + return FALSE; + + if (count && count != indices->count) { + uint32_t *tmp; + tmp = realloc(indices->arr, count * sizeof(uint32_t)); + if (tmp == NULL) + return FALSE; + indices->arr = tmp; + ZeroMemory(indices->arr, count * sizeof(uint32_t)); + indices->count = count; + } + + for (i = 0; i < indices->count; i++) { + if (!xdr_u_int32_t(xdr, &indices->arr[i])) + return FALSE; + } + return TRUE; +} + +static bool_t xdr_pnfs_addr( + XDR *xdr, + netaddr4 *addr) +{ + uint32_t len; + char *netid = addr->netid; + char *uaddr = addr->uaddr; + + if (xdr->x_op == XDR_ENCODE) + len = sizeof(addr->netid); + if (!xdr_bytes(xdr, &netid, &len, NFS41_NETWORK_ID_LEN)) + return FALSE; + + if (xdr->x_op == XDR_DECODE) { + if (len < NFS41_NETWORK_ID_LEN) + addr->netid[len] = 0; + else + addr->netid[NFS41_NETWORK_ID_LEN] = 0; + } + + if (xdr->x_op == XDR_ENCODE) + len = sizeof(addr->uaddr); + if (!xdr_bytes(xdr, &uaddr, &len, NFS41_UNIVERSAL_ADDR_LEN)) + return FALSE; + + if (xdr->x_op == XDR_DECODE){ + if (len < NFS41_UNIVERSAL_ADDR_LEN) + addr->uaddr[len] = 0; + else + addr->uaddr[NFS41_UNIVERSAL_ADDR_LEN] = 0; + } + + return TRUE; +} + +static bool_t xdr_multi_addr( + XDR *xdr, + multi_addr4 *list) +{ + netaddr4 dummy, *dest; + uint32_t i; + + if (!xdr_u_int32_t(xdr, &list->count)) + return FALSE; + + for (i = 0; i < list->count; i++) { + /* if there are too many addrs, decode the extras into 'dummy' */ + dest = i < NFS41_ADDRS_PER_SERVER ? &list->arr[i] : &dummy; + + if (!xdr_pnfs_addr(xdr, dest)) + return FALSE; + } + return TRUE; +} + +static bool_t xdr_data_server_list( + XDR *xdr, + pnfs_data_server_list *servers) +{ + uint32_t i, count; + + if (!xdr_u_int32_t(xdr, &count)) + return FALSE; + + if (count && count != servers->count) { + pnfs_data_server *tmp; + /* clear data server clients; they're still cached with nfs41_root, + * so pnfs_data_server_client() will look them up again */ + for (i = 0; i < servers->count; i++) + servers->arr[i].client = NULL; + + tmp = realloc(servers->arr, count * sizeof(pnfs_data_server)); + if (tmp == NULL) + return FALSE; + servers->arr = tmp; + ZeroMemory(servers->arr, count * sizeof(pnfs_data_server)); + for (i = servers->count; i < count; i++) /* initialize new elements */ + InitializeSRWLock(&servers->arr[i].lock); + servers->count = count; + } + + for (i = 0; i < servers->count; i++) { + if (!xdr_multi_addr(xdr, &servers->arr[i].addrs)) + return FALSE; + } + return TRUE; +} + +static bool_t xdr_file_device( + XDR *xdr, + pnfs_file_device *device) +{ + if (!xdr_stripe_indices(xdr, &device->stripes)) + return FALSE; + + return xdr_data_server_list(xdr, &device->servers); +} + +static bool_t decode_getdeviceinfo_ok( + XDR *xdr, + pnfs_getdeviceinfo_res_ok *res_ok) +{ + u_int32_t len_ignored; + + if (!xdr_enum(xdr, (enum_t *)&res_ok->device->device.type)) + return FALSE; + + if (res_ok->device->device.type != PNFS_LAYOUTTYPE_FILE) + return FALSE; + + if (!xdr_u_int32_t(xdr, &len_ignored)) + return FALSE; + + if (!xdr_file_device(xdr, res_ok->device)) + return FALSE; + + return xdr_bitmap4(xdr, &res_ok->notification); +} + +static bool_t decode_op_getdeviceinfo( + XDR *xdr, + nfs_resop4 *resop) +{ + pnfs_getdeviceinfo_res *res = (pnfs_getdeviceinfo_res*)resop->res; + + if (unexpected_op(resop->op, OP_GETDEVICEINFO)) + return FALSE; + + if (!xdr_u_int32_t(xdr, (uint32_t *)&res->status)) + return FALSE; + + switch (res->status) { + case NFS4_OK: + return decode_getdeviceinfo_ok(xdr, &res->u.res_ok); + break; + case NFS4ERR_TOOSMALL: + { + uint32_t ignored; + return xdr_u_int32_t(xdr, &ignored); + } + break; + } + return TRUE; +} + + +/* + * OP_LAYOUTCOMMIT + */ +static bool_t encode_op_layoutcommit( + XDR *xdr, + nfs_argop4 *argop) +{ + pnfs_layoutcommit_args *args = (pnfs_layoutcommit_args*)argop->arg; + bool_t false_bool = FALSE; + bool_t true_bool = TRUE; + enum_t pnfs_layout = PNFS_LAYOUTTYPE_FILE; + uint32_t zero = 0; + + if (unexpected_op(argop->op, OP_LAYOUTCOMMIT)) + return FALSE; + + if (!xdr_u_hyper(xdr, &args->offset)) + return FALSE; + + if (!xdr_u_hyper(xdr, &args->length)) + return FALSE; + + if (!xdr_bool(xdr, &false_bool)) /* loca_reclaim = 0 */ + return FALSE; + + if (!xdr_stateid4(xdr, args->stateid)) + return FALSE; + + /* loca_last_write_offset */ + if (args->new_offset) { + if (!xdr_bool(xdr, &true_bool)) + return FALSE; + + if (!xdr_u_hyper(xdr, args->new_offset)) + return FALSE; + } else { + if (!xdr_bool(xdr, &false_bool)) + return FALSE; + } + + /* loca_time_modify */ + if (args->new_time) { + if (!xdr_bool(xdr, &true_bool)) + return FALSE; + + if (!xdr_nfstime4(xdr, args->new_time)) + return FALSE; + } else { + if (!xdr_bool(xdr, &false_bool)) + return FALSE; + } + + /* loca_layoutupdate */ + if (!xdr_enum(xdr, &pnfs_layout)) + return FALSE; + + return xdr_u_int32_t(xdr, &zero); +} + +static bool_t decode_op_layoutcommit( + XDR *xdr, + nfs_resop4 *resop) +{ + pnfs_layoutcommit_res *res = (pnfs_layoutcommit_res*)resop->res; + + if (unexpected_op(resop->op, OP_LAYOUTCOMMIT)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->status)) + return FALSE; + + if (res->status == NFS4_OK) { + if (!xdr_bool(xdr, &res->has_new_size)) + return FALSE; + + if (res->has_new_size) + if (!xdr_u_hyper(xdr, &res->new_size)) + return FALSE; + } + return TRUE; +} + +/* + * OP_LAYOUTGET + */ +static bool_t encode_op_layoutget( + XDR *xdr, + nfs_argop4 *argop) +{ + pnfs_layoutget_args *args = (pnfs_layoutget_args*)argop->arg; + + if (unexpected_op(argop->op, OP_LAYOUTGET)) + return FALSE; + + if (!xdr_bool(xdr, &args->signal_layout_avail)) + return FALSE; + + if (!xdr_u_int32_t(xdr, (u_int32_t *)&args->layout_type)) + return FALSE; + + if (!xdr_u_int32_t(xdr, (u_int32_t *)&args->iomode)) + return FALSE; + + if (!xdr_u_hyper(xdr, &args->offset)) + return FALSE; + + if (!xdr_u_hyper(xdr, &args->length)) + return FALSE; + + if (!xdr_u_hyper(xdr, &args->minlength)) + return FALSE; + + if (!xdr_stateid4(xdr, &args->stateid->stateid)) + return FALSE; + + return xdr_u_int32_t(xdr, &args->maxcount); +} + +static bool_t decode_file_layout_handles( + XDR *xdr, + pnfs_file_layout_handles *handles) +{ + uint32_t i, count; + + if (!xdr_u_int32_t(xdr, &count)) + return FALSE; + + if (count && count != handles->count) { + nfs41_path_fh *tmp; + tmp = realloc(handles->arr, count * sizeof(nfs41_path_fh)); + if (tmp == NULL) + return FALSE; + handles->arr = tmp; + ZeroMemory(handles->arr, count * sizeof(nfs41_path_fh)); + handles->count = count; + } + + for (i = 0; i < handles->count; i++) { + if (!xdr_fh(xdr, &handles->arr[i].fh)) + return FALSE; + } + return TRUE; +} + +static bool_t decode_file_layout( + XDR *xdr, + struct list_entry *list, + pnfs_layout *base) +{ + pnfs_file_layout *layout; + u_int32_t len_ignored; + + if (!xdr_u_int32_t(xdr, &len_ignored)) + return FALSE; + + layout = calloc(1, sizeof(pnfs_file_layout)); + if (layout == NULL) + return FALSE; + + layout->layout.offset = base->offset; + layout->layout.length = base->length; + layout->layout.iomode = base->iomode; + layout->layout.type = base->type; + list_init(&layout->layout.entry); + + if (!xdr_opaque(xdr, (char *)layout->deviceid, PNFS_DEVICEID_SIZE)) + goto out_error; + + if (!xdr_u_int32_t(xdr, &layout->util)) + goto out_error; + + if (!xdr_u_int32_t(xdr, &layout->first_index)) + goto out_error; + + if (!xdr_u_hyper(xdr, &layout->pattern_offset)) + goto out_error; + + if (!decode_file_layout_handles(xdr, &layout->filehandles)) + goto out_error; + + list_add_tail(list, &layout->layout.entry); + return TRUE; + +out_error: + free(layout); + return FALSE; +} + +static bool_t decode_layout( + XDR *xdr, + struct list_entry *list) +{ + pnfs_layout layout; + + if (!xdr_u_hyper(xdr, &layout.offset)) + return FALSE; + + if (!xdr_u_hyper(xdr, &layout.length)) + return FALSE; + + if (!xdr_enum(xdr, (enum_t *)&layout.iomode)) + return FALSE; + + if (!xdr_enum(xdr, (enum_t *)&layout.type)) + return FALSE; + + switch (layout.type) { + case PNFS_LAYOUTTYPE_FILE: + return decode_file_layout(xdr, list, &layout); + + default: + eprintf("%s: received non-FILE layout type, %d\n", + "decode_file_layout", layout.type); + } + return FALSE; +} + +static bool_t decode_layout_res_ok( + XDR *xdr, + pnfs_layoutget_res_ok *res) +{ + uint32_t i; + + if (!xdr_bool(xdr, &res->return_on_close)) + return FALSE; + + if (!xdr_stateid4(xdr, &res->stateid)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &res->count)) + return FALSE; + + for (i = 0; i < res->count; i++) + if (!decode_layout(xdr, &res->layouts)) + return FALSE; + return TRUE; +} + +static bool_t decode_op_layoutget( + XDR *xdr, + nfs_resop4 *resop) +{ + pnfs_layoutget_res *res = (pnfs_layoutget_res*)resop->res; + + if (unexpected_op(resop->op, OP_LAYOUTGET)) + return FALSE; + + if (!xdr_u_int32_t(xdr, (uint32_t *)&res->status)) + return FALSE; + + switch (res->status) { + case NFS4_OK: + return decode_layout_res_ok(xdr, res->u.res_ok); + case NFS4ERR_LAYOUTTRYLATER: + return xdr_bool(xdr, &res->u.will_signal_layout_avail); + } + return TRUE; +} + + +/* + * OP_LAYOUTRETURN + */ +static bool_t encode_op_layoutreturn( + XDR *xdr, + nfs_argop4 *argop) +{ + pnfs_layoutreturn_args *args = (pnfs_layoutreturn_args*)argop->arg; + + if (unexpected_op(argop->op, OP_LAYOUTRETURN)) + return FALSE; + + if (!xdr_bool(xdr, &args->reclaim)) + return FALSE; + + if (!xdr_enum(xdr, (enum_t *)&args->type)) + return FALSE; + + if (!xdr_enum(xdr, (enum_t *)&args->iomode)) + return FALSE; + + if (!xdr_enum(xdr, (enum_t *)&args->return_type)) + return FALSE; + + if (args->return_type == PNFS_RETURN_FILE) { + u_int32_t zero = 0; + + if (!xdr_u_hyper(xdr, &args->offset)) + return FALSE; + + if (!xdr_u_hyper(xdr, &args->length)) + return FALSE; + + if (!xdr_stateid4(xdr, args->stateid)) + return FALSE; + + return xdr_u_int32_t(xdr, &zero); /* size of lrf_body is 0 */ + } else { + eprintf("%s: layout type (%d) is not PNFS_RETURN_FILE!\n", + "encode_op_layoutreturn", args->return_type); + return FALSE; + } +} + +static bool_t decode_op_layoutreturn( + XDR *xdr, + nfs_resop4 *resop) +{ + pnfs_layoutreturn_res *res = (pnfs_layoutreturn_res*)resop->res; + + if (unexpected_op(resop->op, OP_LAYOUTRETURN)) + return FALSE; + + if (!xdr_u_int32_t(xdr, (uint32_t *)&res->status)) + return FALSE; + + if (res->status == NFS4_OK) { + if (!xdr_bool(xdr, &res->stateid_present)) + return FALSE; + + if (res->stateid_present) + return xdr_stateid4(xdr, &res->stateid); + } + return TRUE; +} + + +/* op encode/decode table */ +typedef bool_t (*nfs_op_encode_proc)(XDR*, nfs_argop4*); +typedef bool_t (*nfs_op_decode_proc)(XDR*, nfs_resop4*); + +typedef struct __op_table_entry { + nfs_op_encode_proc encode; + nfs_op_decode_proc decode; +} op_table_entry; + +/* table of encode/decode functions, indexed by operation number */ +static const op_table_entry g_op_table[] = { + { NULL, NULL }, /* 0 unused */ + { NULL, NULL }, /* 1 unused */ + { NULL, NULL }, /* 2 unused */ + { encode_op_access, decode_op_access }, /* OP_ACCESS = 3 */ + { encode_op_close, decode_op_close }, /* OP_CLOSE = 4 */ + { encode_op_commit, decode_op_commit }, /* OP_COMMIT = 5 */ + { encode_op_create, decode_op_create }, /* OP_CREATE = 6 */ + { encode_op_delegpurge, decode_op_delegpurge }, /* OP_DELEGPURGE = 7 */ + { encode_op_delegreturn, decode_op_delegreturn }, /* OP_DELEGRETURN = 8 */ + { encode_op_getattr, decode_op_getattr }, /* OP_GETATTR = 9 */ + { encode_op_getfh, decode_op_getfh }, /* OP_GETFH = 10 */ + { encode_op_link, decode_op_link }, /* OP_LINK = 11 */ + { encode_op_lock, decode_op_lock }, /* OP_LOCK = 12 */ + { encode_op_lockt, decode_op_lockt }, /* OP_LOCKT = 13 */ + { encode_op_locku, decode_op_locku }, /* OP_LOCKU = 14 */ + { encode_op_lookup, decode_op_lookup }, /* OP_LOOKUP = 15 */ + { NULL, NULL }, /* OP_LOOKUPP = 16 */ + { NULL, NULL }, /* OP_NVERIFY = 17 */ + { encode_op_open, decode_op_open }, /* OP_OPEN = 18 */ + { encode_op_openattr, decode_op_openattr }, /* OP_OPENATTR = 19 */ + { NULL, NULL }, /* OP_OPEN_CONFIRM = 20 */ + { NULL, NULL }, /* OP_OPEN_DOWNGRADE = 21 */ + { encode_op_putfh, decode_op_putfh }, /* OP_PUTFH = 22 */ + { NULL, NULL }, /* OP_PUTPUBFH = 23 */ + { encode_op_putrootfh, decode_op_putrootfh }, /* OP_PUTROOTFH = 24 */ + { encode_op_read, decode_op_read }, /* OP_READ = 25 */ + { encode_op_readdir, decode_op_readdir }, /* OP_READDIR = 26 */ + { encode_op_readlink, decode_op_readlink }, /* OP_READLINK = 27 */ + { encode_op_remove, decode_op_remove }, /* OP_REMOVE = 28 */ + { encode_op_rename, decode_op_rename }, /* OP_RENAME = 29 */ + { NULL, NULL }, /* OP_RENEW = 30 */ + { encode_op_restorefh, decode_op_restorefh }, /* OP_RESTOREFH = 31 */ + { encode_op_savefh, decode_op_savefh }, /* OP_SAVEFH = 32 */ + { encode_op_secinfo, decode_op_secinfo }, /* OP_SECINFO = 33 */ + { encode_op_setattr, decode_op_setattr }, /* OP_SETATTR = 34 */ + { NULL, NULL }, /* OP_SETCLIENTID = 35 */ + { NULL, NULL }, /* OP_SETCLIENTID_CONFIRM = 36 */ + { NULL, NULL }, /* OP_VERIFY = 37 */ + { encode_op_write, decode_op_write }, /* OP_WRITE = 38 */ + { NULL, NULL }, /* OP_RELEASE_LOCKOWNER = 39 */ + { NULL, NULL }, /* OP_BACKCHANNEL_CTL = 40 */ + { encode_op_bind_conn_to_session, decode_op_bind_conn_to_session }, /* OP_BIND_CONN_TO_SESSION = 41 */ + { encode_op_exchange_id, decode_op_exchange_id }, /* OP_EXCHANGE_ID = 42 */ + { encode_op_create_session, decode_op_create_session }, /* OP_CREATE_SESSION = 43 */ + { encode_op_destroy_session, decode_op_destroy_session }, /* OP_DESTROY_SESSION = 44 */ + { encode_op_free_stateid, decode_op_free_stateid }, /* OP_FREE_STATEID = 45 */ + { NULL, NULL }, /* OP_GET_DIR_DELEGATION = 46 */ + { encode_op_getdeviceinfo, decode_op_getdeviceinfo }, /* OP_GETDEVICEINFO = 47 */ + { NULL, NULL }, /* OP_GETDEVICELIST = 48 */ + { encode_op_layoutcommit, decode_op_layoutcommit }, /* OP_LAYOUTCOMMIT = 49 */ + { encode_op_layoutget, decode_op_layoutget }, /* OP_LAYOUTGET = 50 */ + { encode_op_layoutreturn, decode_op_layoutreturn }, /* OP_LAYOUTRETURN = 51 */ + { encode_op_secinfo_noname, decode_op_secinfo_noname }, /* OP_SECINFO_NO_NAME = 52 */ + { encode_op_sequence, decode_op_sequence }, /* OP_SEQUENCE = 53 */ + { NULL, NULL }, /* OP_SET_SSV = 54 */ + { encode_op_test_stateid, decode_op_test_stateid }, /* OP_TEST_STATEID = 55 */ + { encode_op_want_delegation, decode_op_want_delegation }, /* OP_WANT_DELEGATION = 56 */ + { encode_op_destroy_clientid, decode_op_destroy_clientid }, /* OP_DESTROY_CLIENTID = 57 */ + { encode_op_reclaim_complete, decode_op_reclaim_complete }, /* OP_RECLAIM_COMPLETE = 58 */ +}; +#ifdef __REACTOS__ +static const uint32_t g_op_table_size = (sizeof(g_op_table) / sizeof(g_op_table[0])); +#else +static const uint32_t g_op_table_size = ARRAYSIZE(g_op_table); +#endif + +static const op_table_entry* op_table_find(uint32_t op) +{ + return op >= g_op_table_size ? NULL : &g_op_table[op]; +} + + +/* + * COMPOUND + */ +bool_t nfs_encode_compound( + XDR *xdr, + caddr_t *pargs) +{ + unsigned char *tag; + + nfs41_compound_args *args = (nfs41_compound_args*)pargs; + uint32_t i; + const op_table_entry *entry; + + tag = args->tag; + if (!xdr_bytes(xdr, (char **)&tag, &args->tag_len, NFS4_OPAQUE_LIMIT)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &args->minorversion)) + return FALSE; + + if (!xdr_u_int32_t(xdr, &args->argarray_count)) + return FALSE; + + for (i = 0; i < args->argarray_count; i++) + { + entry = op_table_find(args->argarray[i].op); + if (entry == NULL || entry->encode == NULL) + return FALSE; + + if (!xdr_u_int32_t(xdr, &args->argarray[i].op)) + return FALSE; + if (!entry->encode(xdr, &args->argarray[i])) + return FALSE; + } + return TRUE; +} + +bool_t nfs_decode_compound( + XDR *xdr, + caddr_t *pres) +{ + nfs41_compound_res *res = (nfs41_compound_res*)pres; + uint32_t i, expected_count, expected_op; + const op_table_entry *entry; + unsigned char *tag = res->tag; + + if (!xdr_u_int32_t(xdr, &res->status)) + return FALSE; + + if (!xdr_bytes(xdr, (char **)&tag, &res->tag_len, NFS4_OPAQUE_LIMIT)) + return FALSE; + + expected_count = res->resarray_count; + if (!xdr_u_int32_t(xdr, &res->resarray_count)) + return FALSE; + + /* validate the number of operations against what we sent */ + if (res->resarray_count > expected_count) { + eprintf("reply with %u operations, only sent %u!\n", + res->resarray_count, expected_count); + return FALSE; + } else if (res->resarray_count < expected_count && + res->status == NFS4_OK) { + /* illegal for a server to reply with less operations, + * unless one of them fails */ + eprintf("successful reply with only %u operations, sent %u!\n", + res->resarray_count, expected_count); + return FALSE; + } + + for (i = 0; i < res->resarray_count; i++) + { + expected_op = res->resarray[i].op; + if (!xdr_u_int32_t(xdr, &res->resarray[i].op)) + return FALSE; + + /* validate each operation number against what we sent */ + if (res->resarray[i].op != expected_op) { + eprintf("reply with %s in operation %u, expected %s!\n", + nfs_opnum_to_string(res->resarray[i].op), i+1, + nfs_opnum_to_string(expected_op)); + return FALSE; + } + + entry = op_table_find(res->resarray[i].op); + if (entry == NULL || entry->decode == NULL) + return FALSE; + if (!entry->decode(xdr, &res->resarray[i])) + return FALSE; + } + return TRUE; +} diff --git a/reactos/base/services/nfsd/nfs41_xdr.h b/reactos/base/services/nfsd/nfs41_xdr.h new file mode 100644 index 00000000000..ef8b3746116 --- /dev/null +++ b/reactos/base/services/nfsd/nfs41_xdr.h @@ -0,0 +1,32 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#ifndef __NFS41_NFS_XDR_H__ +#define __NFS41_NFS_XDR_H__ + +#include "nfs41_types.h" + +bool_t nfs_encode_compound(XDR *xdr, caddr_t *args); +bool_t nfs_decode_compound(XDR *xdr, caddr_t *res); + +void nfsacl41_free(nfsacl41 *acl); + +#endif /* !__NFS41_NFS_XDR_H__ */ diff --git a/reactos/base/services/nfsd/nfsd.rc b/reactos/base/services/nfsd/nfsd.rc new file mode 100644 index 00000000000..684a802d8bc --- /dev/null +++ b/reactos/base/services/nfsd/nfsd.rc @@ -0,0 +1,4 @@ +#define REACTOS_STR_FILE_DESCRIPTION "NFS daemon" +#define REACTOS_STR_INTERNAL_NAME "nfsd" +#define REACTOS_STR_ORIGINAL_FILENAME "nfsd.exe" +#include diff --git a/reactos/base/services/nfsd/open.c b/reactos/base/services/nfsd/open.c new file mode 100644 index 00000000000..c43d1d0e1ac --- /dev/null +++ b/reactos/base/services/nfsd/open.c @@ -0,0 +1,948 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include +#include +#include + +#include "nfs41_ops.h" +#include "delegation.h" +#include "from_kernel.h" +#include "daemon_debug.h" +#include "upcall.h" +#include "util.h" + + +static int create_open_state( + IN const char *path, + IN uint32_t open_owner_id, + OUT nfs41_open_state **state_out) +{ + int status; + nfs41_open_state *state; + + state = calloc(1, sizeof(nfs41_open_state)); + if (state == NULL) { + status = GetLastError(); + goto out; + } + + InitializeSRWLock(&state->path.lock); + if (FAILED(StringCchCopyA(state->path.path, NFS41_MAX_PATH_LEN, path))) { + status = ERROR_FILENAME_EXCED_RANGE; + goto out_free; + } + state->path.len = (unsigned short)strlen(state->path.path); + path_fh_init(&state->file, &state->path); + path_fh_init(&state->parent, &state->path); + last_component(state->path.path, state->file.name.name, &state->parent.name); + + StringCchPrintfA((LPSTR)state->owner.owner, NFS4_OPAQUE_LIMIT, "%u", + open_owner_id); + state->owner.owner_len = (uint32_t)strlen((const char*)state->owner.owner); + state->ref_count = 1; + list_init(&state->locks.list); + list_init(&state->client_entry); + InitializeCriticalSection(&state->locks.lock); + + state->ea.list = INVALID_HANDLE_VALUE; + InitializeCriticalSection(&state->ea.lock); + + *state_out = state; + status = NO_ERROR; +out: + return status; + +out_free: + free(state); + goto out; +} + +static void open_state_free( + IN nfs41_open_state *state) +{ + struct list_entry *entry, *tmp; + + /* free associated lock state */ + list_for_each_tmp(entry, tmp, &state->locks.list) + free(list_container(entry, nfs41_lock_state, open_entry)); + if (state->delegation.state) + nfs41_delegation_deref(state->delegation.state); + if (state->ea.list != INVALID_HANDLE_VALUE) + free(state->ea.list); + free(state); +} + + +/* open state reference counting */ +void nfs41_open_state_ref( + IN nfs41_open_state *state) +{ + const LONG count = InterlockedIncrement(&state->ref_count); + + dprintf(2, "nfs41_open_state_ref(%s) count %d\n", state->path.path, count); +} + +void nfs41_open_state_deref( + IN nfs41_open_state *state) +{ + const LONG count = InterlockedDecrement(&state->ref_count); + + dprintf(2, "nfs41_open_state_deref(%s) count %d\n", state->path.path, count); + if (count == 0) + open_state_free(state); +} + +/* 8.2.5. Stateid Use for I/O Operations + * o If the client holds a delegation for the file in question, the + * delegation stateid SHOULD be used. + * o Otherwise, if the entity corresponding to the lock-owner (e.g., a + * process) sending the I/O has a byte-range lock stateid for the + * associated open file, then the byte-range lock stateid for that + * lock-owner and open file SHOULD be used. + * o If there is no byte-range lock stateid, then the OPEN stateid for + * the open file in question SHOULD be used. + * o Finally, if none of the above apply, then a special stateid SHOULD + * be used. */ +void nfs41_open_stateid_arg( + IN nfs41_open_state *state, + OUT stateid_arg *arg) +{ + arg->open = state; + arg->delegation = NULL; + + AcquireSRWLockShared(&state->lock); + + if (state->delegation.state) { + nfs41_delegation_state *deleg = state->delegation.state; + AcquireSRWLockShared(&deleg->lock); + if (deleg->status == DELEGATION_GRANTED) { + arg->type = STATEID_DELEG_FILE; + memcpy(&arg->stateid, &deleg->state.stateid, sizeof(stateid4)); + } + ReleaseSRWLockShared(&deleg->lock); + + if (arg->type == STATEID_DELEG_FILE) + goto out; + + dprintf(2, "delegation recalled, waiting for open stateid..\n"); + + /* wait for nfs41_delegation_to_open() to recover open stateid */ + while (!state->do_close) + SleepConditionVariableSRW(&state->delegation.cond, &state->lock, + INFINITE, CONDITION_VARIABLE_LOCKMODE_SHARED); + } + + if (state->locks.stateid.seqid) { + memcpy(&arg->stateid, &state->locks.stateid, sizeof(stateid4)); + arg->type = STATEID_LOCK; + } else if (state->do_close) { + memcpy(&arg->stateid, &state->stateid, sizeof(stateid4)); + arg->type = STATEID_OPEN; + } else { + memset(&arg->stateid, 0, sizeof(stateid4)); + arg->type = STATEID_SPECIAL; + } +out: + ReleaseSRWLockShared(&state->lock); +} + +/* client list of associated open state */ +static void client_state_add( + IN nfs41_open_state *state) +{ + nfs41_client *client = state->session->client; + + EnterCriticalSection(&client->state.lock); + list_add_tail(&client->state.opens, &state->client_entry); + LeaveCriticalSection(&client->state.lock); +} + +static void client_state_remove( + IN nfs41_open_state *state) +{ + nfs41_client *client = state->session->client; + + EnterCriticalSection(&client->state.lock); + list_remove(&state->client_entry); + LeaveCriticalSection(&client->state.lock); +} + +static int do_open( + IN OUT nfs41_open_state *state, + IN uint32_t create, + IN uint32_t createhow, + IN nfs41_file_info *createattrs, + IN bool_t try_recovery, + OUT nfs41_file_info *info) +{ + open_claim4 claim; + stateid4 open_stateid; + open_delegation4 delegation = { 0 }; + nfs41_delegation_state *deleg_state = NULL; + int status; + + claim.claim = CLAIM_NULL; + claim.u.null.filename = &state->file.name; + + status = nfs41_open(state->session, &state->parent, &state->file, + &state->owner, &claim, state->share_access, state->share_deny, + create, createhow, createattrs, TRUE, &open_stateid, + &delegation, info); + if (status) + goto out; + + /* allocate delegation state and register it with the client */ + nfs41_delegation_granted(state->session, &state->parent, + &state->file, &delegation, TRUE, &deleg_state); + if (deleg_state) { + deleg_state->srv_open = state->srv_open; + dprintf(1, "do_open: received delegation: saving srv_open = %x\n", + state->srv_open); + } + + AcquireSRWLockExclusive(&state->lock); + /* update the stateid */ + memcpy(&state->stateid, &open_stateid, sizeof(open_stateid)); + state->do_close = 1; + state->delegation.state = deleg_state; + ReleaseSRWLockExclusive(&state->lock); +out: + return status; +} + +static int open_or_delegate( + IN OUT nfs41_open_state *state, + IN uint32_t create, + IN uint32_t createhow, + IN nfs41_file_info *createattrs, + IN bool_t try_recovery, + OUT nfs41_file_info *info) +{ + int status; + + /* check for existing delegation */ + status = nfs41_delegate_open(state, create, createattrs, info); + + /* get an open stateid if we have no delegation stateid */ + if (status) + status = do_open(state, create, createhow, + createattrs, try_recovery, info); + + state->pnfs_last_offset = info->size ? info->size - 1 : 0; + + /* register the client's open state on success */ + if (status == NFS4_OK) + client_state_add(state); + return status; +} + + +static int parse_abs_path(unsigned char **buffer, uint32_t *length, nfs41_abs_path *path) +{ + int status = safe_read(buffer, length, &path->len, sizeof(USHORT)); + if (status) goto out; + if (path->len == 0) + goto out; + if (path->len >= NFS41_MAX_PATH_LEN) { + status = ERROR_BUFFER_OVERFLOW; + goto out; + } + status = safe_read(buffer, length, path->path, path->len); + if (status) goto out; + path->len--; /* subtract 1 for null */ +out: + return status; +} + +/* NFS41_OPEN */ +static int parse_open(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall) +{ + int status; + open_upcall_args *args = &upcall->args.open; + + status = get_name(&buffer, &length, &args->path); + if (status) goto out; + status = safe_read(&buffer, &length, &args->access_mask, sizeof(ULONG)); + if (status) goto out; + status = safe_read(&buffer, &length, &args->access_mode, sizeof(ULONG)); + if (status) goto out; + status = safe_read(&buffer, &length, &args->file_attrs, sizeof(ULONG)); + if (status) goto out; + status = safe_read(&buffer, &length, &args->create_opts, sizeof(ULONG)); + if (status) goto out; + status = safe_read(&buffer, &length, &args->disposition, sizeof(ULONG)); + if (status) goto out; + status = safe_read(&buffer, &length, &args->open_owner_id, sizeof(LONG)); + if (status) goto out; + status = safe_read(&buffer, &length, &args->mode, sizeof(DWORD)); + if (status) goto out; + status = safe_read(&buffer, &length, &args->srv_open, sizeof(HANDLE)); + if (status) goto out; + status = parse_abs_path(&buffer, &length, &args->symlink); + if (status) goto out; + status = safe_read(&buffer, &length, &args->ea, sizeof(HANDLE)); + if (status) goto out; + + dprintf(1, "parsing NFS41_OPEN: filename='%s' access mask=%d " + "access mode=%d\n\tfile attrs=0x%x create attrs=0x%x " + "(kernel) disposition=%d\n\topen_owner_id=%d mode=%o " + "srv_open=%p symlink=%s ea=%p\n", args->path, args->access_mask, + args->access_mode, args->file_attrs, args->create_opts, + args->disposition, args->open_owner_id, args->mode, args->srv_open, + args->symlink.path, args->ea); + print_disposition(2, args->disposition); + print_access_mask(2, args->access_mask); + print_share_mode(2, args->access_mode); + print_create_attributes(2, args->create_opts); +out: + return status; +} + +static BOOLEAN open_for_attributes(uint32_t type, ULONG access_mask, + ULONG disposition, int status) +{ + if (type == NF4DIR) { + if (disposition == FILE_OPEN || disposition == FILE_OVERWRITE || + (!status && (disposition == FILE_OPEN_IF || + disposition == FILE_OVERWRITE_IF || + disposition == FILE_SUPERSEDE))) { + dprintf(1, "Opening a directory\n"); + return TRUE; + } else { + dprintf(1, "Creating a directory\n"); + return FALSE; + } + } + + if ((access_mask & FILE_READ_DATA) || + (access_mask & FILE_WRITE_DATA) || + (access_mask & FILE_APPEND_DATA) || + (access_mask & FILE_EXECUTE) || + disposition == FILE_CREATE || + disposition == FILE_OVERWRITE_IF || + disposition == FILE_SUPERSEDE || + disposition == FILE_OPEN_IF || + disposition == FILE_OVERWRITE) + return FALSE; + else { + dprintf(1, "Open call that wants to manage attributes\n"); + return TRUE; + } +} + +static int map_disposition_2_nfsopen(ULONG disposition, int in_status, bool_t persistent, + uint32_t *create, uint32_t *createhowmode, + uint32_t *last_error) +{ + int status = NO_ERROR; + if (disposition == FILE_SUPERSEDE) { + if (in_status == NFS4ERR_NOENT) + *last_error = ERROR_FILE_NOT_FOUND; + //remove and recreate the file + *create = OPEN4_CREATE; + if (persistent) *createhowmode = GUARDED4; + else *createhowmode = EXCLUSIVE4_1; + } else if (disposition == FILE_CREATE) { + // if lookup succeeded which means the file exist, return an error + if (!in_status) + status = ERROR_FILE_EXISTS; + else { + *create = OPEN4_CREATE; + if (persistent) *createhowmode = GUARDED4; + else *createhowmode = EXCLUSIVE4_1; + } + } else if (disposition == FILE_OPEN) { + if (in_status == NFS4ERR_NOENT) + status = ERROR_FILE_NOT_FOUND; + else + *create = OPEN4_NOCREATE; + } else if (disposition == FILE_OPEN_IF) { + if (in_status == NFS4ERR_NOENT) { + dprintf(1, "creating new file\n"); + *create = OPEN4_CREATE; + *last_error = ERROR_FILE_NOT_FOUND; + } else { + dprintf(1, "opening existing file\n"); + *create = OPEN4_NOCREATE; + } + } else if (disposition == FILE_OVERWRITE) { + if (in_status == NFS4ERR_NOENT) + status = ERROR_FILE_NOT_FOUND; + //truncate file + *create = OPEN4_CREATE; + } else if (disposition == FILE_OVERWRITE_IF) { + if (in_status == NFS4ERR_NOENT) + *last_error = ERROR_FILE_NOT_FOUND; + //truncate file + *create = OPEN4_CREATE; + } + return status; +} + +static void map_access_2_allowdeny(ULONG access_mask, ULONG access_mode, + ULONG disposition, uint32_t *allow, uint32_t *deny) +{ + if ((access_mask & + (FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES)) && + (access_mask & (FILE_READ_DATA | FILE_EXECUTE))) + *allow = OPEN4_SHARE_ACCESS_BOTH; + else if (access_mask & (FILE_READ_DATA | FILE_EXECUTE)) + *allow = OPEN4_SHARE_ACCESS_READ; + else if (access_mask & + (FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES)) + *allow = OPEN4_SHARE_ACCESS_WRITE; + /* if we are creating a file and no data access is specified, then + * do an open and request no delegations. example open with share access 0 + * and share deny 0 (ie deny_both). + */ + if ((disposition == FILE_CREATE || disposition == FILE_OPEN_IF || + disposition == FILE_OVERWRITE_IF || disposition == FILE_SUPERSEDE || + disposition == FILE_OVERWRITE) && + !(access_mask & (FILE_WRITE_DATA | FILE_APPEND_DATA | + FILE_WRITE_ATTRIBUTES | FILE_READ_DATA | FILE_EXECUTE))) + *allow = OPEN4_SHARE_ACCESS_READ | OPEN4_SHARE_ACCESS_WANT_NO_DELEG; + +#define FIX_ALLOW_DENY_WIN2NFS_CONVERSION +#ifdef FIX_ALLOW_DENY_WIN2NFS_CONVERSION + if ((access_mode & FILE_SHARE_READ) && + (access_mode & FILE_SHARE_WRITE)) + *deny = OPEN4_SHARE_DENY_NONE; + else if (access_mode & FILE_SHARE_READ) + *deny = OPEN4_SHARE_DENY_WRITE; + else if (access_mode & FILE_SHARE_WRITE) + *deny = OPEN4_SHARE_DENY_READ; + else + *deny = OPEN4_SHARE_DENY_BOTH; +#else + // AGLO: 11/13/2009. + // readonly file that is being opened for reading with a + // share read mode given above logic translates into deny + // write and linux server does not allow it. + *deny = OPEN4_SHARE_DENY_NONE; +#endif +} + +static int check_execute_access(nfs41_open_state *state) +{ + uint32_t supported, access; + int status = nfs41_access(state->session, &state->file, + ACCESS4_EXECUTE | ACCESS4_READ, &supported, &access); + if (status) { + eprintf("nfs41_access() failed with %s for %s\n", + nfs_error_string(status), state->path.path); + status = ERROR_ACCESS_DENIED; + } else if ((supported & ACCESS4_EXECUTE) == 0) { + /* server can't verify execute access; + * for now, assume that read access is good enough */ + if ((supported & ACCESS4_READ) == 0 || (access & ACCESS4_READ) == 0) { + eprintf("server can't verify execute access, and user does " + "not have read access to file %s\n", state->path.path); + status = ERROR_ACCESS_DENIED; + } + } else if ((access & ACCESS4_EXECUTE) == 0) { + dprintf(1, "user does not have execute access to file %s\n", + state->path.path); + status = ERROR_ACCESS_DENIED; + } else + dprintf(2, "user has execute access to file\n"); + return status; +} + +static int create_with_ea( + IN uint32_t disposition, + IN uint32_t lookup_status) +{ + /* only set EAs on file creation */ + return disposition == FILE_SUPERSEDE || disposition == FILE_CREATE + || disposition == FILE_OVERWRITE || disposition == FILE_OVERWRITE_IF + || (disposition == FILE_OPEN_IF && lookup_status == NFS4ERR_NOENT); +} + +static int handle_open(nfs41_upcall *upcall) +{ + int status = 0; + open_upcall_args *args = &upcall->args.open; + nfs41_open_state *state; + nfs41_file_info info = { 0 }; + + status = create_open_state(args->path, args->open_owner_id, &state); + if (status) { + eprintf("create_open_state(%d) failed with %d\n", + args->open_owner_id, status); + goto out; + } + state->srv_open = args->srv_open; + + // first check if windows told us it's a directory + if (args->create_opts & FILE_DIRECTORY_FILE) + state->type = NF4DIR; + else + state->type = NF4REG; + + // always do a lookup + status = nfs41_lookup(upcall->root_ref, nfs41_root_session(upcall->root_ref), + &state->path, &state->parent, &state->file, &info, &state->session); + + if (status == ERROR_REPARSE) { + uint32_t depth = 0; + /* one of the parent components was a symlink */ + do { + if (++depth > NFS41_MAX_SYMLINK_DEPTH) { + status = ERROR_TOO_MANY_LINKS; + goto out_free_state; + } + + /* replace the path with the symlink target's */ + status = nfs41_symlink_target(state->session, + &state->parent, &state->path); + if (status) { + /* can't do the reparse if we can't get the target */ + eprintf("nfs41_symlink_target() failed with %d\n", status); + goto out_free_state; + } + + /* redo the lookup until it doesn't return REPARSE */ + status = nfs41_lookup(upcall->root_ref, state->session, + &state->path, &state->parent, NULL, NULL, &state->session); + } while (status == ERROR_REPARSE); + + if (status == NO_ERROR || status == ERROR_FILE_NOT_FOUND) { + abs_path_copy(&args->symlink, &state->path); + status = NO_ERROR; + upcall->last_error = ERROR_REPARSE; + args->symlink_embedded = TRUE; + } + goto out_free_state; + } + + // now if file/dir exists, use type returned by lookup + if (status == NO_ERROR) { + if (info.type == NF4DIR) { + dprintf(2, "handle_nfs41_open: DIRECTORY\n"); + if (args->create_opts & FILE_NON_DIRECTORY_FILE) { + eprintf("trying to open directory %s as a file\n", + state->path.path); + status = ERROR_DIRECTORY; + goto out_free_state; + } + } else if (info.type == NF4REG) { + dprintf(2, "handle nfs41_open: FILE\n"); + if (args->create_opts & FILE_DIRECTORY_FILE) { + eprintf("trying to open file %s as a directory\n", + state->path.path); + status = ERROR_BAD_FILE_TYPE; + goto out_free_state; + } + } else if (info.type == NF4LNK) { + dprintf(2, "handle nfs41_open: SYMLINK\n"); + if (args->create_opts & FILE_OPEN_REPARSE_POINT) { + /* continue and open the symlink itself, but we need to + * know if the target is a regular file or directory */ + nfs41_file_info target_info; + int target_status = nfs41_symlink_follow(upcall->root_ref, + state->session, &state->file, &target_info); + if (target_status == NO_ERROR && target_info.type == NF4DIR) + info.symlink_dir = TRUE; + } else { + /* replace the path with the symlink target */ + status = nfs41_symlink_target(state->session, + &state->file, &args->symlink); + if (status) { + eprintf("nfs41_symlink_target() for %s failed with %d\n", + args->path, status); + } else { + /* tell the driver to call RxPrepareToReparseSymbolicLink() */ + upcall->last_error = ERROR_REPARSE; + args->symlink_embedded = FALSE; + } + goto out_free_state; + } + } else + dprintf(2, "handle_open(): unsupported type=%d\n", info.type); + state->type = info.type; + } else if (status != ERROR_FILE_NOT_FOUND) + goto out_free_state; + + /* XXX: this is a hard-coded check for the open arguments we see from + * the CreateSymbolicLink() system call. we respond to this by deferring + * the CREATE until we get the upcall to set the symlink. this approach + * is troublesome for two reasons: + * -an application might use these exact arguments to create a normal + * file, and we would return success without actually creating it + * -an application could create a symlink by sending the FSCTL to set + * the reparse point manually, and their open might be different. in + * this case we'd create the file on open, and need to remove it + * before creating the symlink */ + if (args->disposition == FILE_CREATE && + args->access_mask == (FILE_WRITE_ATTRIBUTES | SYNCHRONIZE | DELETE) && + args->access_mode == 0 && + args->create_opts & FILE_OPEN_REPARSE_POINT) { + /* fail if the file already exists */ + if (status == NO_ERROR) { + status = ERROR_FILE_EXISTS; + goto out_free_state; + } + + /* defer the call to CREATE until we get the symlink set upcall */ + dprintf(1, "trying to create a symlink, deferring create\n"); + + /* because of WRITE_ATTR access, be prepared for a setattr upcall; + * will crash if the superblock is null, so use the parent's */ + state->file.fh.superblock = state->parent.fh.superblock; + + status = NO_ERROR; + } else if (args->symlink.len) { + /* handle cygwin symlinks */ + nfs41_file_info createattrs; + createattrs.attrmask.count = 2; + createattrs.attrmask.arr[0] = 0; + createattrs.attrmask.arr[1] = FATTR4_WORD1_MODE; + createattrs.mode = 0777; + + dprintf(1, "creating cygwin symlink %s -> %s\n", + state->file.name.name, args->symlink.path); + + status = nfs41_create(state->session, NF4LNK, &createattrs, + args->symlink.path, &state->parent, &state->file, &info); + if (status) { + eprintf("nfs41_create() for symlink=%s failed with %s\n", + args->symlink.path, nfs_error_string(status)); + status = map_symlink_errors(status); + goto out_free_state; + } + nfs_to_basic_info(&info, &args->basic_info); + nfs_to_standard_info(&info, &args->std_info); + args->mode = info.mode; + args->changeattr = info.change; + } else if (open_for_attributes(state->type, args->access_mask, + args->disposition, status)) { + if (status) { + dprintf(1, "nfs41_lookup failed with %d\n", status); + goto out_free_state; + } + + nfs_to_basic_info(&info, &args->basic_info); + nfs_to_standard_info(&info, &args->std_info); + args->mode = info.mode; + args->changeattr = info.change; + } else { + nfs41_file_info createattrs = { 0 }; + uint32_t create = 0, createhowmode = 0, lookup_status = status; + + if (!lookup_status && (args->disposition == FILE_OVERWRITE || + args->disposition == FILE_OVERWRITE_IF || + args->disposition == FILE_SUPERSEDE)) { + if ((info.hidden && !(args->file_attrs & FILE_ATTRIBUTE_HIDDEN)) || + (info.system && !(args->file_attrs & FILE_ATTRIBUTE_SYSTEM))) { + status = ERROR_ACCESS_DENIED; + goto out_free_state; + } + if (args->disposition != FILE_SUPERSEDE) + args->mode = info.mode; + } + createattrs.attrmask.count = 2; + createattrs.attrmask.arr[0] = FATTR4_WORD0_HIDDEN | FATTR4_WORD0_ARCHIVE; + createattrs.attrmask.arr[1] = FATTR4_WORD1_MODE | FATTR4_WORD1_SYSTEM; + createattrs.mode = args->mode; + createattrs.hidden = args->file_attrs & FILE_ATTRIBUTE_HIDDEN ? 1 : 0; + createattrs.system = args->file_attrs & FILE_ATTRIBUTE_SYSTEM ? 1 : 0; + createattrs.archive = args->file_attrs & FILE_ATTRIBUTE_ARCHIVE ? 1 : 0; + + map_access_2_allowdeny(args->access_mask, args->access_mode, + args->disposition, &state->share_access, &state->share_deny); + status = map_disposition_2_nfsopen(args->disposition, status, + state->session->flags & CREATE_SESSION4_FLAG_PERSIST, + &create, &createhowmode, &upcall->last_error); + if (status) + goto out_free_state; + + if (args->access_mask & FILE_EXECUTE && state->file.fh.len) { + status = check_execute_access(state); + if (status) + goto out_free_state; + } + +supersede_retry: + // XXX file exists and we have to remove it first + if (args->disposition == FILE_SUPERSEDE && lookup_status == NO_ERROR) { + nfs41_component *name = &state->file.name; + if (!(args->create_opts & FILE_DIRECTORY_FILE)) + nfs41_delegation_return(state->session, &state->file, + OPEN_DELEGATE_WRITE, TRUE); + + dprintf(1, "open for FILE_SUPERSEDE removing %s first\n", name->name); + status = nfs41_remove(state->session, &state->parent, + name, state->file.fh.fileid); + if (status) + goto out_free_state; + } + + if (create == OPEN4_CREATE && (args->create_opts & FILE_DIRECTORY_FILE)) { + status = nfs41_create(state->session, NF4DIR, &createattrs, NULL, + &state->parent, &state->file, &info); + args->created = status == NFS4_OK ? TRUE : FALSE; + } else { + createattrs.attrmask.arr[0] |= FATTR4_WORD0_SIZE; + createattrs.size = 0; + dprintf(1, "creating with mod %o\n", args->mode); + status = open_or_delegate(state, create, createhowmode, &createattrs, + TRUE, &info); + if (status == NFS4_OK && state->delegation.state) + args->deleg_type = state->delegation.state->state.type; + } + if (status) { + dprintf(1, "%s failed with %s\n", (create == OPEN4_CREATE && + (args->create_opts & FILE_DIRECTORY_FILE))?"nfs41_create":"nfs41_open", + nfs_error_string(status)); + if (args->disposition == FILE_SUPERSEDE && status == NFS4ERR_EXIST) + goto supersede_retry; + status = nfs_to_windows_error(status, ERROR_FILE_NOT_FOUND); + goto out_free_state; + } else { + nfs_to_basic_info(&info, &args->basic_info); + nfs_to_standard_info(&info, &args->std_info); + args->mode = info.mode; + args->changeattr = info.change; + } + + /* set extended attributes on file creation */ + if (args->ea && create_with_ea(args->disposition, lookup_status)) { + status = nfs41_ea_set(state, args->ea); + status = nfs_to_windows_error(status, ERROR_FILE_NOT_FOUND); + } + } + + upcall->state_ref = state; + nfs41_open_state_ref(upcall->state_ref); +out: + return status; +out_free_state: + nfs41_open_state_deref(state); + goto out; +} + +static int marshall_open(unsigned char *buffer, uint32_t *length, nfs41_upcall *upcall) +{ + int status; + open_upcall_args *args = &upcall->args.open; + + status = safe_write(&buffer, length, &args->basic_info, sizeof(args->basic_info)); + if (status) goto out; + status = safe_write(&buffer, length, &args->std_info, sizeof(args->std_info)); + if (status) goto out; + status = safe_write(&buffer, length, &upcall->state_ref, sizeof(HANDLE)); + if (status) goto out; + status = safe_write(&buffer, length, &args->mode, sizeof(args->mode)); + if (status) goto out; + status = safe_write(&buffer, length, &args->changeattr, sizeof(args->changeattr)); + if (status) goto out; + status = safe_write(&buffer, length, &args->deleg_type, sizeof(args->deleg_type)); + if (status) goto out; + if (upcall->last_error == ERROR_REPARSE) { + unsigned short len = (args->symlink.len + 1) * sizeof(WCHAR); + status = safe_write(&buffer, length, &args->symlink_embedded, sizeof(BOOLEAN)); + if (status) goto out; + status = safe_write(&buffer, length, &len, sizeof(len)); + if (status) goto out; + /* convert args->symlink to wchar */ + if (*length <= len || !MultiByteToWideChar(CP_UTF8, 0, + args->symlink.path, args->symlink.len, + (LPWSTR)buffer, len / sizeof(WCHAR))) { + status = ERROR_BUFFER_OVERFLOW; + goto out; + } + } + dprintf(2, "NFS41_OPEN: downcall open_state=0x%p mode %o changeattr 0x%llu\n", + upcall->state_ref, args->mode, args->changeattr); +out: + return status; +} + +static void cancel_open(IN nfs41_upcall *upcall) +{ + int status = NFS4_OK; + open_upcall_args *args = &upcall->args.open; + nfs41_open_state *state = upcall->state_ref; + + dprintf(1, "--> cancel_open('%s')\n", args->path); + + if (upcall->state_ref == NULL || + upcall->state_ref == INVALID_HANDLE_VALUE) + goto out; /* if handle_open() failed, the state was already freed */ + + if (state->do_close) { + stateid_arg stateid; + stateid.open = state; + stateid.delegation = NULL; + stateid.type = STATEID_OPEN; + memcpy(&stateid.stateid, &state->stateid, sizeof(stateid4)); + + status = nfs41_close(state->session, &state->file, &stateid); + if (status) + dprintf(1, "cancel_open: nfs41_close() failed with %s\n", + nfs_error_string(status)); + + } else if (args->created) { + const nfs41_component *name = &state->file.name; + /* break any delegations and truncate before REMOVE */ + nfs41_delegation_return(state->session, &state->file, + OPEN_DELEGATE_WRITE, TRUE); + status = nfs41_remove(state->session, &state->parent, + name, state->file.fh.fileid); + if (status) + dprintf(1, "cancel_open: nfs41_remove() failed with %s\n", + nfs_error_string(status)); + } + + /* remove from the client's list of state for recovery */ + client_state_remove(state); + nfs41_open_state_deref(state); +out: + status = nfs_to_windows_error(status, ERROR_INTERNAL_ERROR); + dprintf(1, "<-- cancel_open() returning %d\n", status); +} + + +/* NFS41_CLOSE */ +static int parse_close(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall) +{ + int status; + close_upcall_args *args = &upcall->args.close; + + status = safe_read(&buffer, &length, &args->remove, sizeof(BOOLEAN)); + if (status) goto out; + status = safe_read(&buffer, &length, &args->srv_open, sizeof(HANDLE)); + if (status) goto out; + if (args->remove) { + status = get_name(&buffer, &length, &args->path); + if (status) goto out; + status = safe_read(&buffer, &length, &args->renamed, sizeof(BOOLEAN)); + if (status) goto out; + } + + dprintf(1, "parsing NFS41_CLOSE: remove=%d srv_open=%x renamed=%d " + "filename='%s'\n", args->remove, args->srv_open, args->renamed, + args->remove ? args->path : ""); +out: + return status; +} + +static int do_nfs41_close(nfs41_open_state *state) +{ + int status; + stateid_arg stateid; + stateid.open = state; + stateid.delegation = NULL; + stateid.type = STATEID_OPEN; + memcpy(&stateid.stateid, &state->stateid, sizeof(stateid4)); + + status = nfs41_close(state->session, &state->file, &stateid); + if (status) { + dprintf(1, "nfs41_close() failed with error %s.\n", + nfs_error_string(status)); + status = nfs_to_windows_error(status, ERROR_INTERNAL_ERROR); + } + + return status; +} + +static int handle_close(nfs41_upcall *upcall) +{ + int status = NFS4_OK, rm_status = NFS4_OK; + close_upcall_args *args = &upcall->args.close; + nfs41_open_state *state = upcall->state_ref; + + /* return associated file layouts if necessary */ + if (state->type == NF4REG) + pnfs_layout_state_close(state->session, state, args->remove); + + if (state->srv_open == args->srv_open) + nfs41_delegation_remove_srvopen(state->session, &state->file); + + if (args->remove) { + nfs41_component *name = &state->file.name; + + if (args->renamed) { + dprintf(1, "removing a renamed file %s\n", name->name); + create_silly_rename(&state->path, &state->file.fh, name); + status = do_nfs41_close(state); + if (status) + goto out; + else + state->do_close = 0; + } + + /* break any delegations and truncate before REMOVE */ + nfs41_delegation_return(state->session, &state->file, + OPEN_DELEGATE_WRITE, TRUE); + + dprintf(1, "calling nfs41_remove for %s\n", name->name); +retry_delete: + rm_status = nfs41_remove(state->session, &state->parent, + name, state->file.fh.fileid); + if (rm_status) { + if (rm_status == NFS4ERR_FILE_OPEN) { + status = do_nfs41_close(state); + if (!status) { + state->do_close = 0; + goto retry_delete; + } else goto out; + } + dprintf(1, "nfs41_remove() failed with error %s.\n", + nfs_error_string(rm_status)); + rm_status = nfs_to_windows_error(rm_status, ERROR_INTERNAL_ERROR); + } + } + + if (state->do_close) { + status = do_nfs41_close(state); + } +out: + /* remove from the client's list of state for recovery */ + client_state_remove(state); + + if (status || !rm_status) + return status; + else + return rm_status; +} + +static void cleanup_close(nfs41_upcall *upcall) +{ + /* release the initial reference from create_open_state() */ + nfs41_open_state_deref(upcall->state_ref); +} + + +const nfs41_upcall_op nfs41_op_open = { + parse_open, + handle_open, + marshall_open, + cancel_open +}; +const nfs41_upcall_op nfs41_op_close = { + parse_close, + handle_close, + NULL, + NULL, + cleanup_close +}; diff --git a/reactos/base/services/nfsd/pnfs.h b/reactos/base/services/nfsd/pnfs.h new file mode 100644 index 00000000000..afa870bba39 --- /dev/null +++ b/reactos/base/services/nfsd/pnfs.h @@ -0,0 +1,368 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#ifndef __PNFS_H__ +#define __PNFS_H__ + +#include "nfs41_types.h" +#include "list.h" + + +/* preprocessor options */ +#ifndef PNFS_DISABLE + +# ifndef PNFS_DISABLE_READ +# define PNFS_ENABLE_READ +# endif +# ifndef PNFS_DISABLE_WRITE +# define PNFS_ENABLE_WRITE +# endif + +# define PNFS_THREADING + +/* XXX: the thread-by-server model breaks down when using dense layouts, + * because multiple stripes could be mapped to a single data server, and + * the per-data-server thread would have to send a COMMIT for each stripe */ +//# define PNFS_THREAD_BY_SERVER + +#endif + + +/* forward declarations from nfs41.h */ +struct __nfs41_client; +struct __nfs41_session; +struct __nfs41_open_state; +struct __nfs41_root; +struct __stateid_arg; + + +/* pnfs error values, in order of increasing severity */ +enum pnfs_status { + PNFS_SUCCESS = 0, + PNFS_PENDING, + PNFS_READ_EOF, + PNFSERR_NOT_SUPPORTED, + PNFSERR_NOT_CONNECTED, + PNFSERR_IO, + PNFSERR_NO_DEVICE, + PNFSERR_NO_LAYOUT, + PNFSERR_INVALID_FH_LIST, + PNFSERR_INVALID_DS_INDEX, + PNFSERR_RESOURCES, + PNFSERR_LAYOUT_RECALLED, + PNFSERR_LAYOUT_CHANGED, +}; + +enum pnfs_layout_type { + PNFS_LAYOUTTYPE_FILE = 1, + PNFS_LAYOUTTYPE_OBJECT = 2, + PNFS_LAYOUTTYPE_BLOCK = 3 +}; + +enum pnfs_iomode { + PNFS_IOMODE_READ = 0x1, + PNFS_IOMODE_RW = 0x2, + PNFS_IOMODE_ANY = PNFS_IOMODE_READ | PNFS_IOMODE_RW +}; + +enum pnfs_layout_status { + /* a LAYOUTGET error indicated that this layout will never be granted */ + PNFS_LAYOUT_UNAVAILABLE = 0x10, + /* LAYOUTGET returned BADIOMODE, so a RW layout will never be granted */ + PNFS_LAYOUT_NOT_RW = 0x20, +}; + +enum pnfs_device_status { + /* GETDEVICEINFO was successful */ + PNFS_DEVICE_GRANTED = 0x1, + /* a bulk recall or lease expiration led to device invalidation */ + PNFS_DEVICE_REVOKED = 0x2, +}; + +enum pnfs_return_type { + PNFS_RETURN_FILE = 1, + PNFS_RETURN_FSID = 2, + PNFS_RETURN_ALL = 3 +}; + +#define NFL4_UFLG_MASK 0x0000003F +#define NFL4_UFLG_DENSE 0x00000001 +#define NFL4_UFLG_COMMIT_THRU_MDS 0x00000002 +#define NFL4_UFLG_STRIPE_UNIT_SIZE_MASK 0xFFFFFFC0 + +#define PNFS_DEVICEID_SIZE 16 + + +/* device */ +typedef struct __pnfs_device { + unsigned char deviceid[PNFS_DEVICEID_SIZE]; + enum pnfs_layout_type type; + enum pnfs_device_status status; + uint32_t layout_count; /* layouts using this device */ + CRITICAL_SECTION lock; +} pnfs_device; + +typedef struct __pnfs_stripe_indices { + uint32_t count; + uint32_t *arr; +} pnfs_stripe_indices; + +typedef struct __pnfs_data_server { + struct __nfs41_client *client; + multi_addr4 addrs; + SRWLOCK lock; +} pnfs_data_server; + +typedef struct __pnfs_data_server_list { + uint32_t count; + pnfs_data_server *arr; +} pnfs_data_server_list; + +typedef struct __pnfs_file_device { + pnfs_device device; + pnfs_stripe_indices stripes; + pnfs_data_server_list servers; + struct pnfs_file_device_list *devices; /* -> nfs41_client.devices */ + struct list_entry entry; /* position in devices */ +} pnfs_file_device; + + +/* layout */ +typedef struct __pnfs_layout_state { + nfs41_fh meta_fh; + stateid4 stateid; + struct list_entry entry; /* position in nfs41_client.layouts */ + struct list_entry layouts; /* list of pnfs_file_layouts */ + struct list_entry recalls; /* list of pnfs_layouts */ + enum pnfs_layout_status status; + bool_t return_on_close; + LONG open_count; /* for return on last close */ + uint32_t io_count; /* number of pending io operations */ + bool_t pending; /* pending LAYOUTGET/LAYOUTRETURN */ + SRWLOCK lock; + CONDITION_VARIABLE cond; +} pnfs_layout_state; + +typedef struct __pnfs_layout { + struct list_entry entry; + uint64_t offset; + uint64_t length; + enum pnfs_iomode iomode; + enum pnfs_layout_type type; +} pnfs_layout; + +typedef struct __pnfs_file_layout_handles { + uint32_t count; + nfs41_path_fh *arr; +} pnfs_file_layout_handles; + +typedef struct __pnfs_file_layout { + pnfs_layout layout; + pnfs_file_layout_handles filehandles; + unsigned char deviceid[PNFS_DEVICEID_SIZE]; + pnfs_file_device *device; + uint64_t pattern_offset; + uint32_t first_index; + uint32_t util; +} pnfs_file_layout; + + +/* pnfs_layout.c */ +struct pnfs_layout_list; +struct cb_layoutrecall_args; + +enum pnfs_status pnfs_layout_list_create( + OUT struct pnfs_layout_list **layouts_out); + +void pnfs_layout_list_free( + IN struct pnfs_layout_list *layouts); + +enum pnfs_status pnfs_layout_state_open( + IN struct __nfs41_open_state *state, + OUT pnfs_layout_state **layout_out); + +/* expects caller to hold an exclusive lock on pnfs_layout_state */ +enum pnfs_status pnfs_layout_state_prepare( + IN pnfs_layout_state *state, + IN struct __nfs41_session *session, + IN nfs41_path_fh *meta_file, + IN struct __stateid_arg *stateid, + IN enum pnfs_iomode iomode, + IN uint64_t offset, + IN uint64_t length); + +void pnfs_layout_state_close( + IN struct __nfs41_session *session, + IN struct __nfs41_open_state *state, + IN bool_t remove); + +enum pnfs_status pnfs_file_layout_recall( + IN struct __nfs41_client *client, + IN const struct cb_layoutrecall_args *recall); + +/* expects caller to hold a shared lock on pnfs_layout_state */ +enum pnfs_status pnfs_layout_recall_status( + IN const pnfs_layout_state *state, + IN const pnfs_layout *layout); + +void pnfs_layout_recall_fenced( + IN pnfs_layout_state *state, + IN const pnfs_layout *layout); + +/* expects caller to hold an exclusive lock on pnfs_layout_state */ +void pnfs_layout_io_start( + IN pnfs_layout_state *state); + +void pnfs_layout_io_finished( + IN pnfs_layout_state *state); + + +/* pnfs_device.c */ +struct pnfs_file_device_list; + +enum pnfs_status pnfs_file_device_list_create( + OUT struct pnfs_file_device_list **devices_out); + +void pnfs_file_device_list_free( + IN struct pnfs_file_device_list *devices); + +void pnfs_file_device_list_invalidate( + IN struct pnfs_file_device_list *devices); + +enum pnfs_status pnfs_file_device_get( + IN struct __nfs41_session *session, + IN struct pnfs_file_device_list *devices, + IN unsigned char *deviceid, + OUT pnfs_file_device **device_out); + +void pnfs_file_device_put( + IN pnfs_file_device *device); + +struct notify_deviceid4; /* from nfs41_callback.h */ +enum notify_deviceid_type4; +enum pnfs_status pnfs_file_device_notify( + IN struct pnfs_file_device_list *devices, + IN const struct notify_deviceid4 *change); + +enum pnfs_status pnfs_data_server_client( + IN struct __nfs41_root *root, + IN pnfs_data_server *server, + IN uint32_t default_lease, + OUT struct __nfs41_client **client_out); + + +/* pnfs_io.c */ +enum pnfs_status pnfs_read( + IN struct __nfs41_root *root, + IN struct __nfs41_open_state *state, + IN struct __stateid_arg *stateid, + IN pnfs_layout_state *layout, + IN uint64_t offset, + IN uint64_t length, + OUT unsigned char *buffer_out, + OUT ULONG *len_out); + +enum pnfs_status pnfs_write( + IN struct __nfs41_root *root, + IN struct __nfs41_open_state *state, + IN struct __stateid_arg *stateid, + IN pnfs_layout_state *layout, + IN uint64_t offset, + IN uint64_t length, + IN unsigned char *buffer, + OUT ULONG *len_out, + OUT nfs41_file_info *cinfo); + + +/* helper functions */ +#ifndef __REACTOS__ +__inline int is_dense( +#else +FORCEINLINE int is_dense( +#endif + IN const pnfs_file_layout *layout) +{ + return (layout->util & NFL4_UFLG_DENSE) != 0; +} +#ifndef __REACTOS__ +__inline int should_commit_to_mds( +#else +FORCEINLINE int should_commit_to_mds( +#endif + IN const pnfs_file_layout *layout) +{ + return (layout->util & NFL4_UFLG_COMMIT_THRU_MDS) != 0; +} +#ifndef __REACTOS__ +__inline uint32_t layout_unit_size( +#else +FORCEINLINE uint32_t layout_unit_size( +#endif + IN const pnfs_file_layout *layout) +{ + return layout->util & NFL4_UFLG_STRIPE_UNIT_SIZE_MASK; +} +#ifndef __REACTOS__ +__inline uint64_t stripe_unit_number( +#else +FORCEINLINE uint64_t stripe_unit_number( +#endif + IN const pnfs_file_layout *layout, + IN uint64_t offset, + IN uint32_t unit_size) +{ + const uint64_t relative_offset = offset - layout->pattern_offset; + return relative_offset / unit_size; +} +#ifndef __REACTOS__ +__inline uint64_t stripe_unit_offset( +#else +FORCEINLINE uint64_t stripe_unit_offset( +#endif + IN const pnfs_file_layout *layout, + IN uint64_t sui, + IN uint32_t unit_size) +{ + return layout->pattern_offset + unit_size * sui; +} +#ifndef __REACTOS__ +__inline uint32_t stripe_index( +#else +FORCEINLINE uint32_t stripe_index( +#endif + IN const pnfs_file_layout *layout, + IN uint64_t sui, + IN uint32_t stripe_count) +{ + return (uint32_t)((sui + layout->first_index) % stripe_count); +} +#ifndef __REACTOS__ +__inline uint32_t data_server_index( +#else +FORCEINLINE uint32_t data_server_index( +#endif + IN const pnfs_file_device *device, + IN uint32_t stripeid) +{ + return device->stripes.arr[stripeid]; +} + +#endif /* !__PNFS_H__ */ diff --git a/reactos/base/services/nfsd/pnfs_debug.c b/reactos/base/services/nfsd/pnfs_debug.c new file mode 100644 index 00000000000..2c07d005366 --- /dev/null +++ b/reactos/base/services/nfsd/pnfs_debug.c @@ -0,0 +1,123 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include +#include +#include "pnfs.h" +#include "daemon_debug.h" + + +const char* pnfs_error_string(enum pnfs_status status) +{ + switch (status) { + case PNFS_SUCCESS: return "PNFS_SUCCESS"; + case PNFS_PENDING: return "PNFS_PENDING"; + case PNFS_READ_EOF: return "PNFS_READ_EOF"; + case PNFSERR_NOT_SUPPORTED: return "PNFSERR_NOT_SUPPORTED"; + case PNFSERR_NOT_CONNECTED: return "PNFSERR_NOT_CONNECTED"; + case PNFSERR_IO: return "PNFSERR_IO"; + case PNFSERR_NO_DEVICE: return "PNFSERR_NO_DEVICE"; + case PNFSERR_NO_LAYOUT: return "PNFSERR_NO_LAYOUT"; + case PNFSERR_INVALID_FH_LIST: return "PNFSERR_INVALID_FH_LIST"; + case PNFSERR_INVALID_DS_INDEX: return "PNFSERR_INVALID_DS_INDEX"; + case PNFSERR_RESOURCES: return "PNFSERR_RESOURCES"; + case PNFSERR_LAYOUT_RECALLED: return "PNFSERR_LAYOUT_RECALLED"; + case PNFSERR_LAYOUT_CHANGED: return "PNFSERR_LAYOUT_CHANGED"; + default: return "Invalid pnfs status"; + } +} + +const char* pnfs_layout_type_string(enum pnfs_layout_type type) +{ + switch (type) { + case PNFS_LAYOUTTYPE_FILE: return "PNFS_LAYOUTTYPE_FILE"; + case PNFS_LAYOUTTYPE_OBJECT: return "PNFS_LAYOUTTYPE_OBJECT"; + case PNFS_LAYOUTTYPE_BLOCK: return "PNFS_LAYOUTTYPE_BLOCK"; + default: return "Invalid layout type"; + } +} + +const char* pnfs_iomode_string(enum pnfs_iomode iomode) +{ + switch (iomode) { + case PNFS_IOMODE_READ: return "PNFS_IOMODE_READ"; + case PNFS_IOMODE_RW: return "PNFS_IOMODE_RW"; + case PNFS_IOMODE_ANY: return "PNFS_IOMODE_ANY"; + default: return "Invalid io mode"; + } +} + +void dprint_deviceid( + IN int level, + IN const char *title, + IN const unsigned char *deviceid) +{ + /* deviceid is 16 bytes, so print it as 4 uints */ + uint32_t *p = (uint32_t*)deviceid; + dprintf(level, "%s%08X.%08X.%08X.%08X\n", + title, htonl(p[0]), htonl(p[1]), htonl(p[2]), htonl(p[3])); +} + +void dprint_layout( + IN int level, + IN const pnfs_file_layout *layout) +{ + dprintf(level, " type: %s\n", pnfs_layout_type_string(layout->layout.type)); + dprintf(level, " iomode: %s\n", pnfs_iomode_string(layout->layout.iomode)); + dprint_deviceid(level, " deviceid: ", layout->deviceid); + dprintf(level, " offset: %llu\n", layout->layout.offset); + dprintf(level, " length: %llu\n", layout->layout.length); + dprintf(level, " pattern_offset: %llu\n", layout->pattern_offset); + dprintf(level, " first_index: %u\n", layout->first_index); + dprintf(level, " dense: %u\n", is_dense(layout)); + dprintf(level, " commit_to_mds: %u\n", should_commit_to_mds(layout)); + dprintf(level, " stripe_unit_size: %u\n", layout_unit_size(layout)); + dprintf(level, " file handles: %u\n", layout->filehandles.count); +} + +#define MULTI_ADDR_BUFFER_LEN \ + (NFS41_ADDRS_PER_SERVER*(NFS41_UNIVERSAL_ADDR_LEN+1)+1) + +static void dprint_multi_addr( + IN int level, + IN uint32_t index, + IN const multi_addr4 *addrs) +{ + char buffer[MULTI_ADDR_BUFFER_LEN] = ""; + uint32_t i; + for (i = 0; i < addrs->count; i++) { + StringCchCatA(buffer, MULTI_ADDR_BUFFER_LEN, addrs->arr[i].uaddr); + StringCchCatA(buffer, MULTI_ADDR_BUFFER_LEN, " "); + } + dprintf(level, " servers[%d]: [ %s]\n", index, buffer); +} + +void dprint_device( + IN int level, + IN const pnfs_file_device *device) +{ + uint32_t i; + dprint_deviceid(level, " deviceid: ", device->device.deviceid); + dprintf(level, " type: %s\n", pnfs_layout_type_string(device->device.type)); + dprintf(level, " stripes: %u\n", device->stripes.count); + for (i = 0; i < device->servers.count; i++) + dprint_multi_addr(level, i, &device->servers.arr[i].addrs); +} diff --git a/reactos/base/services/nfsd/pnfs_device.c b/reactos/base/services/nfsd/pnfs_device.c new file mode 100644 index 00000000000..425da20fe24 --- /dev/null +++ b/reactos/base/services/nfsd/pnfs_device.c @@ -0,0 +1,365 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include +#include +#include + +#include "nfs41_ops.h" +#include "nfs41_callback.h" +#include "daemon_debug.h" + + +#define FDLVL 2 /* dprintf level for file device logging */ + + +/* pnfs_file_device_list */ +struct pnfs_file_device_list { + struct list_entry head; + CRITICAL_SECTION lock; +}; + +#define device_entry(pos) list_container(pos, pnfs_file_device, entry) + + +static enum pnfs_status file_device_create( + IN const unsigned char *deviceid, + IN struct pnfs_file_device_list *devices, + OUT pnfs_file_device **device_out) +{ + enum pnfs_status status = PNFS_SUCCESS; + pnfs_file_device *device; + + device = calloc(1, sizeof(pnfs_file_device)); + if (device == NULL) { + status = PNFSERR_RESOURCES; + goto out; + } + + memcpy(device->device.deviceid, deviceid, PNFS_DEVICEID_SIZE); + device->devices = devices; + InitializeCriticalSection(&device->device.lock); + *device_out = device; +out: + return status; +} + +static void file_device_free( + IN pnfs_file_device *device) +{ + free(device->servers.arr); + free(device->stripes.arr); + DeleteCriticalSection(&device->device.lock); + free(device); +} + +static int deviceid_compare( + const struct list_entry *entry, + const void *deviceid) +{ + const pnfs_file_device *device = device_entry(entry); + return memcmp(device->device.deviceid, deviceid, PNFS_DEVICEID_SIZE); +} + +static enum pnfs_status file_device_find_or_create( + IN const unsigned char *deviceid, + IN struct pnfs_file_device_list *devices, + OUT pnfs_file_device **device_out) +{ + struct list_entry *entry; + enum pnfs_status status; + + dprintf(FDLVL, "--> pnfs_file_device_find_or_create()\n"); + + EnterCriticalSection(&devices->lock); + + /* search for an existing device */ + entry = list_search(&devices->head, deviceid, deviceid_compare); + if (entry == NULL) { + /* create a new device */ + pnfs_file_device *device; + status = file_device_create(deviceid, devices, &device); + if (status == PNFS_SUCCESS) { + /* add it to the list */ + list_add_tail(&devices->head, &device->entry); + *device_out = device; + + dprintf(FDLVL, "<-- pnfs_file_device_find_or_create() " + "returning new device %p\n", device); + } else { + dprintf(FDLVL, "<-- pnfs_file_device_find_or_create() " + "returning %s\n", pnfs_error_string(status)); + } + } else { + *device_out = device_entry(entry); + status = PNFS_SUCCESS; + + dprintf(FDLVL, "<-- pnfs_file_device_find_or_create() " + "returning existing device %p\n", *device_out); + } + + LeaveCriticalSection(&devices->lock); + return status; +} + + +enum pnfs_status pnfs_file_device_list_create( + OUT struct pnfs_file_device_list **devices_out) +{ + enum pnfs_status status = PNFS_SUCCESS; + struct pnfs_file_device_list *devices; + + devices = calloc(1, sizeof(struct pnfs_file_device_list)); + if (devices == NULL) { + status = PNFSERR_RESOURCES; + goto out; + } + + list_init(&devices->head); + InitializeCriticalSection(&devices->lock); + + *devices_out = devices; +out: + return status; +} + +void pnfs_file_device_list_free( + IN struct pnfs_file_device_list *devices) +{ + struct list_entry *entry, *tmp; + + EnterCriticalSection(&devices->lock); + + list_for_each_tmp(entry, tmp, &devices->head) + file_device_free(device_entry(entry)); + + LeaveCriticalSection(&devices->lock); + DeleteCriticalSection(&devices->lock); + free(devices); +} + +void pnfs_file_device_list_invalidate( + IN struct pnfs_file_device_list *devices) +{ + struct list_entry *entry, *tmp; + pnfs_file_device *device; + + dprintf(FDLVL, "--> pnfs_file_device_list_invalidate()\n"); + + EnterCriticalSection(&devices->lock); + + list_for_each_tmp(entry, tmp, &devices->head) { + device = device_entry(entry); + EnterCriticalSection(&device->device.lock); + /* if there are layouts still using the device, flag it + * as revoked and clean up on last reference */ + if (device->device.layout_count) { + device->device.status |= PNFS_DEVICE_REVOKED; + LeaveCriticalSection(&device->device.lock); + } else { + LeaveCriticalSection(&device->device.lock); + /* no layouts are using it, so it's safe to free */ + list_remove(entry); + file_device_free(device); + } + } + + LeaveCriticalSection(&devices->lock); + + dprintf(FDLVL, "<-- pnfs_file_device_list_invalidate()\n"); +} + + +/* pnfs_file_device */ +enum pnfs_status pnfs_file_device_get( + IN nfs41_session *session, + IN struct pnfs_file_device_list *devices, + IN unsigned char *deviceid, + OUT pnfs_file_device **device_out) +{ + pnfs_file_device *device; + enum pnfs_status status; + enum nfsstat4 nfsstat; + + dprintf(FDLVL, "--> pnfs_file_device_get()\n"); + + status = file_device_find_or_create(deviceid, devices, &device); + if (status) + goto out; + + EnterCriticalSection(&device->device.lock); + + /* don't give out a device that's been revoked */ + if (device->device.status & PNFS_DEVICE_REVOKED) + status = PNFSERR_NO_DEVICE; + else if (device->device.status & PNFS_DEVICE_GRANTED) + status = PNFS_SUCCESS; + else { + nfsstat = pnfs_rpc_getdeviceinfo(session, deviceid, device); + if (nfsstat == NFS4_OK) { + device->device.status = PNFS_DEVICE_GRANTED; + status = PNFS_SUCCESS; + + dprintf(FDLVL, "Received device info:\n"); + dprint_device(FDLVL, device); + } else { + status = PNFSERR_NO_DEVICE; + + eprintf("pnfs_rpc_getdeviceinfo() failed with %s\n", + nfs_error_string(nfsstat)); + } + } + + if (status == PNFS_SUCCESS) { + device->device.layout_count++; + dprintf(FDLVL, "pnfs_file_device_get() -> %u\n", + device->device.layout_count); + *device_out = device; + } + + LeaveCriticalSection(&device->device.lock); +out: + dprintf(FDLVL, "<-- pnfs_file_device_get() returning %s\n", + pnfs_error_string(status)); + return status; +} + +void pnfs_file_device_put( + IN pnfs_file_device *device) +{ + uint32_t count; + EnterCriticalSection(&device->device.lock); + count = --device->device.layout_count; + dprintf(FDLVL, "pnfs_file_device_put() -> %u\n", count); + + /* if the device was revoked, remove/free the device on last reference */ + if (count == 0 && device->device.status & PNFS_DEVICE_REVOKED) { + EnterCriticalSection(&device->devices->lock); + list_remove(&device->entry); + LeaveCriticalSection(&device->devices->lock); + + LeaveCriticalSection(&device->device.lock); + + file_device_free(device); + dprintf(FDLVL, "revoked file device freed after last reference\n"); + } else { + LeaveCriticalSection(&device->device.lock); + } +} + +static enum pnfs_status data_client_status( + IN pnfs_data_server *server, + OUT nfs41_client **client_out) +{ + enum pnfs_status status = PNFSERR_NOT_CONNECTED; + + if (server->client) { + dprintf(FDLVL, "pnfs_data_server_client() returning " + "existing client %llu\n", server->client->clnt_id); + *client_out = server->client; + status = PNFS_SUCCESS; + } + return status; +} + +enum pnfs_status pnfs_data_server_client( + IN nfs41_root *root, + IN pnfs_data_server *server, + IN uint32_t default_lease, + OUT nfs41_client **client_out) +{ + int status; + enum pnfs_status pnfsstat; + + dprintf(FDLVL, "--> pnfs_data_server_client('%s')\n", + server->addrs.arr[0].uaddr); + + /* if we've already created the client, return it */ + AcquireSRWLockShared(&server->lock); + pnfsstat = data_client_status(server, client_out); + ReleaseSRWLockShared(&server->lock); + + if (!pnfsstat) + goto out; + + AcquireSRWLockExclusive(&server->lock); + + pnfsstat = data_client_status(server, client_out); + if (pnfsstat) { + status = nfs41_root_mount_addrs(root, &server->addrs, 1, default_lease, + &server->client); + if (status) { + dprintf(FDLVL, "data_client_create('%s') failed with %d\n", + server->addrs.arr[0].uaddr, status); + } else { + *client_out = server->client; + pnfsstat = PNFS_SUCCESS; + + dprintf(FDLVL, "pnfs_data_server_client() returning new client " + "%llu\n", server->client->clnt_id); + } + } + + ReleaseSRWLockExclusive(&server->lock); +out: + return pnfsstat; +} + + +/* CB_NOTIFY_DEVICEID */ +enum pnfs_status pnfs_file_device_notify( + IN struct pnfs_file_device_list *devices, + IN const struct notify_deviceid4 *change) +{ + struct list_entry *entry; + enum pnfs_status status = PNFSERR_NO_DEVICE; + + dprintf(FDLVL, "--> pnfs_file_device_notify(%u, %0llX:%0llX)\n", + change->type, change->deviceid); + + if (change->layouttype != PNFS_LAYOUTTYPE_FILE) { + status = PNFSERR_NOT_SUPPORTED; + goto out; + } + + EnterCriticalSection(&devices->lock); + + entry = list_search(&devices->head, change->deviceid, deviceid_compare); + if (entry) { + dprintf(FDLVL, "found file device %p\n", device_entry(entry)); + + if (change->type == NOTIFY_DEVICEID4_CHANGE) { + /* if (change->immediate) ... */ + dprintf(FDLVL, "CHANGE (%u)\n", change->immediate); + } else if (change->type == NOTIFY_DEVICEID4_DELETE) { + /* This notification MUST NOT be sent if the client + * has a layout that refers to the device ID. */ + dprintf(FDLVL, "DELETE\n"); + } + status = PNFS_SUCCESS; + } + + LeaveCriticalSection(&devices->lock); +out: + dprintf(FDLVL, "<-- pnfs_file_device_notify() returning %s\n", + pnfs_error_string(status)); + return status; +} diff --git a/reactos/base/services/nfsd/pnfs_io.c b/reactos/base/services/nfsd/pnfs_io.c new file mode 100644 index 00000000000..8ca7df666bc --- /dev/null +++ b/reactos/base/services/nfsd/pnfs_io.c @@ -0,0 +1,871 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include +#include + +#include "nfs41_ops.h" +#include "util.h" +#include "daemon_debug.h" + + +#define IOLVL 2 /* dprintf level for pnfs io logging */ + +#define file_layout_entry(pos) list_container(pos, pnfs_file_layout, layout.entry) + +typedef struct __pnfs_io_pattern { + struct __pnfs_io_thread *threads; + nfs41_root *root; + nfs41_path_fh *meta_file; + const stateid_arg *stateid; + pnfs_layout_state *state; + unsigned char *buffer; + uint64_t offset_start; + uint64_t offset_end; + uint32_t count; + uint32_t default_lease; +} pnfs_io_pattern; + +typedef struct __pnfs_io_thread { + nfs41_write_verf verf; + pnfs_io_pattern *pattern; + pnfs_file_layout *layout; + nfs41_path_fh *file; + uint64_t offset; + uint32_t id; + enum stable_how4 stable; +} pnfs_io_thread; + +typedef struct __pnfs_io_unit { + unsigned char *buffer; + uint64_t offset; + uint64_t length; + uint32_t stripeid; + uint32_t serverid; +} pnfs_io_unit; + +typedef uint32_t (WINAPI *pnfs_io_thread_fn)(void*); + + +static enum pnfs_status stripe_next_unit( + IN const pnfs_file_layout *layout, + IN uint32_t stripeid, + IN uint64_t *position, + IN uint64_t offset_end, + OUT pnfs_io_unit *io); + +/* 13.4.2. Interpreting the File Layout Using Sparse Packing + * http://tools.ietf.org/html/rfc5661#section-13.4.2 */ + +static enum pnfs_status get_sparse_fh( + IN pnfs_file_layout *layout, + IN nfs41_path_fh *meta_file, + IN uint32_t stripeid, + OUT nfs41_path_fh **file_out) +{ + const uint32_t filehandle_count = layout->filehandles.count; + const uint32_t server_count = layout->device->servers.count; + enum pnfs_status status = PNFS_SUCCESS; + + if (filehandle_count == server_count) { + const uint32_t serverid = data_server_index(layout->device, stripeid); + *file_out = &layout->filehandles.arr[serverid]; + } else if (filehandle_count == 1) { + *file_out = &layout->filehandles.arr[0]; + } else if (filehandle_count == 0) { + *file_out = meta_file; + } else { + eprintf("invalid sparse layout! has %u file handles " + "and %u servers\n", filehandle_count, server_count); + status = PNFSERR_INVALID_FH_LIST; + } + return status; +} + +/* 13.4.3. Interpreting the File Layout Using Dense Packing +* http://tools.ietf.org/html/rfc5661#section-13.4.3 */ + +static enum pnfs_status get_dense_fh( + IN pnfs_file_layout *layout, + IN uint32_t stripeid, + OUT nfs41_path_fh **file_out) +{ + const uint32_t filehandle_count = layout->filehandles.count; + const uint32_t stripe_count = layout->device->stripes.count; + enum pnfs_status status = PNFS_SUCCESS; + + if (filehandle_count == stripe_count) { + *file_out = &layout->filehandles.arr[stripeid]; + } else { + eprintf("invalid dense layout! has %u file handles " + "and %u stripes\n", filehandle_count, stripe_count); + status = PNFSERR_INVALID_FH_LIST; + } + return status; +} + +static __inline bool_t layout_compatible( + IN const pnfs_layout *layout, + IN enum pnfs_iomode iomode, + IN uint64_t position) +{ + return layout->iomode >= iomode + && layout->offset <= position + && position < layout->offset + layout->length; +} + +/* count stripes for all layout segments that intersect the range + * and have not been covered by previous segments */ +static uint32_t thread_count( + IN pnfs_layout_state *state, + IN enum pnfs_iomode iomode, + IN uint64_t offset, + IN uint64_t length) +{ + uint64_t position = offset; + struct list_entry *entry; + uint32_t count = 0; + + list_for_each(entry, &state->layouts) { + pnfs_file_layout *layout = file_layout_entry(entry); + + if (!layout_compatible(&layout->layout, iomode, position)) + continue; + + position = layout->layout.offset + layout->layout.length; + count += layout->device->stripes.count; + } + return count; +} + +static enum pnfs_status thread_init( + IN pnfs_io_pattern *pattern, + IN pnfs_io_thread *thread, + IN pnfs_file_layout *layout, + IN uint32_t stripeid, + IN uint64_t offset) +{ + thread->pattern = pattern; + thread->layout = layout; + thread->stable = FILE_SYNC4; + thread->offset = offset; + thread->id = stripeid; + + return is_dense(layout) ? get_dense_fh(layout, stripeid, &thread->file) + : get_sparse_fh(layout, pattern->meta_file, stripeid, &thread->file); +} + +static enum pnfs_status pattern_threads_init( + IN pnfs_io_pattern *pattern, + IN enum pnfs_iomode iomode, + IN uint64_t offset, + IN uint64_t length) +{ + pnfs_io_unit io; + uint64_t position = offset; + struct list_entry *entry; + uint32_t s, t = 0; + enum pnfs_status status = PNFS_SUCCESS; + + list_for_each(entry, &pattern->state->layouts) { + pnfs_file_layout *layout = file_layout_entry(entry); + + if (!layout_compatible(&layout->layout, iomode, position)) + continue; + + for (s = 0; s < layout->device->stripes.count; s++) { + uint64_t off = position; + + /* does the range contain this stripe? */ + status = stripe_next_unit(layout, s, &off, offset + length, &io); + if (status != PNFS_PENDING) + continue; + + if (t >= pattern->count) { /* miscounted threads needed? */ + status = PNFSERR_NO_LAYOUT; + goto out; + } + + status = thread_init(pattern, &pattern->threads[t++], layout, s, off); + if (status) + goto out; + } + position = layout->layout.offset + layout->layout.length; + } + + if (position < offset + length) { + /* unable to satisfy the entire range */ + status = PNFSERR_NO_LAYOUT; + goto out; + } + + /* update the pattern with the actual number of threads used */ + pattern->count = t; +out: + return status; +} + +static enum pnfs_status pattern_init( + IN pnfs_io_pattern *pattern, + IN nfs41_root *root, + IN nfs41_path_fh *meta_file, + IN const stateid_arg *stateid, + IN pnfs_layout_state *state, + IN unsigned char *buffer, + IN enum pnfs_iomode iomode, + IN uint64_t offset, + IN uint64_t length, + IN uint32_t default_lease) +{ + enum pnfs_status status; + + /* calculate an upper bound on the number of threads to allocate */ + pattern->count = thread_count(state, iomode, offset, length); + pattern->threads = calloc(pattern->count, sizeof(pnfs_io_thread)); + if (pattern->threads == NULL) { + status = PNFSERR_RESOURCES; + goto out; + } + + /* information shared between threads */ + pattern->root = root; + pattern->meta_file = meta_file; + pattern->stateid = stateid; + pattern->state = state; + pattern->buffer = buffer; + pattern->offset_start = offset; + pattern->offset_end = offset + length; + pattern->default_lease = default_lease; + + /* initialize a thread for every stripe necessary to cover the range */ + status = pattern_threads_init(pattern, iomode, offset, length); + if (status) + goto out_err_free; + + /* take a reference on the layout so we don't return it during io */ + pnfs_layout_io_start(state); +out: + return status; + +out_err_free: + free(pattern->threads); + pattern->threads = NULL; + goto out; +} + +static void pattern_free( + IN pnfs_io_pattern *pattern) +{ + /* inform the layout that our io is finished */ + pnfs_layout_io_finished(pattern->state); + free(pattern->threads); +} + +static __inline uint64_t positive_remainder( + IN uint64_t dividend, + IN uint32_t divisor) +{ + const uint64_t remainder = dividend % divisor; + return remainder < divisor ? remainder : remainder + divisor; +} + +/* return the next unit of the given stripeid */ +static enum pnfs_status stripe_next_unit( + IN const pnfs_file_layout *layout, + IN uint32_t stripeid, + IN uint64_t *position, + IN uint64_t offset_end, + OUT pnfs_io_unit *io) +{ + const uint32_t unit_size = layout_unit_size(layout); + const uint32_t stripe_count = layout->device->stripes.count; + uint64_t sui = stripe_unit_number(layout, *position, unit_size); + + /* advance to the desired stripeid */ + sui += abs(stripeid - stripe_index(layout, sui, stripe_count)); + + io->offset = stripe_unit_offset(layout, sui, unit_size); + if (io->offset < *position) /* don't start before position */ + io->offset = *position; + else + *position = io->offset; + + io->length = stripe_unit_offset(layout, sui + 1, unit_size); + if (io->length > offset_end) /* don't end past offset_end */ + io->length = offset_end; + + if (io->offset >= io->length) /* nothing to do, return success */ + return PNFS_SUCCESS; + + io->length -= io->offset; + + if (is_dense(layout)) { + const uint64_t rel_offset = io->offset - layout->pattern_offset; + const uint64_t remainder = positive_remainder(rel_offset, unit_size); + const uint32_t stride = unit_size * stripe_count; + + io->offset = (rel_offset / stride) * unit_size + remainder; + } + return PNFS_PENDING; +} + +static enum pnfs_status thread_next_unit( + IN pnfs_io_thread *thread, + OUT pnfs_io_unit *io) +{ + pnfs_io_pattern *pattern = thread->pattern; + pnfs_layout_state *state = pattern->state; + enum pnfs_status status; + + AcquireSRWLockShared(&state->lock); + + /* stop io if the layout is recalled */ + status = pnfs_layout_recall_status(state, &thread->layout->layout); + if (status) + goto out_unlock; + + status = stripe_next_unit(thread->layout, thread->id, + &thread->offset, pattern->offset_end, io); + if (status == PNFS_PENDING) + io->buffer = pattern->buffer + thread->offset - pattern->offset_start; + +out_unlock: + ReleaseSRWLockShared(&state->lock); + return status; +} + +static enum pnfs_status thread_data_server( + IN pnfs_io_thread *thread, + OUT pnfs_data_server **server_out) +{ + pnfs_file_device *device = thread->layout->device; + const uint32_t serverid = data_server_index(device, thread->id); + + if (serverid >= device->servers.count) + return PNFSERR_INVALID_DS_INDEX; + + *server_out = &device->servers.arr[serverid]; + return PNFS_SUCCESS; +} + +static enum pnfs_status pattern_join( + IN HANDLE *threads, + IN DWORD count) +{ + DWORD status; + /* WaitForMultipleObjects() supports a maximum of 64 objects */ + while (count) { + const DWORD n = min(count, MAXIMUM_WAIT_OBJECTS); + status = WaitForMultipleObjects(n, threads, TRUE, INFINITE); + if (status != WAIT_OBJECT_0) + return PNFSERR_RESOURCES; + + count -= n; + threads += n; + } + return PNFS_SUCCESS; +} + +static enum pnfs_status pattern_fork( + IN pnfs_io_pattern *pattern, + IN pnfs_io_thread_fn thread_fn) +{ + HANDLE *threads; + uint32_t i; + enum pnfs_status status = PNFS_SUCCESS; + + if (pattern->count == 0) + goto out; + + if (pattern->count == 1) { + /* no need to fork if there's only 1 thread */ + status = (enum pnfs_status)thread_fn(pattern->threads); + goto out; + } + + /* create a thread for each unit that has actual io */ + threads = calloc(pattern->count, sizeof(HANDLE)); + if (threads == NULL) { + status = PNFSERR_RESOURCES; + goto out; + } + + for (i = 0; i < pattern->count; i++) { + threads[i] = (HANDLE)_beginthreadex(NULL, 0, + thread_fn, &pattern->threads[i], 0, NULL); + if (threads[i] == NULL) { + eprintf("_beginthreadex() failed with %d\n", GetLastError()); + pattern->count = i; /* join any threads already started */ + break; + } + } + + /* wait on all threads to finish */ + status = pattern_join(threads, pattern->count); + if (status) { + eprintf("pattern_join() failed with %s\n", pnfs_error_string(status)); + goto out; + } + + for (i = 0; i < pattern->count; i++) { + /* keep track of the most severe error returned by a thread */ + DWORD exitcode; + if (GetExitCodeThread(threads[i], &exitcode)) + status = max(status, (enum pnfs_status)exitcode); + + CloseHandle(threads[i]); + } + + free(threads); +out: + return status; +} + +static uint64_t pattern_bytes_transferred( + IN pnfs_io_pattern *pattern, + OUT OPTIONAL enum stable_how4 *stable) +{ + uint64_t lowest_offset = pattern->offset_end; + uint32_t i; + + if (stable) *stable = FILE_SYNC4; + + for (i = 0; i < pattern->count; i++) { + lowest_offset = min(lowest_offset, pattern->threads[i].offset); + if (stable) *stable = min(*stable, pattern->threads[i].stable); + } + return lowest_offset - pattern->offset_start; +} + + +static enum pnfs_status map_ds_error( + IN enum nfsstat4 nfsstat, + IN pnfs_layout_state *state, + IN const pnfs_file_layout *layout) +{ + switch (nfsstat) { + case NO_ERROR: + return PNFS_SUCCESS; + + /* 13.11 Layout Revocation and Fencing + * http://tools.ietf.org/html/rfc5661#section-13.11 + * if we've been fenced, we'll either get ERR_STALE when we PUTFH + * something in layout.filehandles, or ERR_PNFS_NO_LAYOUT when + * attempting to READ or WRITE */ + case NFS4ERR_STALE: + case NFS4ERR_PNFS_NO_LAYOUT: + dprintf(IOLVL, "data server fencing detected!\n"); + + pnfs_layout_recall_fenced(state, &layout->layout); + + /* return CHANGED to prevent any further use of the layout */ + return PNFSERR_LAYOUT_CHANGED; + + default: + return PNFSERR_IO; + } +} + +static uint32_t WINAPI file_layout_read_thread(void *args) +{ + pnfs_io_unit io; + stateid_arg stateid; + pnfs_io_thread *thread = (pnfs_io_thread*)args; + pnfs_io_pattern *pattern = thread->pattern; + pnfs_data_server *server; + nfs41_client *client; + uint32_t maxreadsize, bytes_read, total_read; + enum pnfs_status status; + enum nfsstat4 nfsstat; + bool_t eof; + + dprintf(IOLVL, "--> file_layout_read_thread(%u)\n", thread->id); + + /* get the data server for this thread */ + status = thread_data_server(thread, &server); + if (status) { + eprintf("thread_data_server() failed with %s\n", + pnfs_error_string(status)); + goto out; + } + /* find or establish a client for this data server */ + status = pnfs_data_server_client(pattern->root, + server, pattern->default_lease, &client); + if (status) { + eprintf("pnfs_data_server_client() failed with %s\n", + pnfs_error_string(status)); + goto out; + } + + memcpy(&stateid, pattern->stateid, sizeof(stateid)); + stateid.stateid.seqid = 0; + + total_read = 0; + while (thread_next_unit(thread, &io) == PNFS_PENDING) { + maxreadsize = max_read_size(client->session, &thread->file->fh); + if (io.length > maxreadsize) + io.length = maxreadsize; + + nfsstat = nfs41_read(client->session, thread->file, &stateid, + io.offset, (uint32_t)io.length, io.buffer, &bytes_read, &eof); + if (nfsstat) { + eprintf("nfs41_read() failed with %s\n", + nfs_error_string(nfsstat)); + status = map_ds_error(nfsstat, pattern->state, thread->layout); + break; + } + + total_read += bytes_read; + thread->offset += bytes_read; + + if (eof) { + dprintf(IOLVL, "read thread %u reached eof: offset %llu\n", + thread->id, thread->offset); + status = total_read ? PNFS_SUCCESS : PNFS_READ_EOF; + break; + } + } +out: + dprintf(IOLVL, "<-- file_layout_read_thread(%u) returning %s\n", + thread->id, pnfs_error_string(status)); + return status; +} + +static uint32_t WINAPI file_layout_write_thread(void *args) +{ + pnfs_io_unit io; + stateid_arg stateid; + pnfs_io_thread *thread = (pnfs_io_thread*)args; + pnfs_io_pattern *pattern = thread->pattern; + pnfs_data_server *server; + nfs41_client *client; + const uint64_t offset_start = thread->offset; + uint64_t commit_min, commit_max; + uint32_t maxwritesize, bytes_written, total_written; + enum pnfs_status status; + enum nfsstat4 nfsstat; + + dprintf(IOLVL, "--> file_layout_write_thread(%u)\n", thread->id); + + /* get the data server for this thread */ + status = thread_data_server(thread, &server); + if (status) { + eprintf("thread_data_server() failed with %s\n", + pnfs_error_string(status)); + goto out; + } + /* find or establish a client for this data server */ + status = pnfs_data_server_client(pattern->root, + server, pattern->default_lease, &client); + if (status) { + eprintf("pnfs_data_server_client() failed with %s\n", + pnfs_error_string(status)); + goto out; + } + + memcpy(&stateid, pattern->stateid, sizeof(stateid)); + stateid.stateid.seqid = 0; + + maxwritesize = max_write_size(client->session, &thread->file->fh); + +retry_write: + thread->offset = offset_start; + thread->stable = FILE_SYNC4; + commit_min = NFS4_UINT64_MAX; + commit_max = 0; + total_written = 0; + + while (thread_next_unit(thread, &io) == PNFS_PENDING) { + if (io.length > maxwritesize) + io.length = maxwritesize; + + nfsstat = nfs41_write(client->session, thread->file, &stateid, + io.buffer, (uint32_t)io.length, io.offset, UNSTABLE4, + &bytes_written, &thread->verf, NULL); + if (nfsstat) { + eprintf("nfs41_write() failed with %s\n", + nfs_error_string(nfsstat)); + status = map_ds_error(nfsstat, pattern->state, thread->layout); + break; + } + if (!verify_write(&thread->verf, &thread->stable)) + goto retry_write; + + total_written += bytes_written; + thread->offset += bytes_written; + + /* track the range for commit */ + if (commit_min > io.offset) + commit_min = io.offset; + if (commit_max < io.offset + io.length) + commit_max = io.offset + io.length; + } + + /* nothing to commit */ + if (commit_max <= commit_min) + goto out; + /* layout changed; redo all io against metadata server */ + if (status == PNFSERR_LAYOUT_CHANGED) + goto out; + /* the data is already in stable storage */ + if (thread->stable != UNSTABLE4) + goto out; + /* the metadata server expects us to commit there instead */ + if (should_commit_to_mds(thread->layout)) + goto out; + + dprintf(1, "sending COMMIT to data server for offset=%lld len=%lld\n", + commit_min, commit_max - commit_min); + nfsstat = nfs41_commit(client->session, thread->file, + commit_min, (uint32_t)(commit_max - commit_min), 0, &thread->verf, NULL); + + if (nfsstat) + status = map_ds_error(nfsstat, pattern->state, thread->layout); + else if (!verify_commit(&thread->verf)) { + /* resend the writes unless the layout was recalled */ + if (status != PNFSERR_LAYOUT_RECALLED) + goto retry_write; + status = PNFSERR_IO; + } else { + /* on successful commit, leave pnfs_status unchanged; if the + * layout was recalled, we still want to return the error */ + thread->stable = DATA_SYNC4; + } +out: + dprintf(IOLVL, "<-- file_layout_write_thread(%u) returning %s\n", + thread->id, pnfs_error_string(status)); + return status; +} + + +enum pnfs_status pnfs_read( + IN nfs41_root *root, + IN nfs41_open_state *state, + IN stateid_arg *stateid, + IN pnfs_layout_state *layout, + IN uint64_t offset, + IN uint64_t length, + OUT unsigned char *buffer_out, + OUT ULONG *len_out) +{ + pnfs_io_pattern pattern; + enum pnfs_status status; + + dprintf(IOLVL, "--> pnfs_read(%llu, %llu)\n", offset, length); + + *len_out = 0; + + AcquireSRWLockExclusive(&layout->lock); + + /* get layouts/devices for the entire range; PNFS_PENDING means we + * dropped the lock to send an rpc, so repeat until it succeeds */ + do { + status = pnfs_layout_state_prepare(layout, state->session, + &state->file, stateid, PNFS_IOMODE_READ, offset, length); + } while (status == PNFS_PENDING); + + if (status == PNFS_SUCCESS) { + /* interpret the layout and set up threads for io */ + status = pattern_init(&pattern, root, &state->file, stateid, + layout, buffer_out, PNFS_IOMODE_READ, offset, length, + state->session->lease_time); + if (status) + eprintf("pattern_init() failed with %s\n", + pnfs_error_string(status)); + } + + ReleaseSRWLockExclusive(&layout->lock); + + if (status) + goto out; + + status = pattern_fork(&pattern, file_layout_read_thread); + if (status != PNFS_SUCCESS && status != PNFS_READ_EOF) + goto out_free_pattern; + + *len_out = (ULONG)pattern_bytes_transferred(&pattern, NULL); + +out_free_pattern: + pattern_free(&pattern); +out: + dprintf(IOLVL, "<-- pnfs_read() returning %s\n", + pnfs_error_string(status)); + return status; +} + +static enum pnfs_status mds_commit( + IN nfs41_open_state *state, + IN uint64_t offset, + IN uint32_t length, + IN const pnfs_io_pattern *pattern, + OUT nfs41_file_info *info) +{ + nfs41_write_verf verf; + enum nfsstat4 nfsstat; + enum pnfs_status status = PNFS_SUCCESS; + uint32_t i; + + nfsstat = nfs41_commit(state->session, + &state->file, offset, length, 1, &verf, info); + if (nfsstat) { + eprintf("nfs41_commit() to mds failed with %s\n", + nfs_error_string(nfsstat)); + status = PNFSERR_IO; + goto out; + } + + /* 13.7. COMMIT through Metadata Server: + * If nfl_util & NFL4_UFLG_COMMIT_THRU_MDS is TRUE, then in order to + * maintain the current NFSv4.1 commit and recovery model, the data + * servers MUST return a common writeverf verifier in all WRITE + * responses for a given file layout, and the metadata server's + * COMMIT implementation must return the same writeverf. */ + for (i = 0; i < pattern->count; i++) { + const pnfs_io_thread *thread = &pattern->threads[i]; + if (thread->stable != UNSTABLE4) /* already committed */ + continue; + + if (!should_commit_to_mds(thread->layout)) { + /* commit to mds is not allowed on this layout */ + eprintf("mds commit: failed to commit to data server\n"); + status = PNFSERR_IO; + break; + } + if (memcmp(verf.verf, thread->verf.verf, NFS4_VERIFIER_SIZE) != 0) { + eprintf("mds commit verifier doesn't match ds write verifiers\n"); + status = PNFSERR_IO; + break; + } + } +out: + return status; +} + +static enum pnfs_status layout_commit( + IN nfs41_open_state *state, + IN pnfs_layout_state *layout, + IN uint64_t offset, + IN uint64_t length, + OUT nfs41_file_info *info) +{ + stateid4 layout_stateid; + uint64_t last_offset = offset + length - 1; + uint64_t *new_last_offset = NULL; + enum nfsstat4 nfsstat; + enum pnfs_status status = PNFS_SUCCESS; + + AcquireSRWLockExclusive(&state->lock); + /* if this is past the current eof, update the open state's + * last offset, and pass a pointer to LAYOUTCOMMIT */ + if (state->pnfs_last_offset < last_offset || + (state->pnfs_last_offset == 0 && last_offset == 0)) { + state->pnfs_last_offset = last_offset; + new_last_offset = &last_offset; + } + ReleaseSRWLockExclusive(&state->lock); + + AcquireSRWLockShared(&layout->lock); + memcpy(&layout_stateid, &layout->stateid, sizeof(layout_stateid)); + ReleaseSRWLockShared(&layout->lock); + + dprintf(1, "LAYOUTCOMMIT for offset=%lld len=%lld new_last_offset=%u\n", + offset, length, new_last_offset ? 1 : 0); + nfsstat = pnfs_rpc_layoutcommit(state->session, &state->file, + &layout_stateid, offset, length, new_last_offset, NULL, info); + if (nfsstat) { + dprintf(IOLVL, "pnfs_rpc_layoutcommit() failed with %s\n", + nfs_error_string(nfsstat)); + status = PNFSERR_IO; + } + return status; +} + +enum pnfs_status pnfs_write( + IN nfs41_root *root, + IN nfs41_open_state *state, + IN stateid_arg *stateid, + IN pnfs_layout_state *layout, + IN uint64_t offset, + IN uint64_t length, + IN unsigned char *buffer, + OUT ULONG *len_out, + OUT nfs41_file_info *info) +{ + pnfs_io_pattern pattern; + enum stable_how4 stable; + enum pnfs_status status; + + dprintf(IOLVL, "--> pnfs_write(%llu, %llu)\n", offset, length); + + *len_out = 0; + + AcquireSRWLockExclusive(&layout->lock); + + /* get layouts/devices for the entire range; PNFS_PENDING means we + * dropped the lock to send an rpc, so repeat until it succeeds */ + do { + status = pnfs_layout_state_prepare(layout, state->session, + &state->file, stateid, PNFS_IOMODE_RW, offset, length); + } while (status == PNFS_PENDING); + + if (status == PNFS_SUCCESS) { + /* interpret the layout and set up threads for io */ + status = pattern_init(&pattern, root, &state->file, stateid, + layout, buffer, PNFS_IOMODE_RW, offset, length, + state->session->lease_time); + if (status) + eprintf("pattern_init() failed with %s\n", + pnfs_error_string(status)); + } + + ReleaseSRWLockExclusive(&layout->lock); + + if (status) + goto out; + + status = pattern_fork(&pattern, file_layout_write_thread); + /* on layout recall, we still attempt to commit what we wrote */ + if (status != PNFS_SUCCESS && status != PNFSERR_LAYOUT_RECALLED) + goto out_free_pattern; + + *len_out = (ULONG)pattern_bytes_transferred(&pattern, &stable); + if (*len_out == 0) + goto out_free_pattern; + + if (stable == UNSTABLE4) { + /* send COMMIT to the mds and verify against all ds writes */ + status = mds_commit(state, offset, *len_out, &pattern, info); + } else if (stable == DATA_SYNC4) { + /* send LAYOUTCOMMIT to sync the metadata */ + status = layout_commit(state, layout, offset, *len_out, info); + } else { + /* send a GETATTR to update the cached size */ + bitmap4 attr_request; + nfs41_superblock_getattr_mask(state->file.fh.superblock, &attr_request); + nfs41_getattr(state->session, &state->file, &attr_request, info); + } +out_free_pattern: + pattern_free(&pattern); +out: + dprintf(IOLVL, "<-- pnfs_write() returning %s\n", + pnfs_error_string(status)); + return status; +} \ No newline at end of file diff --git a/reactos/base/services/nfsd/pnfs_layout.c b/reactos/base/services/nfsd/pnfs_layout.c new file mode 100644 index 00000000000..243106ae948 --- /dev/null +++ b/reactos/base/services/nfsd/pnfs_layout.c @@ -0,0 +1,1289 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include + +#include "nfs41_ops.h" +#include "nfs41_callback.h" +#include "util.h" +#include "daemon_debug.h" + + +#define FLLVL 2 /* dprintf level for file layout logging */ + + +/* pnfs_layout_list */ +struct pnfs_layout_list { + struct list_entry head; + CRITICAL_SECTION lock; +}; + +#define state_entry(pos) list_container(pos, pnfs_layout_state, entry) +#define layout_entry(pos) list_container(pos, pnfs_layout, entry) +#define file_layout_entry(pos) list_container(pos, pnfs_file_layout, layout.entry) + +static enum pnfs_status layout_state_create( + IN const nfs41_fh *meta_fh, + OUT pnfs_layout_state **layout_out) +{ + pnfs_layout_state *layout; + enum pnfs_status status = PNFS_SUCCESS; + + layout = calloc(1, sizeof(pnfs_layout_state)); + if (layout == NULL) { + status = PNFSERR_RESOURCES; + goto out; + } + + fh_copy(&layout->meta_fh, meta_fh); + list_init(&layout->layouts); + list_init(&layout->recalls); + InitializeSRWLock(&layout->lock); + InitializeConditionVariable(&layout->cond); + + *layout_out = layout; +out: + return status; +} + +static void file_layout_free( + IN pnfs_file_layout *layout) +{ + if (layout->device) pnfs_file_device_put(layout->device); + free(layout->filehandles.arr); + free(layout); +} + +static void layout_state_free_layouts( + IN pnfs_layout_state *state) +{ + struct list_entry *entry, *tmp; + list_for_each_tmp(entry, tmp, &state->layouts) + file_layout_free(file_layout_entry(entry)); + list_init(&state->layouts); +} + +static void layout_state_free_recalls( + IN pnfs_layout_state *state) +{ + struct list_entry *entry, *tmp; + list_for_each_tmp(entry, tmp, &state->recalls) + free(layout_entry(entry)); + list_init(&state->recalls); +} + +static void layout_state_free( + IN pnfs_layout_state *state) +{ + layout_state_free_layouts(state); + layout_state_free_recalls(state); + free(state); +} + +static int layout_entry_compare( + IN const struct list_entry *entry, + IN const void *value) +{ + const pnfs_layout_state *layout = state_entry(entry); + const nfs41_fh *meta_fh = (const nfs41_fh*)value; + const nfs41_fh *layout_fh = (const nfs41_fh*)&layout->meta_fh; + const uint32_t diff = layout_fh->len - meta_fh->len; + return diff ? diff : memcmp(layout_fh->fh, meta_fh->fh, meta_fh->len); +} + +static enum pnfs_status layout_entry_find( + IN struct pnfs_layout_list *layouts, + IN const nfs41_fh *meta_fh, + OUT struct list_entry **entry_out) +{ + *entry_out = list_search(&layouts->head, meta_fh, layout_entry_compare); + return *entry_out ? PNFS_SUCCESS : PNFSERR_NO_LAYOUT; +} + +enum pnfs_status pnfs_layout_list_create( + OUT struct pnfs_layout_list **layouts_out) +{ + struct pnfs_layout_list *layouts; + enum pnfs_status status = PNFS_SUCCESS; + + layouts = calloc(1, sizeof(struct pnfs_layout_list)); + if (layouts == NULL) { + status = PNFSERR_RESOURCES; + goto out; + } + list_init(&layouts->head); + InitializeCriticalSection(&layouts->lock); + *layouts_out = layouts; +out: + return status; +} + +void pnfs_layout_list_free( + IN struct pnfs_layout_list *layouts) +{ + struct list_entry *entry, *tmp; + + EnterCriticalSection(&layouts->lock); + + list_for_each_tmp(entry, tmp, &layouts->head) + layout_state_free(state_entry(entry)); + + LeaveCriticalSection(&layouts->lock); + DeleteCriticalSection(&layouts->lock); + free(layouts); +} + +static enum pnfs_status layout_state_find_or_create( + IN struct pnfs_layout_list *layouts, + IN const nfs41_fh *meta_fh, + OUT pnfs_layout_state **layout_out) +{ + struct list_entry *entry; + enum pnfs_status status; + + dprintf(FLLVL, "--> layout_state_find_or_create()\n"); + + EnterCriticalSection(&layouts->lock); + + /* search for an existing layout */ + status = layout_entry_find(layouts, meta_fh, &entry); + if (status) { + /* create a new layout */ + pnfs_layout_state *layout; + status = layout_state_create(meta_fh, &layout); + if (status == PNFS_SUCCESS) { + /* add it to the list */ + list_add_head(&layouts->head, &layout->entry); + *layout_out = layout; + + dprintf(FLLVL, "<-- layout_state_find_or_create() " + "returning new layout %p\n", layout); + } else { + dprintf(FLLVL, "<-- layout_state_find_or_create() " + "returning %s\n", pnfs_error_string(status)); + } + } else { + *layout_out = state_entry(entry); + + dprintf(FLLVL, "<-- layout_state_find_or_create() " + "returning existing layout %p\n", *layout_out); + } + + LeaveCriticalSection(&layouts->lock); + return status; +} + +static enum pnfs_status layout_state_find_and_delete( + IN struct pnfs_layout_list *layouts, + IN const nfs41_fh *meta_fh) +{ + struct list_entry *entry; + enum pnfs_status status; + + dprintf(FLLVL, "--> layout_state_find_and_delete()\n"); + + EnterCriticalSection(&layouts->lock); + + status = layout_entry_find(layouts, meta_fh, &entry); + if (status == PNFS_SUCCESS) { + list_remove(entry); + layout_state_free(state_entry(entry)); + } + + LeaveCriticalSection(&layouts->lock); + + dprintf(FLLVL, "<-- layout_state_find_and_delete() " + "returning %s\n", pnfs_error_string(status)); + return status; +} + + +/* pnfs_file_layout */ +static uint64_t range_max( + IN const pnfs_layout *layout) +{ + uint64_t result = layout->offset + layout->length; + return result < layout->offset ? NFS4_UINT64_MAX : result; +} + +static bool_t layout_sanity_check( + IN pnfs_file_layout *layout) +{ + /* prevent div/0 */ + if (layout->layout.length == 0 || + layout->layout.iomode < PNFS_IOMODE_READ || + layout->layout.iomode > PNFS_IOMODE_RW || + layout_unit_size(layout) == 0) + return FALSE; + + /* put a cap on layout.length to prevent overflow */ + layout->layout.length = range_max(&layout->layout) - layout->layout.offset; + return TRUE; +} + +static int layout_filehandles_cmp( + IN const pnfs_file_layout_handles *lhs, + IN const pnfs_file_layout_handles *rhs) +{ + const uint32_t diff = rhs->count - lhs->count; + return diff ? diff : memcmp(rhs->arr, lhs->arr, + rhs->count * sizeof(nfs41_path_fh)); +} + +static bool_t layout_merge_segments( + IN pnfs_file_layout *to, + IN pnfs_file_layout *from) +{ + const uint64_t to_max = range_max(&to->layout); + const uint64_t from_max = range_max(&from->layout); + + /* cannot merge a segment with itself */ + if (to == from) + return FALSE; + + /* the ranges must meet or overlap */ + if (to_max < from->layout.offset || from_max < to->layout.offset) + return FALSE; + + /* the following fields must match: */ + if (to->layout.iomode != from->layout.iomode || + to->layout.type != from->layout.type || + layout_filehandles_cmp(&to->filehandles, &from->filehandles) != 0 || + memcmp(to->deviceid, from->deviceid, PNFS_DEVICEID_SIZE) != 0 || + to->pattern_offset != from->pattern_offset || + to->first_index != from->first_index || + to->util != from->util) + return FALSE; + + dprintf(FLLVL, "merging layout range {%llu, %llu} with {%llu, %llu}\n", + to->layout.offset, to->layout.length, + from->layout.offset, from->layout.length); + + /* calculate the union of the two ranges */ + to->layout.offset = min(to->layout.offset, from->layout.offset); + to->layout.length = max(to_max, from_max) - to->layout.offset; + return TRUE; +} + +static enum pnfs_status layout_state_merge( + IN pnfs_layout_state *state, + IN pnfs_file_layout *from) +{ + struct list_entry *entry, *tmp; + pnfs_file_layout *to; + enum pnfs_status status = PNFSERR_NO_LAYOUT; + + /* attempt to merge the new segment with each existing segment */ + list_for_each_tmp(entry, tmp, &state->layouts) { + to = file_layout_entry(entry); + if (!layout_merge_segments(to, from)) + continue; + + /* on success, remove/free the new segment */ + list_remove(&from->layout.entry); + file_layout_free(from); + status = PNFS_SUCCESS; + + /* because the existing segment 'to' has grown, we may + * be able to merge it with later segments */ + from = to; + + /* but if there could be io threads referencing this segment, + * we can't free it until io is finished */ + if (state->io_count) + break; + } + return status; +} + +static void layout_ordered_insert( + IN pnfs_layout_state *state, + IN pnfs_layout *layout) +{ + struct list_entry *entry; + list_for_each(entry, &state->layouts) { + pnfs_layout *existing = layout_entry(entry); + + /* maintain an order of increasing offset */ + if (existing->offset < layout->offset) + continue; + + /* when offsets are equal, prefer a longer segment first */ + if (existing->offset == layout->offset && + existing->length > layout->length) + continue; + + list_add(&layout->entry, existing->entry.prev, &existing->entry); + return; + } + + list_add_tail(&state->layouts, &layout->entry); +} + +static enum pnfs_status layout_update_range( + IN OUT pnfs_layout_state *state, + IN const struct list_entry *layouts) +{ + struct list_entry *entry, *tmp; + pnfs_file_layout *layout; + enum pnfs_status status = PNFSERR_NO_LAYOUT; + + list_for_each_tmp(entry, tmp, layouts) { + layout = file_layout_entry(entry); + + /* don't know what to do with non-file layouts */ + if (layout->layout.type != PNFS_LAYOUTTYPE_FILE) + continue; + + if (!layout_sanity_check(layout)) { + file_layout_free(layout); + continue; + } + + /* attempt to merge the range with existing segments */ + status = layout_state_merge(state, layout); + if (status) { + dprintf(FLLVL, "saving new layout:\n"); + dprint_layout(FLLVL, layout); + + layout_ordered_insert(state, &layout->layout); + status = PNFS_SUCCESS; + } + } + return status; +} + +static enum pnfs_status layout_update_stateid( + IN OUT pnfs_layout_state *state, + IN const stateid4 *stateid) +{ + enum pnfs_status status = PNFS_SUCCESS; + + if (state->stateid.seqid == 0) { + /* save a new layout stateid */ + memcpy(&state->stateid, stateid, sizeof(stateid4)); + } else if (memcmp(&state->stateid.other, stateid->other, + NFS4_STATEID_OTHER) == 0) { + /* update an existing layout stateid */ + state->stateid.seqid = stateid->seqid; + } else { + status = PNFSERR_NO_LAYOUT; + } + return status; +} + +static enum pnfs_status layout_update( + IN OUT pnfs_layout_state *state, + IN const pnfs_layoutget_res_ok *layoutget_res) +{ + enum pnfs_status status; + + /* update the layout ranges held by the client */ + status = layout_update_range(state, &layoutget_res->layouts); + if (status) { + eprintf("LAYOUTGET didn't return any file layouts\n"); + goto out; + } + /* update the layout stateid */ + status = layout_update_stateid(state, &layoutget_res->stateid); + if (status) { + eprintf("LAYOUTGET returned a new stateid when we already had one\n"); + goto out; + } + /* if a previous LAYOUTGET set return_on_close, don't overwrite it */ + if (!state->return_on_close) + state->return_on_close = layoutget_res->return_on_close; +out: + return status; +} + +static enum pnfs_status file_layout_fetch( + IN OUT pnfs_layout_state *state, + IN nfs41_session *session, + IN nfs41_path_fh *meta_file, + IN stateid_arg *stateid, + IN enum pnfs_iomode iomode, + IN uint64_t offset, + IN uint64_t minlength, + IN uint64_t length) +{ + pnfs_layoutget_res_ok layoutget_res = { 0 }; + enum pnfs_status pnfsstat = PNFS_SUCCESS; + enum nfsstat4 nfsstat; + + dprintf(FLLVL, "--> file_layout_fetch(%s, seqid=%u)\n", + pnfs_iomode_string(iomode), state->stateid.seqid); + + list_init(&layoutget_res.layouts); + + /* drop the lock during the rpc call */ + ReleaseSRWLockExclusive(&state->lock); + nfsstat = pnfs_rpc_layoutget(session, meta_file, stateid, + iomode, offset, minlength, length, &layoutget_res); + AcquireSRWLockExclusive(&state->lock); + + if (nfsstat) { + dprintf(FLLVL, "pnfs_rpc_layoutget() failed with %s\n", + nfs_error_string(nfsstat)); + pnfsstat = PNFSERR_NOT_SUPPORTED; + } + + switch (nfsstat) { + case NFS4_OK: + /* use the LAYOUTGET results to update our view of the layout */ + pnfsstat = layout_update(state, &layoutget_res); + break; + + case NFS4ERR_BADIOMODE: + /* don't try RW again */ + if (iomode == PNFS_IOMODE_RW) + state->status |= PNFS_LAYOUT_NOT_RW; + break; + + case NFS4ERR_LAYOUTUNAVAILABLE: + case NFS4ERR_UNKNOWN_LAYOUTTYPE: + case NFS4ERR_BADLAYOUT: + /* don't try again at all */ + state->status |= PNFS_LAYOUT_UNAVAILABLE; + break; + } + + dprintf(FLLVL, "<-- file_layout_fetch() returning %s\n", + pnfs_error_string(pnfsstat)); + return pnfsstat; +} + +/* returns PNFS_SUCCESS if the client holds valid layouts that cover + * the entire range requested. otherwise, returns PNFS_PENDING and + * sets 'offset_missing' to the lowest offset that is not covered */ +static enum pnfs_status layout_coverage_status( + IN pnfs_layout_state *state, + IN enum pnfs_iomode iomode, + IN uint64_t offset, + IN uint64_t length, + OUT uint64_t *offset_missing) +{ + uint64_t position = offset; + struct list_entry *entry; + + list_for_each(entry, &state->layouts) { + /* if the current position intersects with a compatible + * layout, move the position to the end of that layout */ + pnfs_layout *layout = layout_entry(entry); + if (layout->iomode >= iomode && + layout->offset <= position && + position < layout->offset + layout->length) + position = layout->offset + layout->length; + } + + if (position >= offset + length) + return PNFS_SUCCESS; + + *offset_missing = position; + return PNFS_PENDING; +} + +static enum pnfs_status layout_fetch( + IN pnfs_layout_state *state, + IN nfs41_session *session, + IN nfs41_path_fh *meta_file, + IN stateid_arg *stateid, + IN enum pnfs_iomode iomode, + IN uint64_t offset, + IN uint64_t length) +{ + stateid_arg layout_stateid = { 0 }; + enum pnfs_status status = PNFS_PENDING; + + /* check for previous errors from LAYOUTGET */ + if ((state->status & PNFS_LAYOUT_UNAVAILABLE) || + ((state->status & PNFS_LAYOUT_NOT_RW) && iomode == PNFS_IOMODE_RW)) { + status = PNFSERR_NO_LAYOUT; + goto out; + } + + /* wait for any pending LAYOUTGETs/LAYOUTRETURNs */ + while (state->pending) + SleepConditionVariableSRW(&state->cond, &state->lock, INFINITE, 0); + state->pending = TRUE; + + /* if there's an existing layout stateid, use it */ + if (state->stateid.seqid) { + memcpy(&layout_stateid.stateid, &state->stateid, sizeof(stateid4)); + layout_stateid.type = STATEID_LAYOUT; + stateid = &layout_stateid; + } + + if ((state->status & PNFS_LAYOUT_NOT_RW) == 0) { + /* try to get a RW layout first */ + status = file_layout_fetch(state, session, meta_file, + stateid, PNFS_IOMODE_RW, offset, length, NFS4_UINT64_MAX); + } + + if (status && iomode == PNFS_IOMODE_READ) { + /* fall back on READ if necessary */ + status = file_layout_fetch(state, session, meta_file, + stateid, iomode, offset, length, NFS4_UINT64_MAX); + } + + state->pending = FALSE; + WakeConditionVariable(&state->cond); +out: + return status; +} + +static enum pnfs_status device_status( + IN pnfs_layout_state *state, + IN uint64_t offset, + IN uint64_t length, + OUT unsigned char *deviceid) +{ + struct list_entry *entry; + enum pnfs_status status = PNFS_SUCCESS; + + list_for_each(entry, &state->layouts) { + pnfs_file_layout *layout = file_layout_entry(entry); + + if (layout->device == NULL) { + /* copy missing deviceid */ + memcpy(deviceid, layout->deviceid, PNFS_DEVICEID_SIZE); + status = PNFS_PENDING; + break; + } + } + return status; +} + +static void device_assign( + IN pnfs_layout_state *state, + IN const unsigned char *deviceid, + IN pnfs_file_device *device) +{ + struct list_entry *entry; + list_for_each(entry, &state->layouts) { + pnfs_file_layout *layout = file_layout_entry(entry); + + /* assign the device to any matching layouts */ + if (layout->device == NULL && + memcmp(layout->deviceid, deviceid, PNFS_DEVICEID_SIZE) == 0) { + layout->device = device; + + /* XXX: only assign the device to a single segment, because + * pnfs_file_device_get() only gives us a single reference */ + break; + } + } +} + +static enum pnfs_status device_fetch( + IN pnfs_layout_state *state, + IN nfs41_session *session, + IN unsigned char *deviceid) +{ + pnfs_file_device *device; + enum pnfs_status status; + + /* drop the layoutstate lock for the rpc call */ + ReleaseSRWLockExclusive(&state->lock); + status = pnfs_file_device_get(session, + session->client->devices, deviceid, &device); + AcquireSRWLockExclusive(&state->lock); + + if (status == PNFS_SUCCESS) + device_assign(state, deviceid, device); + return status; +} + + +/* nfs41_open_state */ +static enum pnfs_status client_supports_pnfs( + IN nfs41_client *client) +{ + enum pnfs_status status; + AcquireSRWLockShared(&client->exid_lock); + status = client->roles & EXCHGID4_FLAG_USE_PNFS_MDS + ? PNFS_SUCCESS : PNFSERR_NOT_SUPPORTED; + ReleaseSRWLockShared(&client->exid_lock); + return status; +} + +static enum pnfs_status fs_supports_layout( + IN const nfs41_superblock *superblock, + IN enum pnfs_layout_type type) +{ + const uint32_t flag = 1 << (type - 1); + return (superblock->layout_types & flag) == 0 + ? PNFSERR_NOT_SUPPORTED : PNFS_SUCCESS; +} + +static enum pnfs_status open_state_layout_cached( + IN nfs41_open_state *state, + OUT pnfs_layout_state **layout_out) +{ + enum pnfs_status status = PNFSERR_NO_LAYOUT; + + if (state->layout) { + status = PNFS_SUCCESS; + *layout_out = state->layout; + + dprintf(FLLVL, "pnfs_open_state_layout() found " + "cached layout %p\n", *layout_out); + } + return status; +} + +enum pnfs_status pnfs_layout_state_open( + IN nfs41_open_state *state, + OUT pnfs_layout_state **layout_out) +{ + struct pnfs_layout_list *layouts = state->session->client->layouts; + nfs41_session *session = state->session; + pnfs_layout_state *layout; + enum pnfs_status status; + + dprintf(FLLVL, "--> pnfs_layout_state_open()\n"); + + status = client_supports_pnfs(session->client); + if (status) + goto out; + status = fs_supports_layout(state->file.fh.superblock, PNFS_LAYOUTTYPE_FILE); + if (status) + goto out; + + /* under shared lock, check open state for cached layouts */ + AcquireSRWLockShared(&state->lock); + status = open_state_layout_cached(state, &layout); + ReleaseSRWLockShared(&state->lock); + + if (status) { + /* under exclusive lock, find or create a layout for this file */ + AcquireSRWLockExclusive(&state->lock); + + status = open_state_layout_cached(state, &layout); + if (status) { + status = layout_state_find_or_create(layouts, &state->file.fh, &layout); + if (status == PNFS_SUCCESS) { + LONG open_count = InterlockedIncrement(&layout->open_count); + state->layout = layout; + + dprintf(FLLVL, "pnfs_layout_state_open() caching layout %p " + "(%u opens)\n", state->layout, open_count); + } + } + + ReleaseSRWLockExclusive(&state->lock); + + if (status) + goto out; + } + + *layout_out = layout; +out: + dprintf(FLLVL, "<-- pnfs_layout_state_open() returning %s\n", + pnfs_error_string(status)); + return status; +} + +/* expects caller to hold an exclusive lock on pnfs_layout_state */ +enum pnfs_status pnfs_layout_state_prepare( + IN pnfs_layout_state *state, + IN nfs41_session *session, + IN nfs41_path_fh *meta_file, + IN stateid_arg *stateid, + IN enum pnfs_iomode iomode, + IN uint64_t offset, + IN uint64_t length) +{ + unsigned char deviceid[PNFS_DEVICEID_SIZE]; + struct list_entry *entry; + uint64_t missing; + enum pnfs_status status; + + /* fail if the range intersects any pending recalls */ + list_for_each(entry, &state->recalls) { + const pnfs_layout *recall = layout_entry(entry); + if (offset <= recall->offset + recall->length + && recall->offset <= offset + length) { + status = PNFSERR_LAYOUT_RECALLED; + goto out; + } + } + + /* if part of the given range is not covered by a layout, + * attempt to fetch it with LAYOUTGET */ + status = layout_coverage_status(state, iomode, offset, length, &missing); + if (status == PNFS_PENDING) { + status = layout_fetch(state, session, meta_file, stateid, + iomode, missing, offset + length - missing); + + /* return pending because layout_fetch() dropped the lock */ + if (status == PNFS_SUCCESS) + status = PNFS_PENDING; + goto out; + } + + /* if any layouts in the range are missing device info, + * fetch them with GETDEVICEINFO */ + status = device_status(state, offset, length, deviceid); + if (status == PNFS_PENDING) { + status = device_fetch(state, session, deviceid); + + /* return pending because device_fetch() dropped the lock */ + if (status == PNFS_SUCCESS) + status = PNFS_PENDING; + goto out; + } +out: + return status; +} + +static enum pnfs_status layout_return_status( + IN const pnfs_layout_state *state) +{ + /* return the layout if we have a stateid */ + return state->stateid.seqid ? PNFS_SUCCESS : PNFS_PENDING; +} + +static enum pnfs_status file_layout_return( + IN nfs41_session *session, + IN nfs41_path_fh *file, + IN pnfs_layout_state *state) +{ + enum pnfs_status status; + enum nfsstat4 nfsstat; + + dprintf(FLLVL, "--> file_layout_return()\n"); + + /* under shared lock, determine whether we need to return the layout */ + AcquireSRWLockShared(&state->lock); + status = layout_return_status(state); + ReleaseSRWLockShared(&state->lock); + + if (status != PNFS_PENDING) + goto out; + + /* under exclusive lock, return the layout and reset status flags */ + AcquireSRWLockExclusive(&state->lock); + + /* wait for any pending LAYOUTGETs/LAYOUTRETURNs */ + while (state->pending) + SleepConditionVariableSRW(&state->cond, &state->lock, INFINITE, 0); + state->pending = TRUE; + + status = layout_return_status(state); + if (status == PNFS_PENDING) { + pnfs_layoutreturn_res layoutreturn_res = { 0 }; + stateid4 stateid; + memcpy(&stateid, &state->stateid, sizeof(stateid)); + + /* drop the lock during the rpc call */ + ReleaseSRWLockExclusive(&state->lock); + nfsstat = pnfs_rpc_layoutreturn(session, file, PNFS_LAYOUTTYPE_FILE, + PNFS_IOMODE_ANY, 0, NFS4_UINT64_MAX, &stateid, &layoutreturn_res); + AcquireSRWLockExclusive(&state->lock); + + if (nfsstat) { + eprintf("pnfs_rpc_layoutreturn() failed with %s\n", + nfs_error_string(nfsstat)); + status = PNFSERR_NO_LAYOUT; + } else { + status = PNFS_SUCCESS; + + /* update the layout range held by the client */ + layout_state_free_layouts(state); + + /* 12.5.3. Layout Stateid: Once a client has no more + * layouts on a file, the layout stateid is no longer + * valid and MUST NOT be used. */ + ZeroMemory(&state->stateid, sizeof(stateid4)); + } + } + + state->pending = FALSE; + WakeConditionVariable(&state->cond); + ReleaseSRWLockExclusive(&state->lock); + +out: + dprintf(FLLVL, "<-- file_layout_return() returning %s\n", + pnfs_error_string(status)); + return status; +} + +void pnfs_layout_state_close( + IN nfs41_session *session, + IN nfs41_open_state *state, + IN bool_t remove) +{ + pnfs_layout_state *layout; + bool_t return_layout; + enum pnfs_status status; + + AcquireSRWLockExclusive(&state->lock); + layout = state->layout; + state->layout = NULL; + ReleaseSRWLockExclusive(&state->lock); + + if (layout) { + LONG open_count = InterlockedDecrement(&layout->open_count); + + AcquireSRWLockShared(&layout->lock); + /* only return on close if it's the last close */ + return_layout = layout->return_on_close && (open_count <= 0); + ReleaseSRWLockShared(&layout->lock); + + if (return_layout) { + status = file_layout_return(session, &state->file, layout); + if (status) + eprintf("file_layout_return() failed with %s\n", + pnfs_error_string(status)); + } + } + + if (remove && session->client->layouts) { + /* free the layout when the file is removed */ + layout_state_find_and_delete(session->client->layouts, &state->file.fh); + } +} + + +/* pnfs_layout_recall */ +struct layout_recall { + pnfs_layout layout; + bool_t changed; +}; +#define recall_entry(pos) list_container(pos, struct layout_recall, layout.entry) + +static bool_t layout_recall_compatible( + IN const pnfs_layout *layout, + IN const pnfs_layout *recall) +{ + return layout->type == recall->type + && layout->offset <= (recall->offset + recall->length) + && recall->offset <= (layout->offset + layout->length) + && (recall->iomode == PNFS_IOMODE_ANY || + layout->iomode == recall->iomode); +} + +static pnfs_file_layout* layout_allocate_copy( + IN const pnfs_file_layout *existing) +{ + /* allocate a segment to cover the end of the range */ + pnfs_file_layout *layout = calloc(1, sizeof(pnfs_file_layout)); + if (layout == NULL) + goto out; + + memcpy(layout, existing, sizeof(pnfs_file_layout)); + + /* XXX: don't use the device from existing layout; + * we need to get a reference for ourselves */ + layout->device = NULL; + + /* allocate a copy of the filehandle array */ + layout->filehandles.arr = calloc(layout->filehandles.count, + sizeof(nfs41_path_fh)); + if (layout->filehandles.arr == NULL) + goto out_free; + + memcpy(layout->filehandles.arr, existing->filehandles.arr, + layout->filehandles.count * sizeof(nfs41_path_fh)); +out: + return layout; + +out_free: + file_layout_free(layout); + layout = NULL; + goto out; +} + +static void layout_recall_range( + IN pnfs_layout_state *state, + IN const pnfs_layout *recall) +{ + struct list_entry *entry, *tmp; + list_for_each_tmp(entry, tmp, &state->layouts) { + pnfs_file_layout *layout = file_layout_entry(entry); + const uint64_t layout_end = layout->layout.offset + layout->layout.length; + + if (!layout_recall_compatible(&layout->layout, recall)) + continue; + + if (recall->offset > layout->layout.offset) { + /* segment starts before recall; shrink length */ + layout->layout.length = recall->offset - layout->layout.offset; + + if (layout_end > recall->offset + recall->length) { + /* middle chunk of the segment is recalled; + * allocate a new segment to cover the end */ + pnfs_file_layout *remainder = layout_allocate_copy(layout); + if (remainder == NULL) { + /* silently ignore allocation errors here. behave + * as if we 'forgot' this last segment */ + } else { + layout->layout.offset = recall->offset + recall->length; + layout->layout.length = layout_end - layout->layout.offset; + layout_ordered_insert(state, &remainder->layout); + } + } + } else { + /* segment starts after recall */ + if (layout_end <= recall->offset + recall->length) { + /* entire segment is recalled */ + list_remove(&layout->layout.entry); + file_layout_free(layout); + } else { + /* beginning of segment is recalled; shrink offset/length */ + layout->layout.offset = recall->offset + recall->length; + layout->layout.length = layout_end - layout->layout.offset; + } + } + } +} + +static void layout_state_deferred_recalls( + IN pnfs_layout_state *state) +{ + struct list_entry *entry, *tmp; + list_for_each_tmp(entry, tmp, &state->recalls) { + /* process each deferred layout recall */ + pnfs_layout *recall = layout_entry(entry); + layout_recall_range(state, recall); + + /* remove/free the recall entry */ + list_remove(&recall->entry); + free(recall); + } +} + +static void layout_recall_entry_init( + OUT struct layout_recall *lrc, + IN const struct cb_layoutrecall_args *recall) +{ + list_init(&lrc->layout.entry); + if (recall->recall.type == PNFS_RETURN_FILE) { + lrc->layout.offset = recall->recall.args.file.offset; + lrc->layout.length = recall->recall.args.file.length; + } else { + lrc->layout.offset = 0; + lrc->layout.length = NFS4_UINT64_MAX; + } + lrc->layout.iomode = recall->iomode; + lrc->layout.type = PNFS_LAYOUTTYPE_FILE; + lrc->changed = recall->changed; +} + +static enum pnfs_status layout_recall_merge( + IN struct list_entry *list, + IN pnfs_layout *from) +{ + struct list_entry *entry, *tmp; + enum pnfs_status status = PNFSERR_NO_LAYOUT; + + /* attempt to merge the new recall with each existing recall */ + list_for_each_tmp(entry, tmp, list) { + pnfs_layout *to = layout_entry(entry); + const uint64_t to_max = to->offset + to->length; + const uint64_t from_max = from->offset + from->length; + + /* the ranges must meet or overlap */ + if (to_max < from->offset || from_max < to->offset) + continue; + + /* the following fields must match: */ + if (to->iomode != from->iomode || to->type != from->type) + continue; + + dprintf(FLLVL, "merging recalled range {%llu, %llu} with {%llu, %llu}\n", + to->offset, to->length, from->offset, from->length); + + /* calculate the union of the two ranges */ + to->offset = min(to->offset, from->offset); + to->length = max(to_max, from_max) - to->offset; + + /* on success, remove/free the new segment */ + list_remove(&from->entry); + free(from); + status = PNFS_SUCCESS; + + /* because the existing segment 'to' has grown, we may + * be able to merge it with later segments */ + from = to; + } + return status; +} + +static enum pnfs_status file_layout_recall( + IN pnfs_layout_state *state, + IN const struct cb_layoutrecall_args *recall) +{ + const stateid4 *stateid = &recall->recall.args.file.stateid; + enum pnfs_status status = PNFS_SUCCESS; + + /* under an exclusive lock, flag the layout as recalled */ + AcquireSRWLockExclusive(&state->lock); + + if (state->stateid.seqid == 0) { + /* return NOMATCHINGLAYOUT if it wasn't actually granted */ + status = PNFSERR_NO_LAYOUT; + goto out; + } + + if (recall->recall.type == PNFS_RETURN_FILE) { + /* detect races between CB_LAYOUTRECALL and LAYOUTGET/LAYOUTRETURN */ + if (stateid->seqid > state->stateid.seqid + 1) { + /* the server has processed an outstanding LAYOUTGET or + * LAYOUTRETURN; we must return ERR_DELAY until we get the + * response and update our view of the layout */ + status = PNFS_PENDING; + goto out; + } + + /* save the updated seqid */ + state->stateid.seqid = stateid->seqid; + } + + if (state->io_count) { + /* save an entry for this recall, and process it once io finishes */ + struct layout_recall *lrc = calloc(1, sizeof(struct layout_recall)); + if (lrc == NULL) { + /* on failure to allocate, we'll have to respond + * to the CB_LAYOUTRECALL with NFS4ERR_DELAY */ + status = PNFS_PENDING; + goto out; + } + layout_recall_entry_init(lrc, recall); + if (layout_recall_merge(&state->recalls, &lrc->layout) != PNFS_SUCCESS) + list_add_tail(&state->recalls, &lrc->layout.entry); + } else { + /* if there is no pending io, process the recall immediately */ + struct layout_recall lrc = { 0 }; + layout_recall_entry_init(&lrc, recall); + layout_recall_range(state, &lrc.layout); + } +out: + ReleaseSRWLockExclusive(&state->lock); + return status; +} + +static enum pnfs_status file_layout_recall_file( + IN nfs41_client *client, + IN const struct cb_layoutrecall_args *recall) +{ + struct list_entry *entry; + enum pnfs_status status; + + dprintf(FLLVL, "--> file_layout_recall_file()\n"); + + EnterCriticalSection(&client->layouts->lock); + + status = layout_entry_find(client->layouts, &recall->recall.args.file.fh, &entry); + if (status == PNFS_SUCCESS) + status = file_layout_recall(state_entry(entry), recall); + + LeaveCriticalSection(&client->layouts->lock); + + dprintf(FLLVL, "<-- file_layout_recall_file() returning %s\n", + pnfs_error_string(status)); + return status; +} + +static bool_t fsid_matches( + IN const nfs41_fsid *lhs, + IN const nfs41_fsid *rhs) +{ + return lhs->major == rhs->major && lhs->minor == rhs->minor; +} + +static enum pnfs_status file_layout_recall_fsid( + IN nfs41_client *client, + IN const struct cb_layoutrecall_args *recall) +{ + struct list_entry *entry; + pnfs_layout_state *state; + nfs41_fh *fh; + enum pnfs_status status = PNFSERR_NO_LAYOUT; + + dprintf(FLLVL, "--> file_layout_recall_fsid(%llu, %llu)\n", + recall->recall.args.fsid.major, recall->recall.args.fsid.minor); + + EnterCriticalSection(&client->layouts->lock); + + list_for_each(entry, &client->layouts->head) { + state = state_entry(entry); + /* no locks needed to read layout.meta_fh or superblock.fsid, + * because they are only written once on creation */ + fh = &state->meta_fh; + if (fsid_matches(&recall->recall.args.fsid, &fh->superblock->fsid)) + status = file_layout_recall(state, recall); + } + + LeaveCriticalSection(&client->layouts->lock); + + /* bulk recalls require invalidation of cached device info */ + pnfs_file_device_list_invalidate(client->devices); + + dprintf(FLLVL, "<-- file_layout_recall_fsid() returning %s\n", + pnfs_error_string(status)); + return status; +} + +static enum pnfs_status file_layout_recall_all( + IN nfs41_client *client, + IN const struct cb_layoutrecall_args *recall) +{ + struct list_entry *entry; + enum pnfs_status status = PNFSERR_NO_LAYOUT; + + dprintf(FLLVL, "--> file_layout_recall_all()\n"); + + EnterCriticalSection(&client->layouts->lock); + + list_for_each(entry, &client->layouts->head) + status = file_layout_recall(state_entry(entry), recall); + + LeaveCriticalSection(&client->layouts->lock); + + /* bulk recalls require invalidation of cached device info */ + pnfs_file_device_list_invalidate(client->devices); + + dprintf(FLLVL, "<-- file_layout_recall_all() returning %s\n", + pnfs_error_string(status)); + return status; +} + +enum pnfs_status pnfs_file_layout_recall( + IN nfs41_client *client, + IN const struct cb_layoutrecall_args *recall) +{ + enum pnfs_status status = PNFS_SUCCESS; + + dprintf(FLLVL, "--> pnfs_file_layout_recall(%u, %s, %u)\n", + recall->recall.type, pnfs_iomode_string(recall->iomode), + recall->changed); + + if (recall->type != PNFS_LAYOUTTYPE_FILE) { + dprintf(FLLVL, "invalid layout type %u (%s)!\n", + recall->type, pnfs_layout_type_string(recall->type)); + status = PNFSERR_NOT_SUPPORTED; + goto out; + } + + switch (recall->recall.type) { + case PNFS_RETURN_FILE: + status = file_layout_recall_file(client, recall); + break; + case PNFS_RETURN_FSID: + status = file_layout_recall_fsid(client, recall); + break; + case PNFS_RETURN_ALL: + status = file_layout_recall_all(client, recall); + break; + + default: + dprintf(FLLVL, "invalid return type %u!\n", recall->recall); + status = PNFSERR_NOT_SUPPORTED; + goto out; + } +out: + dprintf(FLLVL, "<-- pnfs_file_layout_recall() returning %s\n", + pnfs_error_string(status)); + return status; +} + +/* expects caller to hold a shared lock on pnfs_layout_state */ +enum pnfs_status pnfs_layout_recall_status( + IN const pnfs_layout_state *state, + IN const pnfs_layout *layout) +{ + struct list_entry *entry; + enum pnfs_status status = PNFS_SUCCESS; + + /* search for a pending recall that intersects with the given segment */ + list_for_each(entry, &state->recalls) { + const struct layout_recall *recall = recall_entry(entry); + if (!layout_recall_compatible(layout, &recall->layout)) + continue; + + if (recall->changed) + status = PNFSERR_LAYOUT_CHANGED; + else + status = PNFSERR_LAYOUT_RECALLED; + break; + } + return status; +} + +void pnfs_layout_recall_fenced( + IN pnfs_layout_state *state, + IN const pnfs_layout *layout) +{ + struct layout_recall *lrc = calloc(1, sizeof(struct layout_recall)); + if (lrc == NULL) + return; + + AcquireSRWLockExclusive(&state->lock); + + list_init(&lrc->layout.entry); + lrc->layout.offset = layout->offset; + lrc->layout.length = layout->length; + lrc->layout.iomode = layout->iomode; + lrc->layout.type = layout->type; + lrc->changed = TRUE; + + if (layout_recall_merge(&state->recalls, &lrc->layout) != PNFS_SUCCESS) + list_add_tail(&state->recalls, &lrc->layout.entry); + + ReleaseSRWLockExclusive(&state->lock); +} + +/* expects caller to hold an exclusive lock on pnfs_layout_state */ +void pnfs_layout_io_start( + IN pnfs_layout_state *state) +{ + /* take a reference on the layout, so that it won't be recalled + * until all io is finished */ + state->io_count++; + dprintf(FLLVL, "pnfs_layout_io_start(): count -> %u\n", + state->io_count); +} + +void pnfs_layout_io_finished( + IN pnfs_layout_state *state) +{ + AcquireSRWLockExclusive(&state->lock); + + /* return the reference to signify that an io request is finished */ + state->io_count--; + dprintf(FLLVL, "pnfs_layout_io_finished() count -> %u\n", + state->io_count); + + if (state->io_count > 0) /* more io pending */ + goto out_unlock; + + /* once all io is finished, process any layout recalls */ + layout_state_deferred_recalls(state); + + /* finish any segment merging that was delayed during io */ + if (!list_empty(&state->layouts)) + layout_state_merge(state, file_layout_entry(state->layouts.next)); + +out_unlock: + ReleaseSRWLockExclusive(&state->lock); +} diff --git a/reactos/base/services/nfsd/readdir.c b/reactos/base/services/nfsd/readdir.c new file mode 100644 index 00000000000..eaa9301e732 --- /dev/null +++ b/reactos/base/services/nfsd/readdir.c @@ -0,0 +1,649 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include +#include +#include +#include "from_kernel.h" +#include "nfs41_ops.h" +#include "daemon_debug.h" +#include "upcall.h" +#include "util.h" + + +typedef union _FILE_DIR_INFO_UNION { + ULONG NextEntryOffset; + FILE_NAMES_INFORMATION fni; + FILE_DIRECTORY_INFO fdi; + FILE_FULL_DIR_INFO ffdi; + FILE_ID_FULL_DIR_INFO fifdi; + FILE_BOTH_DIR_INFORMATION fbdi; + FILE_ID_BOTH_DIR_INFO fibdi; +} FILE_DIR_INFO_UNION, *PFILE_DIR_INFO_UNION; + + +/* NFS41_DIR_QUERY */ +static int parse_readdir(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall) +{ + int status; + readdir_upcall_args *args = &upcall->args.readdir; + + status = safe_read(&buffer, &length, &args->query_class, sizeof(args->query_class)); + if (status) goto out; + status = safe_read(&buffer, &length, &args->buf_len, sizeof(args->buf_len)); + if (status) goto out; + status = get_name(&buffer, &length, &args->filter); + if (status) goto out; + status = safe_read(&buffer, &length, &args->initial, sizeof(args->initial)); + if (status) goto out; + status = safe_read(&buffer, &length, &args->restart, sizeof(args->restart)); + if (status) goto out; + status = safe_read(&buffer, &length, &args->single, sizeof(args->single)); + if (status) goto out; + status = safe_read(&buffer, &length, &args->kbuf, sizeof(args->kbuf)); + if (status) goto out; + args->root = upcall->root_ref; + args->state = upcall->state_ref; + + dprintf(1, "parsing NFS41_DIR_QUERY: info_class=%d buf_len=%d " + "filter='%s'\n\tInitial\\Restart\\Single %d\\%d\\%d buf=%p\n", + args->query_class, args->buf_len, args->filter, + args->initial, args->restart, args->single, args->kbuf); +out: + return status; +} + +#define FILTER_STAR '*' +#define FILTER_QM '>' + +static __inline const char* skip_stars( + const char *filter) +{ + while (*filter == FILTER_STAR) + filter++; + return filter; +} + +static int readdir_filter( + const char *filter, + const char *name) +{ + const char *f = filter, *n = name; + + while (*f && *n) { + if (*f == FILTER_STAR) { + f = skip_stars(f); + if (*f == '\0') + return 1; + while (*n && !readdir_filter(f, n)) + n++; + } else if (*f == FILTER_QM || *f == *n) { + f++; + n++; + } else + return 0; + } + return *f == *n || *skip_stars(f) == '\0'; +} + +static uint32_t readdir_size_for_entry( + IN int query_class, + IN uint32_t wname_size) +{ + uint32_t needed = wname_size; + switch (query_class) + { + case FileDirectoryInformation: + needed += FIELD_OFFSET(FILE_DIRECTORY_INFO, FileName); + break; + case FileIdFullDirectoryInformation: + needed += FIELD_OFFSET(FILE_ID_FULL_DIR_INFO, FileName); + break; + case FileFullDirectoryInformation: + needed += FIELD_OFFSET(FILE_FULL_DIR_INFO, FileName); + break; + case FileIdBothDirectoryInformation: + needed += FIELD_OFFSET(FILE_ID_BOTH_DIR_INFO, FileName); + break; + case FileBothDirectoryInformation: + needed += FIELD_OFFSET(FILE_BOTH_DIR_INFORMATION, FileName); + break; + case FileNamesInformation: + needed += FIELD_OFFSET(FILE_NAMES_INFORMATION, FileName); + break; + default: + eprintf("unhandled dir query class %d\n", query_class); + return 0; + } + return needed; +} + +static void readdir_copy_dir_info( + IN nfs41_readdir_entry *entry, + IN PFILE_DIR_INFO_UNION info) +{ + info->fdi.FileIndex = (ULONG)entry->attr_info.fileid; + nfs_time_to_file_time(&entry->attr_info.time_create, + &info->fdi.CreationTime); + nfs_time_to_file_time(&entry->attr_info.time_access, + &info->fdi.LastAccessTime); + nfs_time_to_file_time(&entry->attr_info.time_modify, + &info->fdi.LastWriteTime); + /* XXX: was using 'change' attr, but that wasn't giving a time */ + nfs_time_to_file_time(&entry->attr_info.time_modify, + &info->fdi.ChangeTime); + info->fdi.EndOfFile.QuadPart = + info->fdi.AllocationSize.QuadPart = + entry->attr_info.size; + info->fdi.FileAttributes = nfs_file_info_to_attributes( + &entry->attr_info); +} + +static void readdir_copy_shortname( + IN LPCWSTR name, + OUT LPWSTR name_out, + OUT CCHAR *name_size_out) +{ + /* GetShortPathName returns number of characters, not including \0 */ + *name_size_out = (CCHAR)GetShortPathNameW(name, name_out, 12); + if (*name_size_out) { + *name_size_out++; + *name_size_out *= sizeof(WCHAR); + } +} + +static void readdir_copy_full_dir_info( + IN nfs41_readdir_entry *entry, + IN PFILE_DIR_INFO_UNION info) +{ + readdir_copy_dir_info(entry, info); + /* for files with the FILE_ATTRIBUTE_REPARSE_POINT attribute, + * EaSize is used instead to specify its reparse tag. this makes + * the 'dir' command to show files as , and triggers a + * FSCTL_GET_REPARSE_POINT to query the symlink target + */ + info->fifdi.EaSize = entry->attr_info.type == NF4LNK ? + IO_REPARSE_TAG_SYMLINK : 0; +} + +static void readdir_copy_both_dir_info( + IN nfs41_readdir_entry *entry, + IN LPWSTR wname, + IN PFILE_DIR_INFO_UNION info) +{ + readdir_copy_full_dir_info(entry, info); + readdir_copy_shortname(wname, info->fbdi.ShortName, + &info->fbdi.ShortNameLength); +} + +static void readdir_copy_filename( + IN LPCWSTR name, + IN uint32_t name_size, + OUT LPWSTR name_out, + OUT ULONG *name_size_out) +{ + *name_size_out = name_size; + memcpy(name_out, name, name_size); +} + +static int format_abs_path( + IN const nfs41_abs_path *path, + IN const nfs41_component *name, + OUT nfs41_abs_path *path_out) +{ + /* format an absolute path 'parent\name' */ + int status = NO_ERROR; + + InitializeSRWLock(&path_out->lock); + abs_path_copy(path_out, path); + if (FAILED(StringCchPrintfA(path_out->path + path_out->len, + NFS41_MAX_PATH_LEN - path_out->len, "\\%s", name->name))) { + status = ERROR_FILENAME_EXCED_RANGE; + goto out; + } + path_out->len += name->len + 1; +out: + return status; +} + +static int lookup_entry( + IN nfs41_root *root, + IN nfs41_session *session, + IN nfs41_path_fh *parent, + OUT nfs41_readdir_entry *entry) +{ + nfs41_abs_path path; + nfs41_component name; + int status; + + name.name = entry->name; + name.len = (unsigned short)entry->name_len - 1; + + status = format_abs_path(parent->path, &name, &path); + if (status) goto out; + + status = nfs41_lookup(root, session, &path, + NULL, NULL, &entry->attr_info, NULL); + if (status) goto out; +out: + return status; +} + +static int lookup_symlink( + IN nfs41_root *root, + IN nfs41_session *session, + IN nfs41_path_fh *parent, + IN const nfs41_component *name, + OUT nfs41_file_info *info_out) +{ + nfs41_abs_path path; + nfs41_path_fh file; + nfs41_file_info info; + int status; + + status = format_abs_path(parent->path, name, &path); + if (status) goto out; + + file.path = &path; + status = nfs41_lookup(root, session, &path, NULL, &file, &info, &session); + if (status) goto out; + + last_component(path.path, path.path + path.len, &file.name); + + status = nfs41_symlink_follow(root, session, &file, &info); + if (status) goto out; + + info_out->symlink_dir = info.type == NF4DIR; +out: + return status; +} + +static int readdir_copy_entry( + IN readdir_upcall_args *args, + IN nfs41_readdir_entry *entry, + IN OUT unsigned char **dst_pos, + IN OUT uint32_t *dst_len) +{ + int status = 0; + WCHAR wname[NFS4_OPAQUE_LIMIT]; + uint32_t wname_len, wname_size, needed; + PFILE_DIR_INFO_UNION info; + + wname_len = MultiByteToWideChar(CP_UTF8, 0, + entry->name, entry->name_len, wname, NFS4_OPAQUE_LIMIT); + wname_size = (wname_len - 1) * sizeof(WCHAR); + + needed = readdir_size_for_entry(args->query_class, wname_size); + if (!needed || needed > *dst_len) { + status = -1; + goto out; + } + + info = (PFILE_DIR_INFO_UNION)*dst_pos; + info->NextEntryOffset = align8(needed); + *dst_pos += info->NextEntryOffset; + *dst_len -= info->NextEntryOffset; + + if (entry->attr_info.rdattr_error == NFS4ERR_MOVED) { + entry->attr_info.type = NF4DIR; /* default to dir */ + /* look up attributes for referral entries, but ignore return value; + * it's okay if lookup fails, we'll just write garbage attributes */ + lookup_entry(args->root, args->state->session, + &args->state->file, entry); + } else if (entry->attr_info.type == NF4LNK) { + nfs41_component name; + name.name = entry->name; + name.len = (unsigned short)entry->name_len - 1; + /* look up the symlink target to see whether it's a directory */ + lookup_symlink(args->root, args->state->session, + &args->state->file, &name, &entry->attr_info); + } + + switch (args->query_class) + { + case FileNamesInformation: + info->fni.FileIndex = 0; + readdir_copy_filename(wname, wname_size, + info->fni.FileName, &info->fni.FileNameLength); + break; + case FileDirectoryInformation: + readdir_copy_dir_info(entry, info); + readdir_copy_filename(wname, wname_size, + info->fdi.FileName, &info->fdi.FileNameLength); + break; + case FileFullDirectoryInformation: + readdir_copy_full_dir_info(entry, info); + readdir_copy_filename(wname, wname_size, + info->ffdi.FileName, &info->ffdi.FileNameLength); + break; + case FileIdFullDirectoryInformation: + readdir_copy_full_dir_info(entry, info); + info->fibdi.FileId.QuadPart = (LONGLONG)entry->attr_info.fileid; + readdir_copy_filename(wname, wname_size, + info->fifdi.FileName, &info->fifdi.FileNameLength); + break; + case FileBothDirectoryInformation: + readdir_copy_both_dir_info(entry, wname, info); + readdir_copy_filename(wname, wname_size, + info->fbdi.FileName, &info->fbdi.FileNameLength); + break; + case FileIdBothDirectoryInformation: + readdir_copy_both_dir_info(entry, wname, info); + info->fibdi.FileId.QuadPart = (LONGLONG)entry->attr_info.fileid; + readdir_copy_filename(wname, wname_size, + info->fibdi.FileName, &info->fibdi.FileNameLength); + break; + default: + eprintf("unhandled dir query class %d\n", args->query_class); + status = -1; + break; + } +out: + return status; +} + +#define COOKIE_DOT ((uint64_t)-2) +#define COOKIE_DOTDOT ((uint64_t)-1) + +static int readdir_add_dots( + IN readdir_upcall_args *args, + IN OUT unsigned char *entry_buf, + IN uint32_t entry_buf_len, + OUT uint32_t *len_out, + OUT uint32_t **last_offset) +{ + int status = 0; + const uint32_t entry_len = (uint32_t)FIELD_OFFSET(nfs41_readdir_entry, name); + nfs41_readdir_entry *entry; + nfs41_open_state *state = args->state; + + *len_out = 0; + *last_offset = NULL; + switch (state->cookie.cookie) { + case 0: + if (entry_buf_len < entry_len + 2) { + status = ERROR_BUFFER_OVERFLOW; + dprintf(1, "not enough room for '.' entry. received %d need %d\n", + entry_buf_len, entry_len + 2); + args->query_reply_len = entry_len + 2; + goto out; + } + + entry = (nfs41_readdir_entry*)entry_buf; + ZeroMemory(&entry->attr_info, sizeof(nfs41_file_info)); + + status = nfs41_cached_getattr(state->session, + &state->file, &entry->attr_info); + if (status) { + dprintf(1, "failed to add '.' entry.\n"); + goto out; + } + entry->cookie = COOKIE_DOT; + entry->name_len = 2; + StringCbCopyA(entry->name, entry->name_len, "."); + entry->next_entry_offset = entry_len + entry->name_len; + + entry_buf += entry->next_entry_offset; + entry_buf_len -= entry->next_entry_offset; + *len_out += entry->next_entry_offset; + *last_offset = &entry->next_entry_offset; + if (args->single) + break; + /* else no break! */ + case COOKIE_DOT: + if (entry_buf_len < entry_len + 3) { + status = ERROR_BUFFER_OVERFLOW; + dprintf(1, "not enough room for '..' entry. received %d need %d\n", + entry_buf_len, entry_len); + args->query_reply_len = entry_len + 2; + goto out; + } + /* XXX: this skips '..' when listing root fh */ + if (state->file.name.len == 0) + break; + + entry = (nfs41_readdir_entry*)entry_buf; + ZeroMemory(&entry->attr_info, sizeof(nfs41_file_info)); + + status = nfs41_cached_getattr(state->session, + &state->parent, &entry->attr_info); + if (status) { + status = ERROR_FILE_NOT_FOUND; + dprintf(1, "failed to add '..' entry.\n"); + goto out; + } + entry->cookie = COOKIE_DOTDOT; + entry->name_len = 3; + StringCbCopyA(entry->name, entry->name_len, ".."); + entry->next_entry_offset = entry_len + entry->name_len; + + entry_buf += entry->next_entry_offset; + entry_buf_len -= entry->next_entry_offset; + *len_out += entry->next_entry_offset; + *last_offset = &entry->next_entry_offset; + break; + } + if (state->cookie.cookie == COOKIE_DOTDOT || + state->cookie.cookie == COOKIE_DOT) + ZeroMemory(&state->cookie, sizeof(nfs41_readdir_cookie)); +out: + return status; +} + +static int handle_readdir(nfs41_upcall *upcall) +{ + int status; + readdir_upcall_args *args = &upcall->args.readdir; + nfs41_open_state *state = upcall->state_ref; + unsigned char *entry_buf = NULL; + uint32_t entry_buf_len; + bitmap4 attr_request; + bool_t eof; + /* make sure we allocate enough space for one nfs41_readdir_entry */ + const uint32_t max_buf_len = max(args->buf_len, + sizeof(nfs41_readdir_entry) + NFS41_MAX_COMPONENT_LEN); + + dprintf(1, "-> handle_nfs41_dirquery(%s,%d,%d,%d)\n", + args->filter, args->initial, args->restart, args->single); + + args->query_reply_len = 0; + + if (args->initial || args->restart) { + ZeroMemory(&state->cookie, sizeof(nfs41_readdir_cookie)); + if (!state->cookie.cookie) + dprintf(1, "initializing the 1st readdir cookie\n"); + else if (args->restart) + dprintf(1, "restarting; clearing previous cookie %llu\n", + state->cookie.cookie); + else if (args->initial) + dprintf(1, "*** initial; clearing previous cookie %llu!\n", + state->cookie.cookie); + } else if (!state->cookie.cookie) { + dprintf(1, "handle_nfs41_readdir: EOF\n"); + status = ERROR_NO_MORE_FILES; + goto out; + } + + entry_buf = calloc(max_buf_len, sizeof(unsigned char)); + if (entry_buf == NULL) { + status = GetLastError(); + goto out_free_cookie; + } +fetch_entries: + entry_buf_len = max_buf_len; + + nfs41_superblock_getattr_mask(state->file.fh.superblock, &attr_request); + attr_request.arr[0] |= FATTR4_WORD0_RDATTR_ERROR; + + if (strchr(args->filter, FILTER_STAR) || strchr(args->filter, FILTER_QM)) { + /* use READDIR for wildcards */ + + uint32_t dots_len = 0; + uint32_t *dots_next_offset = NULL; + + if (args->filter[0] == '*' && args->filter[1] == '\0') { + status = readdir_add_dots(args, entry_buf, + entry_buf_len, &dots_len, &dots_next_offset); + if (status) + goto out_free_cookie; + entry_buf_len -= dots_len; + } + + if (dots_len && args->single) { + dprintf(2, "skipping nfs41_readdir because the single query " + "will use . or ..\n"); + entry_buf_len = 0; + eof = 0; + } else { + dprintf(2, "calling nfs41_readdir with cookie %llu\n", + state->cookie.cookie); + status = nfs41_readdir(state->session, &state->file, + &attr_request, &state->cookie, entry_buf + dots_len, + &entry_buf_len, &eof); + if (status) { + dprintf(1, "nfs41_readdir failed with %s\n", + nfs_error_string(status)); + status = nfs_to_windows_error(status, ERROR_BAD_NET_RESP); + goto out_free_cookie; + } + } + + if (!entry_buf_len && dots_next_offset) + *dots_next_offset = 0; + entry_buf_len += dots_len; + } else { + /* use LOOKUP for single files */ + nfs41_readdir_entry *entry = (nfs41_readdir_entry*)entry_buf; + entry->cookie = 0; + entry->name_len = (uint32_t)strlen(args->filter) + 1; + StringCbCopyA(entry->name, entry->name_len, args->filter); + entry->next_entry_offset = 0; + + status = lookup_entry(upcall->root_ref, + state->session, &state->file, entry); + if (status) { + dprintf(1, "single_lookup failed with %d\n", status); + goto out_free_cookie; + } + entry_buf_len = entry->name_len + + FIELD_OFFSET(nfs41_readdir_entry, name); + + eof = 1; + } + + status = args->initial ? ERROR_FILE_NOT_FOUND : ERROR_NO_MORE_FILES; + + if (entry_buf_len) { + unsigned char *entry_pos = entry_buf; + unsigned char *dst_pos = args->kbuf; + uint32_t dst_len = args->buf_len; + nfs41_readdir_entry *entry; + PULONG offset, last_offset = NULL; + + for (;;) { + entry = (nfs41_readdir_entry*)entry_pos; + offset = (PULONG)dst_pos; /* ULONG NextEntryOffset */ + + dprintf(2, "filter %s looking at %s with cookie %d\n", + args->filter, entry->name, entry->cookie); + if (readdir_filter((const char*)args->filter, entry->name)) { + if (readdir_copy_entry(args, entry, &dst_pos, &dst_len)) { + eof = 0; + dprintf(2, "not enough space to copy entry %s (cookie %d)\n", + entry->name, entry->cookie); + break; + } + last_offset = offset; + status = NO_ERROR; + } + state->cookie.cookie = entry->cookie; + + /* last entry we got from the server */ + if (!entry->next_entry_offset) + break; + + /* we found our single entry, but the server has more */ + if (args->single && last_offset) { + eof = 0; + break; + } + entry_pos += entry->next_entry_offset; + } + args->query_reply_len = args->buf_len - dst_len; + if (last_offset) { + *last_offset = 0; + } else if (!eof) { + dprintf(1, "no entries matched; fetch more\n"); + goto fetch_entries; + } + } + + if (eof) { + dprintf(1, "we don't need to save a cookie\n"); + goto out_free_cookie; + } else + dprintf(1, "saving cookie %llu\n", state->cookie.cookie); + +out_free_entry: + free(entry_buf); +out: + dprintf(1, "<- handle_nfs41_dirquery(%s,%d,%d,%d) returning ", + args->filter, args->initial, args->restart, args->single); + if (status) { + switch (status) { + case ERROR_FILE_NOT_FOUND: + dprintf(1, "ERROR_FILE_NOT_FOUND.\n"); + break; + case ERROR_NO_MORE_FILES: + dprintf(1, "ERROR_NO_MORE_FILES.\n"); + break; + case ERROR_BUFFER_OVERFLOW: + upcall->last_error = status; + status = ERROR_SUCCESS; + break; + default: + dprintf(1, "error code %d.\n", status); + break; + } + } else { + dprintf(1, "success!\n"); + } + return status; +out_free_cookie: + state->cookie.cookie = 0; + goto out_free_entry; +} + +static int marshall_readdir(unsigned char *buffer, uint32_t *length, nfs41_upcall *upcall) +{ + int status; + readdir_upcall_args *args = &upcall->args.readdir; + + status = safe_write(&buffer, length, &args->query_reply_len, sizeof(args->query_reply_len)); + return status; +} + + +const nfs41_upcall_op nfs41_op_readdir = { + parse_readdir, + handle_readdir, + marshall_readdir +}; diff --git a/reactos/base/services/nfsd/readwrite.c b/reactos/base/services/nfsd/readwrite.c new file mode 100644 index 00000000000..6844df5fddf --- /dev/null +++ b/reactos/base/services/nfsd/readwrite.c @@ -0,0 +1,325 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include +#include + +#include "nfs41_ops.h" +#include "name_cache.h" +#include "upcall.h" +#include "daemon_debug.h" +#include "util.h" + + +/* number of times to retry on write/commit verifier mismatch */ +#define MAX_WRITE_RETRIES 6 + + +const stateid4 special_read_stateid = {0xffffffff, + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}; + +static int parse_rw(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall) +{ + int status; + readwrite_upcall_args *args = &upcall->args.rw; + + status = safe_read(&buffer, &length, &args->len, sizeof(args->len)); + if (status) goto out; + status = safe_read(&buffer, &length, &args->offset, sizeof(args->offset)); + if (status) goto out; + status = safe_read(&buffer, &length, &args->buffer, sizeof(args->buffer)); + if (status) goto out; + + dprintf(1, "parsing %s len=%lu offset=%llu buf=%p\n", + opcode2string(upcall->opcode), args->len, args->offset, args->buffer); +out: + return status; +} + +/* NFS41_READ */ +static int read_from_mds( + IN nfs41_upcall *upcall, + IN stateid_arg *stateid) +{ + nfs41_session *session = upcall->state_ref->session; + nfs41_path_fh *file = &upcall->state_ref->file; + readwrite_upcall_args *args = &upcall->args.rw; + int status = 0; + bool_t eof; + unsigned char *p = args->buffer; + ULONG to_rcv = args->len, reloffset = 0, len = 0; + const uint32_t maxreadsize = max_read_size(session, &file->fh); + + if (to_rcv > maxreadsize) + dprintf(1, "handle_nfs41_read: reading %d in chunks of %d\n", + to_rcv, maxreadsize); + + while(to_rcv > 0) { + uint32_t bytes_read = 0, chunk = min(to_rcv, maxreadsize); + + status = nfs41_read(session, file, stateid, args->offset + reloffset, chunk, + p, &bytes_read, &eof); + if (status == NFS4ERR_OPENMODE && !len) { + stateid->type = STATEID_SPECIAL; + memcpy(&stateid->stateid, &special_read_stateid, sizeof(stateid4)); + continue; + } else if (status && !len) { + status = nfs_to_windows_error(status, ERROR_NET_WRITE_FAULT); + goto out; + } + + p += bytes_read; + to_rcv -= bytes_read; + len += bytes_read; + args->offset += bytes_read; + if (status) { + status = NO_ERROR; + break; + } + if (eof) { + if (!len) + status = ERROR_HANDLE_EOF; + break; + } + } +out: + args->out_len = len; + return status; +} + +static int read_from_pnfs( + IN nfs41_upcall *upcall, + IN stateid_arg *stateid) +{ + readwrite_upcall_args *args = &upcall->args.rw; + pnfs_layout_state *layout; + enum pnfs_status pnfsstat; + int status = NO_ERROR; + + if (pnfs_layout_state_open(upcall->state_ref, &layout)) { + status = ERROR_NOT_SUPPORTED; + goto out; + } + + pnfsstat = pnfs_read(upcall->root_ref, upcall->state_ref, stateid, layout, + args->offset, args->len, args->buffer, &args->out_len); + switch (pnfsstat) { + case PNFS_SUCCESS: + break; + case PNFS_READ_EOF: + status = ERROR_HANDLE_EOF; + break; + default: + status = ERROR_READ_FAULT; + break; + } +out: + return status; +} + +static int handle_read(nfs41_upcall *upcall) +{ + readwrite_upcall_args *args = &upcall->args.rw; + stateid_arg stateid; + ULONG pnfs_bytes_read = 0; + int status = NO_ERROR; + + nfs41_open_stateid_arg(upcall->state_ref, &stateid); + +#ifdef PNFS_ENABLE_READ + status = read_from_pnfs(upcall, &stateid); + + if (status == NO_ERROR || status == ERROR_HANDLE_EOF) + goto out; + + if (args->out_len) { + pnfs_bytes_read = args->out_len; + args->out_len = 0; + + args->offset += pnfs_bytes_read; + args->buffer += pnfs_bytes_read; + args->len -= pnfs_bytes_read; + } +#endif + + status = read_from_mds(upcall, &stateid); + + args->out_len += pnfs_bytes_read; +out: + return status; +} + + +/* NFS41_WRITE */ +static int write_to_mds( + IN nfs41_upcall *upcall, + IN stateid_arg *stateid) +{ + nfs41_session *session = upcall->state_ref->session; + nfs41_path_fh *file = &upcall->state_ref->file; + readwrite_upcall_args *args = &upcall->args.rw; + nfs41_write_verf verf; + enum stable_how4 stable, committed; + unsigned char *p; + const uint32_t maxwritesize = max_write_size(session, &file->fh); + uint32_t to_send, reloffset, len; + int status = 0; + /* on write verifier mismatch, retry N times before failing */ + uint32_t retries = MAX_WRITE_RETRIES; + nfs41_file_info info = { 0 }; + +retry_write: + p = args->buffer; + to_send = args->len; + reloffset = 0; + len = 0; + stable = to_send <= maxwritesize ? FILE_SYNC4 : UNSTABLE4; + committed = FILE_SYNC4; + + if (to_send > maxwritesize) + dprintf(1, "handle_nfs41_write: writing %d in chunks of %d\n", + to_send, maxwritesize); + + while(to_send > 0) { + uint32_t bytes_written = 0, chunk = min(to_send, maxwritesize); + + status = nfs41_write(session, file, stateid, p, chunk, + args->offset + reloffset, stable, &bytes_written, &verf, &info); + if (status && !len) + goto out; + p += bytes_written; + to_send -= bytes_written; + len += bytes_written; + reloffset += bytes_written; + if (status) { + status = 0; + break; + } + if (!verify_write(&verf, &committed)) { + if (retries--) goto retry_write; + goto out_verify_failed; + } + } + if (committed != FILE_SYNC4) { + dprintf(1, "sending COMMIT for offset=%d and len=%d\n", args->offset, len); + status = nfs41_commit(session, file, args->offset, len, 1, &verf, &info); + if (status) + goto out; + + if (!verify_commit(&verf)) { + if (retries--) goto retry_write; + goto out_verify_failed; + } + } else if (stable == UNSTABLE4) { + nfs41_file_info info; + bitmap4 attr_request; + nfs41_superblock_getattr_mask(file->fh.superblock, &attr_request); + status = nfs41_getattr(session, file, &attr_request, &info); + if (status) + goto out; + } + args->ctime = info.change; +out: + args->out_len = len; + return nfs_to_windows_error(status, ERROR_NET_WRITE_FAULT); + +out_verify_failed: + len = 0; + status = NFS4ERR_IO; + goto out; +} + +static int write_to_pnfs( + IN nfs41_upcall *upcall, + IN stateid_arg *stateid) +{ + readwrite_upcall_args *args = &upcall->args.rw; + pnfs_layout_state *layout; + int status = NO_ERROR; + nfs41_file_info info = { 0 }; + + if (pnfs_layout_state_open(upcall->state_ref, &layout)) { + status = ERROR_NOT_SUPPORTED; + goto out; + } + + if (pnfs_write(upcall->root_ref, upcall->state_ref, stateid, layout, + args->offset, args->len, args->buffer, &args->out_len, &info)) { + status = ERROR_WRITE_FAULT; + goto out; + } + args->ctime = info.change; +out: + return status; +} + +static int handle_write(nfs41_upcall *upcall) +{ + readwrite_upcall_args *args = &upcall->args.rw; + stateid_arg stateid; + uint32_t pnfs_bytes_written = 0; + int status; + + nfs41_open_stateid_arg(upcall->state_ref, &stateid); + +#ifdef PNFS_ENABLE_WRITE + status = write_to_pnfs(upcall, &stateid); + if (args->out_len) { + pnfs_bytes_written = args->out_len; + args->out_len = 0; + + args->offset += pnfs_bytes_written; + args->buffer += pnfs_bytes_written; + args->len -= pnfs_bytes_written; + + if (args->len == 0) + goto out; + } +#endif + + status = write_to_mds(upcall, &stateid); +out: + args->out_len += pnfs_bytes_written; + return status; +} + +static int marshall_rw(unsigned char *buffer, uint32_t *length, nfs41_upcall *upcall) +{ + readwrite_upcall_args *args = &upcall->args.rw; + int status; + status = safe_write(&buffer, length, &args->out_len, sizeof(args->out_len)); + if (status) goto out; + status = safe_write(&buffer, length, &args->ctime, sizeof(args->ctime)); +out: + return status; +} + + +const nfs41_upcall_op nfs41_op_read = { + parse_rw, + handle_read, + marshall_rw +}; +const nfs41_upcall_op nfs41_op_write = { + parse_rw, + handle_write, + marshall_rw +}; diff --git a/reactos/base/services/nfsd/recovery.c b/reactos/base/services/nfsd/recovery.c new file mode 100644 index 00000000000..e6ee5b16284 --- /dev/null +++ b/reactos/base/services/nfsd/recovery.c @@ -0,0 +1,855 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include + +#include "recovery.h" +#include "delegation.h" +#include "nfs41_callback.h" +#include "nfs41_compound.h" +#include "nfs41_ops.h" +#include "daemon_debug.h" + + +/* session/client recovery uses a lock and condition variable in nfs41_client + * to prevent multiple threads from attempting to recover at the same time */ +bool_t nfs41_recovery_start_or_wait( + IN nfs41_client *client) +{ + bool_t status = TRUE; + + EnterCriticalSection(&client->recovery.lock); + + if (!client->recovery.in_recovery) { + dprintf(1, "Entering recovery mode for client %llu\n", client->clnt_id); + client->recovery.in_recovery = TRUE; + } else { + status = FALSE; + dprintf(1, "Waiting for recovery of client %llu\n", client->clnt_id); + while (client->recovery.in_recovery) + SleepConditionVariableCS(&client->recovery.cond, + &client->recovery.lock, INFINITE); + dprintf(1, "Woke up after recovery of client %llu\n", client->clnt_id); + } + + LeaveCriticalSection(&client->recovery.lock); + return status; +} + +void nfs41_recovery_finish( + IN nfs41_client *client) +{ + EnterCriticalSection(&client->recovery.lock); + dprintf(1, "Finished recovery for client %llu\n", client->clnt_id); + client->recovery.in_recovery = FALSE; + WakeAllConditionVariable(&client->recovery.cond); + LeaveCriticalSection(&client->recovery.lock); +} + + +/* session/client/state recovery */ +int nfs41_recover_session( + IN nfs41_session *session, + IN bool_t client_state_lost) +{ + enum nfsstat4 status = NFS4_OK; + +restart_recovery: + /* recover the session */ + status = nfs41_session_renew(session); + + if (status == NFS4ERR_STALE_CLIENTID) { + /* recover the client */ + client_state_lost = TRUE; + status = nfs41_client_renew(session->client); + if (status == NFS4_OK) + goto restart_recovery; /* resume session recovery */ + + eprintf("nfs41_client_renew() failed with %d\n", status); + } else if (status) { + eprintf("nfs41_session_renew() failed with %d\n", status); + } else if (client_state_lost) { + /* recover the client's state */ + status = nfs41_recover_client_state(session, session->client); + if (status == NFS4ERR_BADSESSION) + goto restart_recovery; + } + return status; +} + +void nfs41_recover_sequence_flags( + IN nfs41_session *session, + IN uint32_t flags) +{ + const uint32_t revoked = flags & + (SEQ4_STATUS_EXPIRED_ALL_STATE_REVOKED + | SEQ4_STATUS_EXPIRED_SOME_STATE_REVOKED + | SEQ4_STATUS_ADMIN_STATE_REVOKED + | SEQ4_STATUS_RECALLABLE_STATE_REVOKED); + const uint32_t restarted = flags & + SEQ4_STATUS_RESTART_RECLAIM_NEEDED; + + /* no state recovery needed */ + if (revoked == 0 && restarted == 0) + return; + + if (!nfs41_recovery_start_or_wait(session->client)) + return; + + if (revoked) { + /* free stateids and attempt to recover them */ + nfs41_client_state_revoked(session, session->client, revoked); + + /* if RESTART_RECLAIM_NEEDED is also set, just do RECLAIM_COMPLETE */ + if (restarted) nfs41_reclaim_complete(session); + + } else if (restarted) { + /* do server reboot state recovery */ + uint32_t status = nfs41_recover_client_state(session, session->client); + if (status == NFS4ERR_BADSESSION) { + /* recover the session and finish state recovery */ + nfs41_recover_session(session, TRUE); + } + } + + nfs41_recovery_finish(session->client); +} + + +/* client state recovery for server reboot or lease expiration */ +static int recover_open_grace( + IN nfs41_session *session, + IN nfs41_path_fh *parent, + IN nfs41_path_fh *file, + IN state_owner4 *owner, + IN uint32_t access, + IN uint32_t deny, + OUT stateid4 *stateid, + OUT open_delegation4 *delegation) +{ + /* reclaim the open stateid with CLAIM_PREVIOUS */ + open_claim4 claim; + claim.claim = CLAIM_PREVIOUS; + claim.u.prev.delegate_type = delegation->type; + + return nfs41_open(session, parent, file, owner, &claim, access, deny, + OPEN4_NOCREATE, 0, NULL, FALSE, stateid, delegation, NULL); +} + +static int recover_open_no_grace( + IN nfs41_session *session, + IN nfs41_path_fh *parent, + IN nfs41_path_fh *file, + IN state_owner4 *owner, + IN uint32_t access, + IN uint32_t deny, + OUT stateid4 *stateid, + OUT open_delegation4 *delegation) +{ + open_claim4 claim; + int status; + + if (delegation->type != OPEN_DELEGATE_NONE) { + /* attempt out-of-grace recovery with CLAIM_DELEGATE_PREV */ + claim.claim = CLAIM_DELEGATE_PREV; + claim.u.deleg_prev.filename = &file->name; + + status = nfs41_open(session, parent, file, owner, + &claim, access, deny, OPEN4_NOCREATE, 0, NULL, FALSE, + stateid, delegation, NULL); + if (status == NFS4_OK || status == NFS4ERR_BADSESSION) + goto out; + + /* server support for CLAIM_DELEGATE_PREV is optional; + * fall back to CLAIM_NULL on errors */ + } + + /* attempt out-of-grace recovery with CLAIM_NULL */ + claim.claim = CLAIM_NULL; + claim.u.null.filename = &file->name; + + /* ask nicely for the delegation we had */ + if (delegation->type == OPEN_DELEGATE_READ) + access |= OPEN4_SHARE_ACCESS_WANT_READ_DELEG; + else if (delegation->type == OPEN_DELEGATE_WRITE) + access |= OPEN4_SHARE_ACCESS_WANT_WRITE_DELEG; + + status = nfs41_open(session, parent, file, owner, + &claim, access, deny, OPEN4_NOCREATE, 0, NULL, FALSE, + stateid, delegation, NULL); +out: + return status; +} + +static int recover_open( + IN nfs41_session *session, + IN nfs41_open_state *open, + IN OUT bool_t *grace) +{ + open_delegation4 delegation = { 0 }; + stateid4 stateid = { 0 }; + int status = NFS4ERR_BADHANDLE; + + /* check for an associated delegation */ + AcquireSRWLockExclusive(&open->lock); + if (open->delegation.state) { + nfs41_delegation_state *deleg = open->delegation.state; + if (deleg->revoked) { + /* reclaim the delegation along with the open */ + AcquireSRWLockShared(&deleg->lock); + delegation.type = deleg->state.type; + ReleaseSRWLockShared(&deleg->lock); + } else if (deleg->state.recalled) { + /* we'll need an open stateid regardless */ + } else if (list_empty(&open->locks.list)) { + /* if there are locks, we need an open stateid to + * reclaim them; otherwise, the open can be delegated */ + open->do_close = FALSE; + status = NFS4_OK; + } + } + ReleaseSRWLockExclusive(&open->lock); + + if (status == NFS4_OK) /* use existing delegation */ + goto out; + + if (*grace) { + status = recover_open_grace(session, &open->parent, &open->file, + &open->owner, open->share_access, open->share_deny, + &stateid, &delegation); + if (status == NFS4ERR_NO_GRACE) { + *grace = FALSE; + /* send RECLAIM_COMPLETE before any out-of-grace recovery */ + nfs41_reclaim_complete(session); + } + } + if (!*grace) { + status = recover_open_no_grace(session, &open->parent, &open->file, + &open->owner, open->share_access, open->share_deny, + &stateid, &delegation); + } + + if (status) + goto out; + + AcquireSRWLockExclusive(&open->lock); + /* update the open stateid */ + memcpy(&open->stateid, &stateid, sizeof(stateid4)); + open->do_close = TRUE; + + if (open->delegation.state) { + nfs41_delegation_state *deleg = open->delegation.state; + if (deleg->revoked) { + /* update delegation state */ + AcquireSRWLockExclusive(&deleg->lock); + if (delegation.type != OPEN_DELEGATE_READ && + delegation.type != OPEN_DELEGATE_WRITE) { + eprintf("recover_open() got delegation type %u, " + "expected %u\n", delegation.type, deleg->state.type); + } else { + memcpy(&deleg->state, &delegation, sizeof(open_delegation4)); + deleg->revoked = FALSE; + } + ReleaseSRWLockExclusive(&deleg->lock); + } + } else /* granted a new delegation? */ + nfs41_delegation_granted(session, &open->parent, &open->file, + &delegation, FALSE, &open->delegation.state); + ReleaseSRWLockExclusive(&open->lock); +out: + return status; +} + +static int recover_locks( + IN nfs41_session *session, + IN nfs41_open_state *open, + IN OUT bool_t *grace) +{ + stateid_arg stateid; + struct list_entry *entry; + nfs41_lock_state *lock; + int status = NFS4_OK; + + AcquireSRWLockExclusive(&open->lock); + + /* initialize the open stateid for the first lock request */ + memcpy(&stateid.stateid, &open->stateid, sizeof(stateid4)); + stateid.type = STATEID_OPEN; + stateid.open = open; + stateid.delegation = NULL; + + /* recover any locks for this open */ + list_for_each(entry, &open->locks.list) { + lock = list_container(entry, nfs41_lock_state, open_entry); + if (lock->delegated) + continue; + + if (*grace) { + status = nfs41_lock(session, &open->file, &open->owner, + lock->exclusive ? WRITE_LT : READ_LT, lock->offset, + lock->length, TRUE, FALSE, &stateid); + if (status == NFS4ERR_NO_GRACE) { + *grace = FALSE; + /* send RECLAIM_COMPLETE before any out-of-grace recovery */ + nfs41_reclaim_complete(session); + } + } + if (!*grace) { + /* attempt out-of-grace recovery with a normal LOCK */ + status = nfs41_lock(session, &open->file, &open->owner, + lock->exclusive ? WRITE_LT : READ_LT, lock->offset, + lock->length, FALSE, FALSE, &stateid); + } + if (status == NFS4ERR_BADSESSION) + break; + } + + if (status != NFS4ERR_BADSESSION) { + /* if we got a lock stateid back, save the lock with the open */ + if (stateid.type == STATEID_LOCK) + memcpy(&open->locks.stateid, &stateid.stateid, sizeof(stateid4)); + else + open->locks.stateid.seqid = 0; + } + + ReleaseSRWLockExclusive(&open->lock); + return status; +} + +/* delegation recovery via WANT_DELEGATION */ +static int recover_delegation_want( + IN nfs41_session *session, + IN nfs41_delegation_state *deleg, + IN OUT bool_t *grace) +{ + deleg_claim4 claim; + open_delegation4 delegation = { 0 }; + uint32_t want_flags = 0; + int status = NFS4_OK; + + AcquireSRWLockShared(&deleg->lock); + delegation.type = deleg->state.type; + ReleaseSRWLockShared(&deleg->lock); + + if (delegation.type == OPEN_DELEGATE_READ) + want_flags |= OPEN4_SHARE_ACCESS_WANT_READ_DELEG; + else + want_flags |= OPEN4_SHARE_ACCESS_WANT_WRITE_DELEG; + + if (*grace) { + /* recover the delegation with WANT_DELEGATION/CLAIM_PREVIOUS */ + claim.claim = CLAIM_PREVIOUS; + claim.prev_delegate_type = delegation.type; + + status = nfs41_want_delegation(session, &deleg->file, &claim, + want_flags, FALSE, &delegation); + if (status == NFS4ERR_NO_GRACE) { + *grace = FALSE; + /* send RECLAIM_COMPLETE before any out-of-grace recovery */ + nfs41_reclaim_complete(session); + } + } + if (!*grace) { + /* attempt out-of-grace recovery with with CLAIM_DELEG_PREV_FH */ + claim.claim = CLAIM_DELEG_PREV_FH; + + status = nfs41_want_delegation(session, &deleg->file, &claim, + want_flags, FALSE, &delegation); + } + if (status) + goto out; + + /* update delegation state */ + AcquireSRWLockExclusive(&deleg->lock); + if (delegation.type != OPEN_DELEGATE_READ && + delegation.type != OPEN_DELEGATE_WRITE) { + eprintf("recover_delegation_want() got delegation type %u, " + "expected %u\n", delegation.type, deleg->state.type); + } else { + memcpy(&deleg->state, &delegation, sizeof(open_delegation4)); + deleg->revoked = FALSE; + } + ReleaseSRWLockExclusive(&deleg->lock); +out: + return status; +} + +/* delegation recovery via OPEN (requires corresponding CLOSE) */ +static int recover_delegation_open( + IN nfs41_session *session, + IN nfs41_delegation_state *deleg, + IN OUT bool_t *grace) +{ + state_owner4 owner; + open_delegation4 delegation = { 0 }; + stateid_arg stateid; + uint32_t access = OPEN4_SHARE_ACCESS_READ; + uint32_t deny = OPEN4_SHARE_DENY_NONE; + int status = NFS4_OK; + + /* choose the desired access mode based on delegation type */ + AcquireSRWLockShared(&deleg->lock); + delegation.type = deleg->state.type; + if (delegation.type == OPEN_DELEGATE_WRITE) + access |= OPEN4_SHARE_ACCESS_WRITE | OPEN4_SHARE_ACCESS_WANT_WRITE_DELEG; + else + access |= OPEN4_SHARE_ACCESS_WANT_READ_DELEG; + ReleaseSRWLockShared(&deleg->lock); + + /* construct a temporary open owner by concatenating the time + * in seconds with the delegation pointer */ + time((time_t*)owner.owner); + memcpy(owner.owner + sizeof(time_t), deleg, sizeof(deleg)); + owner.owner_len = sizeof(time_t) + sizeof(deleg); + + if (*grace) { + status = recover_open_grace(session, &deleg->parent, &deleg->file, + &owner, access, deny, &stateid.stateid, &delegation); + if (status == NFS4ERR_NO_GRACE) { + *grace = FALSE; + /* send RECLAIM_COMPLETE before any out-of-grace recovery */ + nfs41_reclaim_complete(session); + } + } + if (!*grace) { + status = recover_open_no_grace(session, &deleg->parent, &deleg->file, + &owner, access, deny, &stateid.stateid, &delegation); + } + if (status) + goto out; + + /* update delegation state */ + AcquireSRWLockExclusive(&deleg->lock); + if (delegation.type != OPEN_DELEGATE_READ && + delegation.type != OPEN_DELEGATE_WRITE) { + eprintf("recover_delegation_open() got delegation type %u, " + "expected %u\n", delegation.type, deleg->state.type); + } else { + memcpy(&deleg->state, &delegation, sizeof(open_delegation4)); + deleg->revoked = FALSE; + } + ReleaseSRWLockExclusive(&deleg->lock); + + /* send CLOSE to free the open stateid */ + stateid.open = NULL; + stateid.delegation = NULL; + stateid.type = STATEID_OPEN; + nfs41_close(session, &deleg->file, &stateid); +out: + return status; +} + +static int recover_delegation( + IN nfs41_session *session, + IN nfs41_delegation_state *deleg, + IN OUT bool_t *grace, + IN OUT bool_t *want_supported) +{ + int status; + + /* 10.2.1. Delegation Recovery + * When a client needs to reclaim a delegation and there is no + * associated open, the client may use the CLAIM_PREVIOUS variant + * of the WANT_DELEGATION operation. However, since the server is + * not required to support this operation, an alternative is to + * reclaim via a dummy OPEN together with the delegation using an + * OPEN of type CLAIM_PREVIOUS. */ + if (*want_supported) + status = recover_delegation_want(session, deleg, grace); + else + status = NFS4ERR_NOTSUPP; + + if (status == NFS4ERR_NOTSUPP) { + *want_supported = FALSE; + status = recover_delegation_open(session, deleg, grace); + } + return status; +} + +int nfs41_recover_client_state( + IN nfs41_session *session, + IN nfs41_client *client) +{ + const struct cb_layoutrecall_args recall = { PNFS_LAYOUTTYPE_FILE, + PNFS_IOMODE_ANY, TRUE, { PNFS_RETURN_ALL } }; + struct client_state *state = &session->client->state; + struct list_entry *entry; + nfs41_open_state *open; + nfs41_delegation_state *deleg; + bool_t grace = TRUE; + bool_t want_supported = TRUE; + int status = NFS4_OK; + + EnterCriticalSection(&state->lock); + + /* flag all delegations as revoked until successful recovery; + * recover_open() and recover_delegation_open() will only ask + * for delegations when revoked = TRUE */ + list_for_each(entry, &state->delegations) { + deleg = list_container(entry, nfs41_delegation_state, client_entry); + deleg->revoked = TRUE; + } + + /* recover each of the client's opens and associated delegations */ + list_for_each(entry, &state->opens) { + open = list_container(entry, nfs41_open_state, client_entry); + status = recover_open(session, open, &grace); + if (status == NFS4_OK) + status = recover_locks(session, open, &grace); + if (status == NFS4ERR_BADSESSION) + goto unlock; + } + + /* recover delegations that weren't associated with any opens */ + list_for_each(entry, &state->delegations) { + deleg = list_container(entry, nfs41_delegation_state, client_entry); + if (deleg->revoked) { + status = recover_delegation(session, + deleg, &grace, &want_supported); + if (status == NFS4ERR_BADSESSION) + goto unlock; + } + } + + /* return any delegations that were reclaimed as 'recalled' */ + status = nfs41_client_delegation_recovery(client); +unlock: + LeaveCriticalSection(&state->lock); + + /* revoke all of the client's layouts */ + pnfs_file_layout_recall(client, &recall); + + if (grace && status != NFS4ERR_BADSESSION) { + /* send reclaim_complete, but don't fail on errors */ + nfs41_reclaim_complete(session); + } + return status; +} + +static uint32_t stateid_array( + IN struct list_entry *delegations, + IN struct list_entry *opens, + OUT stateid_arg **stateids_out, + OUT uint32_t **statuses_out) +{ + struct list_entry *entry; + nfs41_open_state *open; + nfs41_delegation_state *deleg; + stateid_arg *stateids = NULL; + uint32_t *statuses = NULL; + uint32_t i = 0, count = 0; + + /* count how many stateids the client needs to test */ + list_for_each(entry, delegations) + count++; + list_for_each(entry, opens) + count += 3; /* open and potentially lock and layout */ + + if (count == 0) + goto out; + + /* allocate the stateid and status arrays */ + stateids = calloc(count, sizeof(stateid_arg)); + if (stateids == NULL) + goto out_err; + statuses = calloc(count, sizeof(uint32_t)); + if (statuses == NULL) + goto out_err; + memset(statuses, NFS4ERR_BAD_STATEID, count * sizeof(uint32_t)); + + /* copy stateids into the array */ + list_for_each(entry, delegations) { + deleg = list_container(entry, nfs41_delegation_state, client_entry); + AcquireSRWLockShared(&deleg->lock); + /* delegation stateid */ + memcpy(&stateids[i].stateid, &deleg->state.stateid, sizeof(stateid4)); + stateids[i].type = STATEID_DELEG_FILE; + stateids[i].delegation = deleg; + i++; + ReleaseSRWLockShared(&deleg->lock); + } + + list_for_each(entry, opens) { + open = list_container(entry, nfs41_open_state, client_entry); + + AcquireSRWLockShared(&open->lock); + /* open stateid */ + memcpy(&stateids[i].stateid, &open->stateid, sizeof(stateid4)); + stateids[i].type = STATEID_OPEN; + stateids[i].open = open; + i++; + + if (open->locks.stateid.seqid) { /* lock stateid? */ + memcpy(&stateids[i].stateid, &open->locks.stateid, sizeof(stateid4)); + stateids[i].type = STATEID_LOCK; + stateids[i].open = open; + i++; + } + + if (open->layout) { /* layout stateid? */ + AcquireSRWLockShared(&open->layout->lock); + if (open->layout->stateid.seqid) { + memcpy(&stateids[i].stateid, &open->layout->stateid, sizeof(stateid4)); + stateids[i].type = STATEID_LAYOUT; + stateids[i].open = open; + i++; + } + ReleaseSRWLockShared(&open->layout->lock); + } + ReleaseSRWLockShared(&open->lock); + } + + count = i; + *stateids_out = stateids; + *statuses_out = statuses; +out: + return count; + +out_err: + free(stateids); + free(statuses); + count = 0; + goto out; +} + +void nfs41_client_state_revoked( + IN nfs41_session *session, + IN nfs41_client *client, + IN uint32_t revoked) +{ + const struct cb_layoutrecall_args recall = { PNFS_LAYOUTTYPE_FILE, + PNFS_IOMODE_ANY, TRUE, { PNFS_RETURN_ALL } }; + struct list_entry empty, *opens; + struct client_state *clientstate = &session->client->state; + stateid_arg *stateids = NULL; + uint32_t *statuses = NULL; + uint32_t i, count; + bool_t grace = TRUE; + bool_t want_supported = TRUE; + + EnterCriticalSection(&clientstate->lock); + + if (revoked == SEQ4_STATUS_RECALLABLE_STATE_REVOKED) { + /* only delegations were revoked. use an empty list for opens */ + list_init(&empty); + opens = ∅ + } else { + opens = &clientstate->opens; + } + + /* get an array of the client's stateids */ + count = stateid_array(&clientstate->delegations, + opens, &stateids, &statuses); + if (count == 0) + goto out; + + /* determine which stateids were revoked with TEST_STATEID */ + if ((revoked & SEQ4_STATUS_EXPIRED_ALL_STATE_REVOKED) == 0) + nfs41_test_stateid(session, stateids, count, statuses); + + /* free all revoked stateids with FREE_STATEID */ + for (i = 0; i < count; i++) + if (statuses[i]) + nfs41_free_stateid(session, &stateids[i].stateid); + + /* revoke all of the client's layouts */ + pnfs_file_layout_recall(client, &recall); + + /* recover the revoked stateids */ + for (i = 0; i < count; i++) { + if (statuses[i]) { + if (stateids[i].type == STATEID_DELEG_FILE) + stateids[i].delegation->revoked = TRUE; + else if (stateids[i].type == STATEID_OPEN) + recover_open(session, stateids[i].open, &grace); + else if (stateids[i].type == STATEID_LOCK) + recover_locks(session, stateids[i].open, &grace); + } + } + for (i = 0; i < count; i++) { + /* delegations that weren't recovered by recover_open() */ + if (statuses[i] && stateids[i].type == STATEID_DELEG_FILE + && stateids[i].delegation->revoked) + recover_delegation(session, stateids[i].delegation, + &grace, &want_supported); + } + + nfs41_client_delegation_recovery(client); +out: + LeaveCriticalSection(&clientstate->lock); + free(stateids); + free(statuses); +} + + +static bool_t recover_stateid_open( + IN nfs_argop4 *argop, + IN stateid_arg *stateid) +{ + bool_t retry = FALSE; + + if (stateid->open) { + stateid4 *source = &stateid->open->stateid; + + /* if the source stateid is different, update and retry */ + AcquireSRWLockShared(&stateid->open->lock); + if (memcmp(&stateid->stateid, source, sizeof(stateid4))) { + memcpy(&stateid->stateid, source, sizeof(stateid4)); + retry = TRUE; + } + ReleaseSRWLockShared(&stateid->open->lock); + } + return retry; +} + +static bool_t recover_stateid_lock( + IN nfs_argop4 *argop, + IN stateid_arg *stateid) +{ + bool_t retry = FALSE; + + if (stateid->open) { + stateid4 *source = &stateid->open->locks.stateid; + + /* if the source stateid is different, update and retry */ + AcquireSRWLockShared(&stateid->open->lock); + if (memcmp(&stateid->stateid, source, sizeof(stateid4))) { + if (argop->op == OP_LOCK && source->seqid == 0) { + /* resend LOCK with an open stateid */ + nfs41_lock_args *lock = (nfs41_lock_args*)argop->arg; + lock->locker.new_lock_owner = 1; + lock->locker.u.open_owner.open_stateid = stateid; + lock->locker.u.open_owner.lock_owner = &stateid->open->owner; + source = &stateid->open->stateid; + } + + memcpy(&stateid->stateid, source, sizeof(stateid4)); + retry = TRUE; + } + ReleaseSRWLockShared(&stateid->open->lock); + } + return retry; +} + +static bool_t recover_stateid_delegation( + IN nfs_argop4 *argop, + IN stateid_arg *stateid) +{ + bool_t retry = FALSE; + + if (stateid->open) { + /* if the source stateid is different, update and retry */ + AcquireSRWLockShared(&stateid->open->lock); + if (argop->op == OP_OPEN && stateid->open->do_close) { + /* for nfs41_delegation_to_open(); if we've already reclaimed + * an open stateid, just fail this OPEN with BAD_STATEID */ + } else if (stateid->open->delegation.state) { + nfs41_delegation_state *deleg = stateid->open->delegation.state; + stateid4 *source = &deleg->state.stateid; + AcquireSRWLockShared(&deleg->lock); + if (memcmp(&stateid->stateid, source, sizeof(stateid4))) { + memcpy(&stateid->stateid, source, sizeof(stateid4)); + retry = TRUE; + } + ReleaseSRWLockShared(&deleg->lock); + } + ReleaseSRWLockShared(&stateid->open->lock); + } else if (stateid->delegation) { + nfs41_delegation_state *deleg = stateid->delegation; + stateid4 *source = &deleg->state.stateid; + AcquireSRWLockShared(&deleg->lock); + if (memcmp(&stateid->stateid, source, sizeof(stateid4))) { + memcpy(&stateid->stateid, source, sizeof(stateid4)); + retry = TRUE; + } + ReleaseSRWLockShared(&deleg->lock); + } + return retry; +} + +bool_t nfs41_recover_stateid( + IN nfs41_session *session, + IN nfs_argop4 *argop) +{ + stateid_arg *stateid = NULL; + + /* get the stateid_arg from the operation's arguments */ + if (argop->op == OP_OPEN) { + nfs41_op_open_args *open = (nfs41_op_open_args*)argop->arg; + if (open->claim->claim == CLAIM_DELEGATE_CUR) + stateid = open->claim->u.deleg_cur.delegate_stateid; + else if (open->claim->claim == CLAIM_DELEG_CUR_FH) + stateid = open->claim->u.deleg_cur_fh.delegate_stateid; + } else if (argop->op == OP_CLOSE) { + nfs41_op_close_args *close = (nfs41_op_close_args*)argop->arg; + stateid = close->stateid; + } else if (argop->op == OP_READ) { + nfs41_read_args *read = (nfs41_read_args*)argop->arg; + stateid = read->stateid; + } else if (argop->op == OP_WRITE) { + nfs41_write_args *write = (nfs41_write_args*)argop->arg; + stateid = write->stateid; + } else if (argop->op == OP_LOCK) { + nfs41_lock_args *lock = (nfs41_lock_args*)argop->arg; + if (lock->locker.new_lock_owner) + stateid = lock->locker.u.open_owner.open_stateid; + else + stateid = lock->locker.u.lock_owner.lock_stateid; + } else if (argop->op == OP_LOCKU) { + nfs41_locku_args *locku = (nfs41_locku_args*)argop->arg; + stateid = locku->lock_stateid; + } else if (argop->op == OP_SETATTR) { + nfs41_setattr_args *setattr = (nfs41_setattr_args*)argop->arg; + stateid = setattr->stateid; + } else if (argop->op == OP_LAYOUTGET) { + pnfs_layoutget_args *lget = (pnfs_layoutget_args*)argop->arg; + stateid = lget->stateid; + } else if (argop->op == OP_DELEGRETURN) { + nfs41_delegreturn_args *dr = (nfs41_delegreturn_args*)argop->arg; + stateid = dr->stateid; + } + if (stateid == NULL) + return FALSE; + + /* if there's recovery in progress, wait for it to finish */ + EnterCriticalSection(&session->client->recovery.lock); + while (session->client->recovery.in_recovery) + SleepConditionVariableCS(&session->client->recovery.cond, + &session->client->recovery.lock, INFINITE); + LeaveCriticalSection(&session->client->recovery.lock); + + switch (stateid->type) { + case STATEID_OPEN: + return recover_stateid_open(argop, stateid); + + case STATEID_LOCK: + return recover_stateid_lock(argop, stateid); + + case STATEID_DELEG_FILE: + return recover_stateid_delegation(argop, stateid); + + default: + eprintf("%s can't recover stateid type %u\n", + nfs_opnum_to_string(argop->op), stateid->type); + break; + } + return FALSE; +} diff --git a/reactos/base/services/nfsd/recovery.h b/reactos/base/services/nfsd/recovery.h new file mode 100644 index 00000000000..6bd63d3f93e --- /dev/null +++ b/reactos/base/services/nfsd/recovery.h @@ -0,0 +1,59 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#ifndef RECOVERY_H +#define RECOVERY_H + +#include "nfs41.h" + + +/* session/client recovery uses a lock and condition variable in nfs41_client + * to prevent multiple threads from attempting to recover at the same time */ +bool_t nfs41_recovery_start_or_wait( + IN nfs41_client *client); + +void nfs41_recovery_finish( + IN nfs41_client *client); + + +int nfs41_recover_session( + IN nfs41_session *session, + IN bool_t client_state_lost); + +void nfs41_recover_sequence_flags( + IN nfs41_session *session, + IN uint32_t flags); + +int nfs41_recover_client_state( + IN nfs41_session *session, + IN nfs41_client *client); + +void nfs41_client_state_revoked( + IN nfs41_session *session, + IN nfs41_client *client, + IN uint32_t revoked); + +struct __nfs_argop4; +bool_t nfs41_recover_stateid( + IN nfs41_session *session, + IN struct __nfs_argop4 *argop); + +#endif /* RECOVERY_H */ diff --git a/reactos/base/services/nfsd/service.c b/reactos/base/services/nfsd/service.c new file mode 100644 index 00000000000..c31fd143d64 --- /dev/null +++ b/reactos/base/services/nfsd/service.c @@ -0,0 +1,607 @@ +/*--------------------------------------------------------------------------- +THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +PARTICULAR PURPOSE. + +Copyright (C) Microsoft Corporation. All rights reserved. + +MODULE: service.c + +PURPOSE: Implements functions required by all Windows NT services + +FUNCTIONS: + main(int argc, char **argv); + service_ctrl(DWORD dwCtrlCode); + service_main(DWORD dwArgc, LPTSTR *lpszArgv); + CmdInstallService(); + CmdRemoveService(); + CmdDebugService(int argc, char **argv); + ControlHandler ( DWORD dwCtrlType ); + GetLastErrorText( LPTSTR lpszBuf, DWORD dwSize ); + + ---------------------------------------------------------------------------*/ +#include +#ifndef STANDALONE_NFSD +#include +#include +#include +#include + +#include "service.h" + +// internal variables +SERVICE_STATUS ssStatus; // current status of the service +SERVICE_STATUS_HANDLE sshStatusHandle; +DWORD dwErr = 0; +BOOL bDebug = FALSE; +TCHAR szErr[256]; + +// internal function prototypes +VOID WINAPI service_ctrl(DWORD dwCtrlCode); +VOID WINAPI service_main(DWORD dwArgc, LPTSTR *lpszArgv); +VOID CmdInstallService(); +VOID CmdRemoveService(); +VOID CmdDebugService(int argc, char **argv); +BOOL WINAPI ControlHandler ( DWORD dwCtrlType ); +LPTSTR GetLastErrorText( LPTSTR lpszBuf, DWORD dwSize ); + +// +// FUNCTION: main +// +// PURPOSE: entrypoint for service +// +// PARAMETERS: +// argc - number of command line arguments +// argv - array of command line arguments +// +// RETURN VALUE: +// none +// +// COMMENTS: +// main() either performs the command line task, or +// call StartServiceCtrlDispatcher to register the +// main service thread. When the this call returns, +// the service has stopped, so exit. +// +void __cdecl main(int argc, char **argv) +{ + SERVICE_TABLE_ENTRY dispatchTable[] = + { + { TEXT(SZSERVICENAME), (LPSERVICE_MAIN_FUNCTION)service_main}, + { NULL, NULL} + }; + + if ( (argc > 1) && + ((*argv[1] == '-') || (*argv[1] == '/')) ) + { + if ( _stricmp( "install", argv[1]+1 ) == 0 ) + { + CmdInstallService(); + } + else if ( _stricmp( "remove", argv[1]+1 ) == 0 ) + { + CmdRemoveService(); + } + else if ( _stricmp( "debug", argv[1]+1 ) == 0 ) + { + bDebug = TRUE; + CmdDebugService(argc, argv); + } + else + { + goto dispatch; + } + exit(0); + } + + // if it doesn't match any of the above parameters + // the service control manager may be starting the service + // so we must call StartServiceCtrlDispatcher + dispatch: + // this is just to be friendly + printf( "%s -install to install the service\n", SZAPPNAME ); + printf( "%s -remove to remove the service\n", SZAPPNAME ); + printf( "%s -debug to run as a console app for debugging\n", SZAPPNAME ); + printf( "\nStartServiceCtrlDispatcher being called.\n" ); + printf( "This may take several seconds. Please wait.\n" ); + + if (!StartServiceCtrlDispatcher(dispatchTable)) + AddToMessageLog(TEXT("StartServiceCtrlDispatcher failed.")); +} + + + +// +// FUNCTION: service_main +// +// PURPOSE: To perform actual initialization of the service +// +// PARAMETERS: +// dwArgc - number of command line arguments +// lpszArgv - array of command line arguments +// +// RETURN VALUE: +// none +// +// COMMENTS: +// This routine performs the service initialization and then calls +// the user defined ServiceStart() routine to perform majority +// of the work. +// +void WINAPI service_main(DWORD dwArgc, LPTSTR *lpszArgv) +{ + + // register our service control handler: + // + sshStatusHandle = RegisterServiceCtrlHandler( TEXT(SZSERVICENAME), service_ctrl); + + if (!sshStatusHandle) + goto cleanup; + + // SERVICE_STATUS members that don't change in example + // + ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + ssStatus.dwServiceSpecificExitCode = 0; + + + // report the status to the service control manager. + // + if (!ReportStatusToSCMgr( + SERVICE_START_PENDING, // service state + NO_ERROR, // exit code + 3000)) // wait hint + goto cleanup; + + + ServiceStart( dwArgc, lpszArgv ); + + cleanup: + + // try to report the stopped status to the service control manager. + // + if (sshStatusHandle) + (VOID)ReportStatusToSCMgr( + SERVICE_STOPPED, + dwErr, + 0); + + return; +} + + + +// +// FUNCTION: service_ctrl +// +// PURPOSE: This function is called by the SCM whenever +// ControlService() is called on this service. +// +// PARAMETERS: +// dwCtrlCode - type of control requested +// +// RETURN VALUE: +// none +// +// COMMENTS: +// +VOID WINAPI service_ctrl(DWORD dwCtrlCode) +{ + // Handle the requested control code. + // + switch (dwCtrlCode) + { + // Stop the service. + // + // SERVICE_STOP_PENDING should be reported before + // setting the Stop Event - hServerStopEvent - in + // ServiceStop(). This avoids a race condition + // which may result in a 1053 - The Service did not respond... + // error. + case SERVICE_CONTROL_STOP: + ReportStatusToSCMgr(SERVICE_STOP_PENDING, NO_ERROR, 0); + ServiceStop(); + return; + + // Update the service status. + // + case SERVICE_CONTROL_INTERROGATE: + break; + + // invalid control code + // + default: + break; + + } + + ReportStatusToSCMgr(ssStatus.dwCurrentState, NO_ERROR, 0); +} + + + +// +// FUNCTION: ReportStatusToSCMgr() +// +// PURPOSE: Sets the current status of the service and +// reports it to the Service Control Manager +// +// PARAMETERS: +// dwCurrentState - the state of the service +// dwWin32ExitCode - error code to report +// dwWaitHint - worst case estimate to next checkpoint +// +// RETURN VALUE: +// TRUE - success +// FALSE - failure +// +// COMMENTS: +// +BOOL ReportStatusToSCMgr(DWORD dwCurrentState, + DWORD dwWin32ExitCode, + DWORD dwWaitHint) +{ + static DWORD dwCheckPoint = 1; + BOOL fResult = TRUE; + + + if ( !bDebug ) // when debugging we don't report to the SCM + { + if (dwCurrentState == SERVICE_START_PENDING) + ssStatus.dwControlsAccepted = 0; + else + ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; + + ssStatus.dwCurrentState = dwCurrentState; + ssStatus.dwWin32ExitCode = dwWin32ExitCode; + ssStatus.dwWaitHint = dwWaitHint; + + if ( ( dwCurrentState == SERVICE_RUNNING ) || + ( dwCurrentState == SERVICE_STOPPED ) ) + ssStatus.dwCheckPoint = 0; + else + ssStatus.dwCheckPoint = dwCheckPoint++; + + + // Report the status of the service to the service control manager. + fResult = SetServiceStatus(sshStatusHandle, &ssStatus); + if (!fResult) + AddToMessageLog(TEXT("SetServiceStatus")); + } + return fResult; +} + + + +// +// FUNCTION: AddToMessageLog(LPTSTR lpszMsg) +// +// PURPOSE: Allows any thread to log an error message +// +// PARAMETERS: +// lpszMsg - text for message +// +// RETURN VALUE: +// none +// +// COMMENTS: +// +VOID AddToMessageLog(LPTSTR lpszMsg) +{ + TCHAR szMsg [(sizeof(SZSERVICENAME) / sizeof(TCHAR)) + 100 ]; + HANDLE hEventSource; + LPTSTR lpszStrings[2]; + + if ( !bDebug ) + { + dwErr = GetLastError(); + + // Use event logging to log the error. + // + hEventSource = RegisterEventSource(NULL, TEXT(SZSERVICENAME)); + +#ifndef __REACTOS__ + _stprintf_s(szMsg,(sizeof(SZSERVICENAME) / sizeof(TCHAR)) + 100, TEXT("%s error: %d"), TEXT(SZSERVICENAME), dwErr); +#else + _sntprintf(szMsg,(sizeof(SZSERVICENAME) / sizeof(TCHAR)) + 100, TEXT("%s error: %d"), TEXT(SZSERVICENAME), dwErr); +#endif + lpszStrings[0] = szMsg; + lpszStrings[1] = lpszMsg; + + if (hEventSource != NULL) + { + ReportEvent(hEventSource, // handle of event source + EVENTLOG_ERROR_TYPE, // event type + 0, // event category + 0, // event ID + NULL, // current user's SID + 2, // strings in lpszStrings + 0, // no bytes of raw data + lpszStrings, // array of error strings + NULL); // no raw data + + (VOID) DeregisterEventSource(hEventSource); + } + } +} + + + + +/////////////////////////////////////////////////////////////////// +// +// The following code handles service installation and removal +// + + +// +// FUNCTION: CmdInstallService() +// +// PURPOSE: Installs the service +// +// PARAMETERS: +// none +// +// RETURN VALUE: +// none +// +// COMMENTS: +// +void CmdInstallService() +{ + SC_HANDLE schService; + SC_HANDLE schSCManager; + + TCHAR szPath[512]; + + if ( GetModuleFileName( NULL, szPath, 512 ) == 0 ) + { + _tprintf(TEXT("Unable to install %s - %s\n"), TEXT(SZSERVICEDISPLAYNAME), GetLastErrorText(szErr, 256)); + return; + } + + schSCManager = OpenSCManager( + NULL, // machine (NULL == local) + NULL, // database (NULL == default) + SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE // access required + ); + if ( schSCManager ) + { + schService = CreateService( + schSCManager, // SCManager database + TEXT(SZSERVICENAME), // name of service + TEXT(SZSERVICEDISPLAYNAME), // name to display + SERVICE_QUERY_STATUS, // desired access + SERVICE_WIN32_OWN_PROCESS, // service type + SERVICE_AUTO_START, // start type + SERVICE_ERROR_NORMAL, // error control type + szPath, // service's binary + NULL, // no load ordering group + NULL, // no tag identifier + TEXT(SZDEPENDENCIES), // dependencies + NULL, // LocalSystem account + NULL); // no password + + if ( schService ) + { + _tprintf(TEXT("%s installed.\n"), TEXT(SZSERVICEDISPLAYNAME) ); + CloseServiceHandle(schService); + } + else + { + _tprintf(TEXT("CreateService failed - %s\n"), GetLastErrorText(szErr, 256)); + } + + CloseServiceHandle(schSCManager); + } + else + _tprintf(TEXT("OpenSCManager failed - %s\n"), GetLastErrorText(szErr,256)); +} + + + +// +// FUNCTION: CmdRemoveService() +// +// PURPOSE: Stops and removes the service +// +// PARAMETERS: +// none +// +// RETURN VALUE: +// none +// +// COMMENTS: +// +void CmdRemoveService() +{ + SC_HANDLE schService; + SC_HANDLE schSCManager; + + schSCManager = OpenSCManager( + NULL, // machine (NULL == local) + NULL, // database (NULL == default) + SC_MANAGER_CONNECT // access required + ); + if ( schSCManager ) + { + schService = OpenService(schSCManager, TEXT(SZSERVICENAME), DELETE | SERVICE_STOP | SERVICE_QUERY_STATUS); + + if (schService) + { + // try to stop the service + if ( ControlService( schService, SERVICE_CONTROL_STOP, &ssStatus ) ) + { + _tprintf(TEXT("Stopping %s."), TEXT(SZSERVICEDISPLAYNAME)); + Sleep( 1000 ); + + while ( QueryServiceStatus( schService, &ssStatus ) ) + { + if ( ssStatus.dwCurrentState == SERVICE_STOP_PENDING ) + { + _tprintf(TEXT(".")); + Sleep( 1000 ); + } + else + break; + } + + if ( ssStatus.dwCurrentState == SERVICE_STOPPED ) + _tprintf(TEXT("\n%s stopped.\n"), TEXT(SZSERVICEDISPLAYNAME) ); + else + _tprintf(TEXT("\n%s failed to stop.\n"), TEXT(SZSERVICEDISPLAYNAME) ); + + } + + // now remove the service + if ( DeleteService(schService) ) + _tprintf(TEXT("%s removed.\n"), TEXT(SZSERVICEDISPLAYNAME) ); + else + _tprintf(TEXT("DeleteService failed - %s\n"), GetLastErrorText(szErr,256)); + + + CloseServiceHandle(schService); + } + else + _tprintf(TEXT("OpenService failed - %s\n"), GetLastErrorText(szErr,256)); + + CloseServiceHandle(schSCManager); + } + else + _tprintf(TEXT("OpenSCManager failed - %s\n"), GetLastErrorText(szErr,256)); +} + + + + +/////////////////////////////////////////////////////////////////// +// +// The following code is for running the service as a console app +// + + +// +// FUNCTION: CmdDebugService(int argc, char ** argv) +// +// PURPOSE: Runs the service as a console application +// +// PARAMETERS: +// argc - number of command line arguments +// argv - array of command line arguments +// +// RETURN VALUE: +// none +// +// COMMENTS: +// +void CmdDebugService(int argc, char ** argv) +{ + DWORD dwArgc; + LPTSTR *lpszArgv; + +#ifdef UNICODE + lpszArgv = CommandLineToArgvW(GetCommandLineW(), &(dwArgc) ); + if (NULL == lpszArgv) + { + // CommandLineToArvW failed!! + _tprintf(TEXT("CmdDebugService CommandLineToArgvW returned NULL\n")); + return; + } +#else + dwArgc = (DWORD) argc; + lpszArgv = argv; +#endif + + _tprintf(TEXT("Debugging %s.\n"), TEXT(SZSERVICEDISPLAYNAME)); + + SetConsoleCtrlHandler( ControlHandler, TRUE ); + + ServiceStart( dwArgc, lpszArgv ); + +#ifdef UNICODE +// Must free memory allocated for arguments + + GlobalFree(lpszArgv); +#endif // UNICODE + +} + + +// +// FUNCTION: ControlHandler ( DWORD dwCtrlType ) +// +// PURPOSE: Handled console control events +// +// PARAMETERS: +// dwCtrlType - type of control event +// +// RETURN VALUE: +// True - handled +// False - unhandled +// +// COMMENTS: +// +BOOL WINAPI ControlHandler ( DWORD dwCtrlType ) +{ + switch ( dwCtrlType ) + { + case CTRL_BREAK_EVENT: // use Ctrl+C or Ctrl+Break to simulate + case CTRL_C_EVENT: // SERVICE_CONTROL_STOP in debug mode + _tprintf(TEXT("Stopping %s.\n"), TEXT(SZSERVICEDISPLAYNAME)); + ServiceStop(); + return TRUE; + break; + + } + return FALSE; +} + +// +// FUNCTION: GetLastErrorText +// +// PURPOSE: copies error message text to string +// +// PARAMETERS: +// lpszBuf - destination buffer +// dwSize - size of buffer +// +// RETURN VALUE: +// destination buffer +// +// COMMENTS: +// +LPTSTR GetLastErrorText( LPTSTR lpszBuf, DWORD dwSize ) +{ + DWORD dwRet; + LPTSTR lpszTemp = NULL; + + dwRet = FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |FORMAT_MESSAGE_ARGUMENT_ARRAY, + NULL, + GetLastError(), + LANG_NEUTRAL, + (LPTSTR)&lpszTemp, + 0, + NULL ); + + // supplied buffer is not long enough + if ( !dwRet || ( (long)dwSize < (long)dwRet+14 ) ) + lpszBuf[0] = TEXT('\0'); + else + { + if (NULL != lpszTemp) + { + lpszTemp[lstrlen(lpszTemp)-2] = TEXT('\0'); //remove cr and newline character +#ifndef __REACTOS__ + _stprintf_s( lpszBuf, dwSize, TEXT("%s (0x%x)"), lpszTemp, GetLastError() ); +#else + _sntprintf( lpszBuf, dwSize, TEXT("%s (0x%x)"), lpszTemp, GetLastError() ); +#endif + } + } + + if ( NULL != lpszTemp ) + LocalFree((HLOCAL) lpszTemp ); + + return lpszBuf; +} +#endif diff --git a/reactos/base/services/nfsd/service.h b/reactos/base/services/nfsd/service.h new file mode 100644 index 00000000000..1bee89bd3a7 --- /dev/null +++ b/reactos/base/services/nfsd/service.h @@ -0,0 +1,137 @@ +/*--------------------------------------------------------------------------- +THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +PARTICULAR PURPOSE. + +Copyright (C) Microsoft Corporation. All rights reserved. + + MODULE: service.h + + Comments: The use of this header file and the accompanying service.c + file simplifies the process of writting a service. You as a developer + simply need to follow the TODO's outlined in this header file, and + implement the ServiceStart() and ServiceStop() functions. + + There is no need to modify the code in service.c. Just add service.c + to your project and link with the following libraries... + + libcmt.lib kernel32.lib advapi.lib shell32.lib + + This code also supports unicode. Be sure to compile both service.c and + and code #include "service.h" with the same Unicode setting. + + Upon completion, your code will have the following command line interface + + -? to display this list + -install to install the service + -remove to remove the service + -debug to run as a console app for debugging + + Note: This code also implements Ctrl+C and Ctrl+Break handlers + when using the debug option. These console events cause + your ServiceStop routine to be called + + Also, this code only handles the OWN_SERVICE service type + running in the LOCAL_SYSTEM security context. + + To control your service ( start, stop, etc ) you may use the + Services control panel applet or the NET.EXE program. + + To aid in writing/debugging service, the + SDK contains a utility (MSTOOLS\BIN\SC.EXE) that + can be used to control, configure, or obtain service status. + SC displays complete status for any service/driver + in the service database, and allows any of the configuration + parameters to be easily changed at the command line. + For more information on SC.EXE, type SC at the command line. + + +------------------------------------------------------------------------------*/ + +#ifndef _SERVICE_H +#define _SERVICE_H + + +#ifdef __cplusplus +extern "C" { +#endif + + +////////////////////////////////////////////////////////////////////////////// +//// todo: change to desired strings +//// +// name of the executable +#define SZAPPNAME "nfsd" +// internal name of the service +#define SZSERVICENAME "pnfs" +// displayed name of the service +#define SZSERVICEDISPLAYNAME "NFSv4.1 Client" +// list of service dependencies - "dep1\0dep2\0\0" +#define SZDEPENDENCIES "" +////////////////////////////////////////////////////////////////////////////// + + + +////////////////////////////////////////////////////////////////////////////// +//// todo: ServiceStart()must be defined by in your code. +//// The service should use ReportStatusToSCMgr to indicate +//// progress. This routine must also be used by StartService() +//// to report to the SCM when the service is running. +//// +//// If a ServiceStop procedure is going to take longer than +//// 3 seconds to execute, it should spawn a thread to +//// execute the stop code, and return. Otherwise, the +//// ServiceControlManager will believe that the service has +//// stopped responding +//// + VOID ServiceStart(DWORD dwArgc, LPTSTR *lpszArgv); + VOID ServiceStop(); +////////////////////////////////////////////////////////////////////////////// + + + +////////////////////////////////////////////////////////////////////////////// +//// The following are procedures which +//// may be useful to call within the above procedures, +//// but require no implementation by the user. +//// They are implemented in service.c + +// +// FUNCTION: ReportStatusToSCMgr() +// +// PURPOSE: Sets the current status of the service and +// reports it to the Service Control Manager +// +// PARAMETERS: +// dwCurrentState - the state of the service +// dwWin32ExitCode - error code to report +// dwWaitHint - worst case estimate to next checkpoint +// +// RETURN VALUE: +// TRUE - success +// FALSE - failure +// + BOOL ReportStatusToSCMgr(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint); + + +// +// FUNCTION: AddToMessageLog(LPTSTR lpszMsg) +// +// PURPOSE: Allows any thread to log an error message +// +// PARAMETERS: +// lpszMsg - text for message +// +// RETURN VALUE: +// none +// + void AddToMessageLog(LPTSTR lpszMsg); +////////////////////////////////////////////////////////////////////////////// + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/reactos/base/services/nfsd/setattr.c b/reactos/base/services/nfsd/setattr.c new file mode 100644 index 00000000000..ea4b0588902 --- /dev/null +++ b/reactos/base/services/nfsd/setattr.c @@ -0,0 +1,526 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include +#include +#include + +#include "from_kernel.h" +#include "nfs41_ops.h" +#include "delegation.h" +#include "name_cache.h" +#include "upcall.h" +#include "util.h" +#include "daemon_debug.h" + + +/* NFS41_FILE_SET */ +static int parse_setattr(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall) +{ + int status; + setattr_upcall_args *args = &upcall->args.setattr; + + status = get_name(&buffer, &length, &args->path); + if (status) goto out; + status = safe_read(&buffer, &length, &args->set_class, sizeof(args->set_class)); + if (status) goto out; + status = safe_read(&buffer, &length, &args->buf_len, sizeof(args->buf_len)); + if (status) goto out; + + args->buf = buffer; + args->root = upcall->root_ref; + args->state = upcall->state_ref; + + dprintf(1, "parsing NFS41_FILE_SET: filename='%s' info_class=%d " + "buf_len=%d\n", args->path, args->set_class, args->buf_len); +out: + return status; +} + +static int handle_nfs41_setattr(setattr_upcall_args *args) +{ + PFILE_BASIC_INFO basic_info = (PFILE_BASIC_INFO)args->buf; + nfs41_open_state *state = args->state; + nfs41_superblock *superblock = state->file.fh.superblock; + stateid_arg stateid; + nfs41_file_info info = { 0 }, old_info = { 0 }; + int status = NO_ERROR, getattr_status; + + if (basic_info->FileAttributes) { + info.hidden = basic_info->FileAttributes & FILE_ATTRIBUTE_HIDDEN ? 1 : 0; + info.system = basic_info->FileAttributes & FILE_ATTRIBUTE_SYSTEM ? 1 : 0; + info.archive = basic_info->FileAttributes & FILE_ATTRIBUTE_ARCHIVE ? 1 : 0; + getattr_status = nfs41_attr_cache_lookup(session_name_cache(state->session), + state->file.fh.fileid, &old_info); + + if (getattr_status || info.hidden != old_info.hidden) { + info.attrmask.arr[0] = FATTR4_WORD0_HIDDEN; + info.attrmask.count = 1; + } + if (getattr_status || info.archive != old_info.archive) { + info.attrmask.arr[0] |= FATTR4_WORD0_ARCHIVE; + info.attrmask.count = 1; + } + if (getattr_status || info.system != old_info.system) { + info.attrmask.arr[1] = FATTR4_WORD1_SYSTEM; + info.attrmask.count = 2; + } + } + if (old_info.mode == 0444 && + ((basic_info->FileAttributes & FILE_ATTRIBUTE_READONLY) == 0)) { + info.mode = 0644; + info.attrmask.arr[1] |= FATTR4_WORD1_MODE; + info.attrmask.count = 2; + } + + if (superblock->cansettime) { + /* set the time_delta so xdr_settime4() can decide + * whether or not to use SET_TO_SERVER_TIME4 */ + info.time_delta = &superblock->time_delta; + + /* time_create */ + if (basic_info->CreationTime.QuadPart > 0) { + file_time_to_nfs_time(&basic_info->CreationTime, + &info.time_create); + info.attrmask.arr[1] |= FATTR4_WORD1_TIME_CREATE; + info.attrmask.count = 2; + } + /* time_access_set */ + if (basic_info->LastAccessTime.QuadPart > 0) { + file_time_to_nfs_time(&basic_info->LastAccessTime, + &info.time_access); + info.attrmask.arr[1] |= FATTR4_WORD1_TIME_ACCESS_SET; + info.attrmask.count = 2; + } + /* time_modify_set */ + if (basic_info->LastWriteTime.QuadPart > 0) { + file_time_to_nfs_time(&basic_info->LastWriteTime, + &info.time_modify); + info.attrmask.arr[1] |= FATTR4_WORD1_TIME_MODIFY_SET; + info.attrmask.count = 2; + } + } + + /* mode */ + if (basic_info->FileAttributes & FILE_ATTRIBUTE_READONLY) { + info.mode = 0444; + info.attrmask.arr[1] |= FATTR4_WORD1_MODE; + info.attrmask.count = 2; + } + + /* mask out unsupported attributes */ + nfs41_superblock_supported_attrs(superblock, &info.attrmask); + + if (!info.attrmask.count) + goto out; + + /* break read delegations before SETATTR */ + nfs41_delegation_return(state->session, &state->file, + OPEN_DELEGATE_READ, FALSE); + + nfs41_open_stateid_arg(state, &stateid); + + status = nfs41_setattr(state->session, &state->file, &stateid, &info); + if (status) { + dprintf(1, "nfs41_setattr() failed with error %s.\n", + nfs_error_string(status)); + status = nfs_to_windows_error(status, ERROR_NOT_SUPPORTED); + } + args->ctime = info.change; +out: + return status; +} + +static int handle_nfs41_remove(setattr_upcall_args *args) +{ + nfs41_open_state *state = args->state; + int status; + + /* break any delegations and truncate before REMOVE */ + nfs41_delegation_return(state->session, &state->file, + OPEN_DELEGATE_WRITE, TRUE); + + status = nfs41_remove(state->session, &state->parent, + &state->file.name, state->file.fh.fileid); + if (status) + dprintf(1, "nfs41_remove() failed with error %s.\n", + nfs_error_string(status)); + + return nfs_to_windows_error(status, ERROR_ACCESS_DENIED); +} + +static void open_state_rename( + OUT nfs41_open_state *state, + IN const nfs41_abs_path *path) +{ + AcquireSRWLockExclusive(&state->path.lock); + + abs_path_copy(&state->path, path); + last_component(state->path.path, state->path.path + state->path.len, + &state->file.name); + last_component(state->path.path, state->file.name.name, + &state->parent.name); + + ReleaseSRWLockExclusive(&state->path.lock); +} + +static int nfs41_abs_path_compare( + IN const struct list_entry *entry, + IN const void *value) +{ + nfs41_open_state *client = list_container(entry, nfs41_open_state, client_entry); + const nfs41_abs_path *name = (const nfs41_abs_path *)value; + if (client->path.len == name->len && + !strncmp(client->path.path, name->path, client->path.len)) + return NO_ERROR; + return ERROR_FILE_NOT_FOUND; +} + +static int is_dst_name_opened(nfs41_abs_path *dst_path, nfs41_session *dst_session) +{ + int status; + nfs41_client *client = dst_session->client; + + EnterCriticalSection(&client->state.lock); + if (list_search(&client->state.opens, dst_path, nfs41_abs_path_compare)) + status = TRUE; + else + status = FALSE; + LeaveCriticalSection(&client->state.lock); + + return status; +} +static int handle_nfs41_rename(setattr_upcall_args *args) +{ + nfs41_open_state *state = args->state; + nfs41_session *dst_session; + PFILE_RENAME_INFO rename = (PFILE_RENAME_INFO)args->buf; + nfs41_abs_path dst_path = { 0 }; + nfs41_path_fh dst_dir, dst; + nfs41_component dst_name, *src_name; + uint32_t depth = 0; + int status; + + src_name = &state->file.name; + + if (rename->FileNameLength == 0) { + /* start from state->path instead of args->path, in case we got + * the file from a referred server */ + AcquireSRWLockShared(&state->path.lock); + abs_path_copy(&dst_path, &state->path); + ReleaseSRWLockShared(&state->path.lock); + + path_fh_init(&dst_dir, &dst_path); + fh_copy(&dst_dir.fh, &state->parent.fh); + + create_silly_rename(&dst_path, &state->file.fh, &dst_name); + dprintf(1, "silly rename: %s -> %s\n", src_name->name, dst_name.name); + + /* break any delegations and truncate before silly rename */ + nfs41_delegation_return(state->session, &state->file, + OPEN_DELEGATE_WRITE, TRUE); + + status = nfs41_rename(state->session, + &state->parent, src_name, + &dst_dir, &dst_name); + if (status) { + dprintf(1, "nfs41_rename() failed with error %s.\n", + nfs_error_string(status)); + status = nfs_to_windows_error(status, ERROR_ACCESS_DENIED); + } else { + /* rename state->path on success */ + open_state_rename(state, &dst_path); + } + goto out; + } + + dst_path.len = (unsigned short)WideCharToMultiByte(CP_UTF8, 0, + rename->FileName, rename->FileNameLength/sizeof(WCHAR), + dst_path.path, NFS41_MAX_PATH_LEN, NULL, NULL); + if (dst_path.len == 0) { + eprintf("WideCharToMultiByte failed to convert destination " + "filename %S.\n", rename->FileName); + status = ERROR_INVALID_PARAMETER; + goto out; + } + path_fh_init(&dst_dir, &dst_path); + + /* the destination path is absolute, so start from the root session */ + status = nfs41_lookup(args->root, nfs41_root_session(args->root), + &dst_path, &dst_dir, &dst, NULL, &dst_session); + + while (status == ERROR_REPARSE) { + if (++depth > NFS41_MAX_SYMLINK_DEPTH) { + status = ERROR_TOO_MANY_LINKS; + goto out; + } + + /* replace the path with the symlink target's */ + status = nfs41_symlink_target(dst_session, &dst_dir, &dst_path); + if (status) { + eprintf("nfs41_symlink_target() for %s failed with %d\n", + dst_dir.path->path, status); + goto out; + } + + /* redo the lookup until it doesn't return REPARSE */ + status = nfs41_lookup(args->root, dst_session, + &dst_path, &dst_dir, NULL, NULL, &dst_session); + } + + /* get the components after lookup in case a referral changed its path */ + last_component(dst_path.path, dst_path.path + dst_path.len, &dst_name); + last_component(dst_path.path, dst_name.name, &dst_dir.name); + + if (status == NO_ERROR) { + if (!rename->ReplaceIfExists) { + status = ERROR_FILE_EXISTS; + goto out; + } + /* break any delegations and truncate the destination file */ + nfs41_delegation_return(dst_session, &dst, + OPEN_DELEGATE_WRITE, TRUE); + } else if (status != ERROR_FILE_NOT_FOUND) { + dprintf(1, "nfs41_lookup('%s') failed to find destination " + "directory with %d\n", dst_path.path, status); + goto out; + } + + /* http://tools.ietf.org/html/rfc5661#section-18.26.3 + * "Source and target directories MUST reside on the same + * file system on the server." */ + if (state->parent.fh.superblock != dst_dir.fh.superblock) { + status = ERROR_NOT_SAME_DEVICE; + goto out; + } + + status = is_dst_name_opened(&dst_path, dst_session); + if (status) { + /* AGLO: 03/21/2011: we can't handle rename of a file with a filename + * that is currently opened by this client + */ + eprintf("handle_nfs41_rename: %s is opened\n", dst_path.path); + status = ERROR_FILE_EXISTS; + goto out; + } + + /* break any delegations on the source file */ + nfs41_delegation_return(state->session, &state->file, + OPEN_DELEGATE_WRITE, FALSE); + + status = nfs41_rename(state->session, + &state->parent, src_name, + &dst_dir, &dst_name); + if (status) { + dprintf(1, "nfs41_rename() failed with error %s.\n", + nfs_error_string(status)); + status = nfs_to_windows_error(status, ERROR_ACCESS_DENIED); + } else { + /* rename state->path on success */ + open_state_rename(state, &dst_path); + } +out: + return status; +} + +static int handle_nfs41_set_size(setattr_upcall_args *args) +{ + nfs41_file_info info = { 0 }; + stateid_arg stateid; + /* note: this is called with either FILE_END_OF_FILE_INFO or + * FILE_ALLOCATION_INFO, both of which contain a single LARGE_INTEGER */ + PLARGE_INTEGER size = (PLARGE_INTEGER)args->buf; + nfs41_open_state *state = args->state; + int status; + + /* break read delegations before SETATTR */ + nfs41_delegation_return(state->session, &state->file, + OPEN_DELEGATE_READ, FALSE); + + nfs41_open_stateid_arg(state, &stateid); + + info.size = size->QuadPart; + info.attrmask.count = 1; + info.attrmask.arr[0] = FATTR4_WORD0_SIZE; + + dprintf(2, "calling setattr() with size=%lld\n", info.size); + status = nfs41_setattr(state->session, &state->file, &stateid, &info); + if (status) { + dprintf(1, "nfs41_setattr() failed with error %s.\n", + nfs_error_string(status)); + goto out; + } + + /* update the last offset for LAYOUTCOMMIT */ + AcquireSRWLockExclusive(&state->lock); + state->pnfs_last_offset = info.size ? info.size - 1 : 0; + ReleaseSRWLockExclusive(&state->lock); + args->ctime = info.change; +out: + return status = nfs_to_windows_error(status, ERROR_NOT_SUPPORTED); +} + +static int handle_nfs41_link(setattr_upcall_args *args) +{ + nfs41_open_state *state = args->state; + PFILE_LINK_INFORMATION link = (PFILE_LINK_INFORMATION)args->buf; + nfs41_session *dst_session; + nfs41_abs_path dst_path = { 0 }; + nfs41_path_fh dst_dir, dst; + nfs41_component dst_name; + uint32_t depth = 0; + nfs41_file_info info = { 0 }; + int status; + + dst_path.len = (unsigned short)WideCharToMultiByte(CP_UTF8, 0, + link->FileName, link->FileNameLength/sizeof(WCHAR), + dst_path.path, NFS41_MAX_PATH_LEN, NULL, NULL); + if (dst_path.len == 0) { + eprintf("WideCharToMultiByte failed to convert destination " + "filename %S.\n", link->FileName); + status = ERROR_INVALID_PARAMETER; + goto out; + } + path_fh_init(&dst_dir, &dst_path); + + /* the destination path is absolute, so start from the root session */ + status = nfs41_lookup(args->root, nfs41_root_session(args->root), + &dst_path, &dst_dir, &dst, NULL, &dst_session); + + while (status == ERROR_REPARSE) { + if (++depth > NFS41_MAX_SYMLINK_DEPTH) { + status = ERROR_TOO_MANY_LINKS; + goto out; + } + + /* replace the path with the symlink target's */ + status = nfs41_symlink_target(dst_session, &dst_dir, &dst_path); + if (status) { + eprintf("nfs41_symlink_target() for %s failed with %d\n", + dst_dir.path->path, status); + goto out; + } + + /* redo the lookup until it doesn't return REPARSE */ + status = nfs41_lookup(args->root, dst_session, + &dst_path, &dst_dir, &dst, NULL, &dst_session); + } + + /* get the components after lookup in case a referral changed its path */ + last_component(dst_path.path, dst_path.path + dst_path.len, &dst_name); + last_component(dst_path.path, dst_name.name, &dst_dir.name); + + if (status == NO_ERROR) { + if (!link->ReplaceIfExists) { + status = ERROR_FILE_EXISTS; + goto out; + } + } else if (status != ERROR_FILE_NOT_FOUND) { + dprintf(1, "nfs41_lookup('%s') failed to find destination " + "directory with %d\n", dst_path.path, status); + goto out; + } + + /* http://tools.ietf.org/html/rfc5661#section-18.9.3 + * "The existing file and the target directory must reside within + * the same file system on the server." */ + if (state->file.fh.superblock != dst_dir.fh.superblock) { + status = ERROR_NOT_SAME_DEVICE; + goto out; + } + + if (status == NO_ERROR) { + /* break any delegations and truncate the destination file */ + nfs41_delegation_return(dst_session, &dst, + OPEN_DELEGATE_WRITE, TRUE); + + /* LINK will return NFS4ERR_EXIST if the target file exists, + * so we have to remove it ourselves */ + status = nfs41_remove(state->session, + &dst_dir, &dst_name, dst.fh.fileid); + if (status) { + dprintf(1, "nfs41_remove() failed with error %s.\n", + nfs_error_string(status)); + status = ERROR_FILE_EXISTS; + goto out; + } + } + + /* break read delegations on the source file */ + nfs41_delegation_return(state->session, &state->file, + OPEN_DELEGATE_READ, FALSE); + + status = nfs41_link(state->session, &state->file, &dst_dir, &dst_name, + &info); + if (status) { + dprintf(1, "nfs41_link() failed with error %s.\n", + nfs_error_string(status)); + status = nfs_to_windows_error(status, ERROR_INVALID_PARAMETER); + } + args->ctime = info.change; +out: + return status; +} + +static int handle_setattr(nfs41_upcall *upcall) +{ + setattr_upcall_args *args = &upcall->args.setattr; + int status; + + switch (args->set_class) { + case FileBasicInformation: + status = handle_nfs41_setattr(args); + break; + case FileDispositionInformation: + status = handle_nfs41_remove(args); + break; + case FileRenameInformation: + status = handle_nfs41_rename(args); + break; + case FileAllocationInformation: + case FileEndOfFileInformation: + status = handle_nfs41_set_size(args); + break; + case FileLinkInformation: + status = handle_nfs41_link(args); + break; + default: + eprintf("unknown set_file information class %d\n", + args->set_class); + status = ERROR_NOT_SUPPORTED; + break; + } + + return status; +} + +static int marshall_setattr(unsigned char *buffer, uint32_t *length, nfs41_upcall *upcall) +{ + setattr_upcall_args *args = &upcall->args.setattr; + return safe_write(&buffer, length, &args->ctime, sizeof(args->ctime)); +} + + +const nfs41_upcall_op nfs41_op_setattr = { + parse_setattr, + handle_setattr, + marshall_setattr +}; diff --git a/reactos/base/services/nfsd/sources b/reactos/base/services/nfsd/sources new file mode 100644 index 00000000000..af4eef92ae0 --- /dev/null +++ b/reactos/base/services/nfsd/sources @@ -0,0 +1,32 @@ +TARGETTYPE=PROGRAM +TARGETNAME=nfsd +SOURCES=nfs41_daemon.c daemon_debug.c nfs41_ops.c nfs41_compound.c nfs41_xdr.c \ + nfs41_server.c nfs41_client.c nfs41_superblock.c nfs41_session.c lookup.c \ + mount.c open.c readwrite.c lock.c readdir.c getattr.c setattr.c upcall.c \ + nfs41_rpc.c util.c pnfs_layout.c pnfs_device.c pnfs_debug.c pnfs_io.c \ + name_cache.c namespace.c rbtree.c volume.c callback_server.c callback_xdr.c \ + service.c symlink.c idmap.c +UMTYPE=console +USE_LIBCMT=1 +#USE_MSVCRT=1 +C_DEFINES=-DSTANDALONE_NFSD #-- use this for non-service nfsd +INCLUDES=..\sys;..\dll;..\libtirpc\tirpc +# Use the following for "service" version of nfsd +#TARGETLIBS=$(SDK_LIB_PATH)\ws2_32.lib $(SDK_LIB_PATH)\iphlpapi.lib \ +# ..\libtirpc\src\obj$(BUILD_ALT_DIR)\*\libtirpc.lib \ +# $(SDK_LIB_PATH)\kernel32.lib \ +# $(SDK_LIB_PATH)\advapi32.lib \ +# $(SDK_LIB_PATH)\shell32.lib +TARGETLIBS=$(SDK_LIB_PATH)\ws2_32.lib $(SDK_LIB_PATH)\iphlpapi.lib \ + ..\libtirpc\src\obj$(BUILD_ALT_DIR)\*\libtirpc.lib + +!IF 0 +/W3 is default level +bump to /Wall, but suppress warnings generated by system includes, +as well as the following warnings: +4100 - unused function call arguments (we have lots of stubs) +4127 - constant conditional (I like to use if(0) or if(1)) +4220 - varargs matching remaining parameters +4204 - nonstandard extension +!ENDIF +MSC_WARNING_LEVEL=/Wall /wd4668 /wd4619 /wd4820 /wd4255 /wd4100 /wd4127 /wd4711 /wd4220 /wd4204 diff --git a/reactos/base/services/nfsd/symlink.c b/reactos/base/services/nfsd/symlink.c new file mode 100644 index 00000000000..53b6f7b3da7 --- /dev/null +++ b/reactos/base/services/nfsd/symlink.c @@ -0,0 +1,299 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include +#include + +#include "nfs41_ops.h" +#include "upcall.h" +#include "util.h" +#include "daemon_debug.h" + + +static int abs_path_link( + OUT nfs41_abs_path *path, + IN char *path_pos, + IN const char *link, + IN uint32_t link_len) +{ + nfs41_component name; + const char *path_max = path->path + NFS41_MAX_PATH_LEN; + const char *link_pos = link; + const char *link_end = link + link_len; + int status = NO_ERROR; + + /* if link is an absolute path, start path_pos at the beginning */ + if (is_delimiter(*link)) + path_pos = path->path; + + /* copy each component of link into the path */ + while (next_component(link_pos, link_end, &name)) { + link_pos = name.name + name.len; + + if (is_delimiter(*path_pos)) + path_pos++; + + /* handle special components . and .. */ + if (name.len == 1 && name.name[0] == '.') + continue; + if (name.len == 2 && name.name[0] == '.' && name.name[1] == '.') { + /* back path_pos up by one component */ + if (!last_component(path->path, path_pos, &name)) { + eprintf("symlink with .. that points below server root!\n"); + status = ERROR_BAD_NETPATH; + goto out; + } + path_pos = (char*)prev_delimiter(name.name, path->path); + continue; + } + + /* copy the component and add a \ */ + if (FAILED(StringCchCopyNA(path_pos, path_max-path_pos, name.name, + name.len))) { + status = ERROR_BUFFER_OVERFLOW; + goto out; + } + path_pos += name.len; + if (FAILED(StringCchCopyNA(path_pos, path_max-path_pos, "\\", 1))) { + status = ERROR_BUFFER_OVERFLOW; + goto out; + } + } + + /* make sure the path is null terminated */ + if (path_pos == path_max) { + status = ERROR_BUFFER_OVERFLOW; + goto out; + } + *path_pos = '\0'; +out: + path->len = (unsigned short)(path_pos - path->path); + return status; +} + +int nfs41_symlink_target( + IN nfs41_session *session, + IN nfs41_path_fh *file, + OUT nfs41_abs_path *target) +{ + char link[NFS41_MAX_PATH_LEN]; + const nfs41_abs_path *path = file->path; + ptrdiff_t path_offset; + uint32_t link_len; + int status; + + /* read the link */ + status = nfs41_readlink(session, file, NFS41_MAX_PATH_LEN, link, &link_len); + if (status) { + eprintf("nfs41_readlink() for %s failed with %s\n", file->path->path, + nfs_error_string(status)); + status = ERROR_PATH_NOT_FOUND; + goto out; + } + + dprintf(2, "--> nfs41_symlink_target('%s', '%s')\n", path->path, link); + + /* append any components after the symlink */ + if (FAILED(StringCchCatA(link, NFS41_MAX_PATH_LEN, + file->name.name + file->name.len))) { + status = ERROR_BUFFER_OVERFLOW; + goto out; + } + link_len = (uint32_t)strlen(link); + + /* overwrite the last component of the path; get the starting offset */ + path_offset = file->name.name - path->path; + + /* copy the path and update it with the results from link */ + if (target != path) { + target->len = path->len; + if (FAILED(StringCchCopyNA(target->path, NFS41_MAX_PATH_LEN, + path->path, path->len))) { + status = ERROR_BUFFER_OVERFLOW; + goto out; + } + } + status = abs_path_link(target, target->path + path_offset, link, link_len); + if (status) { + eprintf("abs_path_link() for path %s with link %s failed with %d\n", + target->path, link, status); + goto out; + } +out: + dprintf(2, "<-- nfs41_symlink_target('%s') returning %d\n", + target->path, status); + return status; +} + +int nfs41_symlink_follow( + IN nfs41_root *root, + IN nfs41_session *session, + IN nfs41_path_fh *symlink, + OUT nfs41_file_info *info) +{ + nfs41_abs_path path; + nfs41_path_fh file; + uint32_t depth = 0; + int status = NO_ERROR; + + file.path = &path; + InitializeSRWLock(&path.lock); + + dprintf(2, "--> nfs41_symlink_follow('%s')\n", symlink->path->path); + + do { + if (++depth > NFS41_MAX_SYMLINK_DEPTH) { + status = ERROR_TOO_MANY_LINKS; + goto out; + } + + /* construct the target path */ + status = nfs41_symlink_target(session, symlink, &path); + if (status) goto out; + + dprintf(2, "looking up '%s'\n", path.path); + + last_component(path.path, path.path + path.len, &file.name); + + /* get attributes for the target */ + status = nfs41_lookup(root, session, &path, + NULL, &file, info, &session); + if (status) goto out; + + symlink = &file; + } while (info->type == NF4LNK); +out: + dprintf(2, "<-- nfs41_symlink_follow() returning %d\n", status); + return status; +} + + +/* NFS41_SYMLINK */ +static int parse_symlink(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall) +{ + symlink_upcall_args *args = &upcall->args.symlink; + int status; + + status = get_name(&buffer, &length, &args->path); + if (status) goto out; + status = safe_read(&buffer, &length, &args->set, sizeof(BOOLEAN)); + if (status) goto out; + + if (args->set) + status = get_name(&buffer, &length, &args->target_set); + else + args->target_set = NULL; + + dprintf(1, "parsing NFS41_SYMLINK: path='%s' set=%u target='%s'\n", + args->path, args->set, args->target_set); +out: + return status; +} + +static int handle_symlink(nfs41_upcall *upcall) +{ + symlink_upcall_args *args = &upcall->args.symlink; + nfs41_open_state *state = upcall->state_ref; + int status = NO_ERROR; + + if (args->set) { + nfs41_file_info info, createattrs; + + /* don't send windows slashes to the server */ + char *p; + for (p = args->target_set; *p; p++) if (*p == '\\') *p = '/'; + + if (state->file.fh.len) { + /* the check in handle_open() didn't catch that we're creating + * a symlink, so we have to remove the file it already created */ + eprintf("handle_symlink: attempting to create a symlink when " + "the file=%s was already created on open; sending REMOVE " + "first\n", state->file.path->path); + status = nfs41_remove(state->session, &state->parent, + &state->file.name, state->file.fh.fileid); + if (status) { + eprintf("nfs41_remove() for symlink=%s failed with %s\n", + args->target_set, nfs_error_string(status)); + status = map_symlink_errors(status); + goto out; + } + } + + /* create the symlink */ + createattrs.attrmask.count = 2; + createattrs.attrmask.arr[0] = 0; + createattrs.attrmask.arr[1] = FATTR4_WORD1_MODE; + createattrs.mode = 0777; + status = nfs41_create(state->session, NF4LNK, &createattrs, + args->target_set, &state->parent, &state->file, &info); + if (status) { + eprintf("nfs41_create() for symlink=%s failed with %s\n", + args->target_set, nfs_error_string(status)); + status = map_symlink_errors(status); + goto out; + } + } else { + uint32_t len; + + /* read the link */ + status = nfs41_readlink(state->session, &state->file, + NFS41_MAX_PATH_LEN, args->target_get.path, &len); + if (status) { + eprintf("nfs41_readlink() for filename=%s failed with %s\n", + state->file.path->path, nfs_error_string(status)); + status = map_symlink_errors(status); + goto out; + } + args->target_get.len = (unsigned short)len; + dprintf(2, "returning symlink target '%s'\n", args->target_get.path); + } +out: + return status; +} + +static int marshall_symlink(unsigned char *buffer, uint32_t *length, nfs41_upcall *upcall) +{ + symlink_upcall_args *args = &upcall->args.symlink; + unsigned short len = (args->target_get.len + 1) * sizeof(WCHAR); + int status = NO_ERROR; + + if (args->set) + goto out; + + status = safe_write(&buffer, length, &len, sizeof(len)); + if (status) goto out; + + if (*length <= len || !MultiByteToWideChar(CP_UTF8, 0, + args->target_get.path, args->target_get.len, + (LPWSTR)buffer, len / sizeof(WCHAR))) { + status = ERROR_BUFFER_OVERFLOW; + goto out; + } +out: + return status; +} + + +const nfs41_upcall_op nfs41_op_symlink = { + parse_symlink, + handle_symlink, + marshall_symlink +}; diff --git a/reactos/base/services/nfsd/tree.h b/reactos/base/services/nfsd/tree.h new file mode 100644 index 00000000000..5a1bbd5ad4a --- /dev/null +++ b/reactos/base/services/nfsd/tree.h @@ -0,0 +1,765 @@ +/* $NetBSD: tree.h,v 1.8 2004/03/28 19:38:30 provos Exp $ */ +/* $OpenBSD: tree.h,v 1.7 2002/10/17 21:51:54 art Exp $ */ +/* $FreeBSD: src/sys/sys/tree.h,v 1.9.2.1.4.1 2010/06/14 02:09:06 kensmith Exp $ */ + +/*- + * Copyright 2002 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _SYS_TREE_H_ +#define _SYS_TREE_H_ + +//#include + +/* + * This file defines data structures for different types of trees: + * splay trees and red-black trees. + * + * A splay tree is a self-organizing data structure. Every operation + * on the tree causes a splay to happen. The splay moves the requested + * node to the root of the tree and partly rebalances it. + * + * This has the benefit that request locality causes faster lookups as + * the requested nodes move to the top of the tree. On the other hand, + * every lookup causes memory writes. + * + * The Balance Theorem bounds the total access time for m operations + * and n inserts on an initially empty tree as O((m + n)lg n). The + * amortized cost for a sequence of m accesses to a splay tree is O(lg n); + * + * A red-black tree is a binary search tree with the node color as an + * extra attribute. It fulfills a set of conditions: + * - every search path from the root to a leaf consists of the + * same number of black nodes, + * - each red node (except for the root) has a black parent, + * - each leaf node is black. + * + * Every operation on a red-black tree is bounded as O(lg n). + * The maximum height of a red-black tree is 2lg (n+1). + */ + +#define SPLAY_HEAD(name, type) \ +struct name { \ + struct type *sph_root; /* root of the tree */ \ +} + +#define SPLAY_INITIALIZER(root) \ + { NULL } + +#define SPLAY_INIT(root) do { \ + (root)->sph_root = NULL; \ +} while (/*CONSTCOND*/ 0) + +#define SPLAY_ENTRY(type) \ +struct { \ + struct type *spe_left; /* left element */ \ + struct type *spe_right; /* right element */ \ +} + +#define SPLAY_LEFT(elm, field) (elm)->field.spe_left +#define SPLAY_RIGHT(elm, field) (elm)->field.spe_right +#define SPLAY_ROOT(head) (head)->sph_root +#define SPLAY_EMPTY(head) (SPLAY_ROOT(head) == NULL) + +/* SPLAY_ROTATE_{LEFT,RIGHT} expect that tmp hold SPLAY_{RIGHT,LEFT} */ +#define SPLAY_ROTATE_RIGHT(head, tmp, field) do { \ + SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(tmp, field); \ + SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ + (head)->sph_root = tmp; \ +} while (/*CONSTCOND*/ 0) + +#define SPLAY_ROTATE_LEFT(head, tmp, field) do { \ + SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(tmp, field); \ + SPLAY_LEFT(tmp, field) = (head)->sph_root; \ + (head)->sph_root = tmp; \ +} while (/*CONSTCOND*/ 0) + +#define SPLAY_LINKLEFT(head, tmp, field) do { \ + SPLAY_LEFT(tmp, field) = (head)->sph_root; \ + tmp = (head)->sph_root; \ + (head)->sph_root = SPLAY_LEFT((head)->sph_root, field); \ +} while (/*CONSTCOND*/ 0) + +#define SPLAY_LINKRIGHT(head, tmp, field) do { \ + SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ + tmp = (head)->sph_root; \ + (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field); \ +} while (/*CONSTCOND*/ 0) + +#define SPLAY_ASSEMBLE(head, node, left, right, field) do { \ + SPLAY_RIGHT(left, field) = SPLAY_LEFT((head)->sph_root, field); \ + SPLAY_LEFT(right, field) = SPLAY_RIGHT((head)->sph_root, field);\ + SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(node, field); \ + SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(node, field); \ +} while (/*CONSTCOND*/ 0) + +/* Generates prototypes and inline functions */ + +#define SPLAY_PROTOTYPE(name, type, field, cmp) \ +void name##_SPLAY(struct name *, struct type *); \ +void name##_SPLAY_MINMAX(struct name *, int); \ +struct type *name##_SPLAY_INSERT(struct name *, struct type *); \ +struct type *name##_SPLAY_REMOVE(struct name *, struct type *); \ + \ +/* Finds the node with the same key as elm */ \ +static __inline struct type * \ +name##_SPLAY_FIND(struct name *head, struct type *elm) \ +{ \ + if (SPLAY_EMPTY(head)) \ + return(NULL); \ + name##_SPLAY(head, elm); \ + if ((cmp)(elm, (head)->sph_root) == 0) \ + return (head->sph_root); \ + return (NULL); \ +} \ + \ +static __inline struct type * \ +name##_SPLAY_NEXT(struct name *head, struct type *elm) \ +{ \ + name##_SPLAY(head, elm); \ + if (SPLAY_RIGHT(elm, field) != NULL) { \ + elm = SPLAY_RIGHT(elm, field); \ + while (SPLAY_LEFT(elm, field) != NULL) { \ + elm = SPLAY_LEFT(elm, field); \ + } \ + } else \ + elm = NULL; \ + return (elm); \ +} \ + \ +static __inline struct type * \ +name##_SPLAY_MIN_MAX(struct name *head, int val) \ +{ \ + name##_SPLAY_MINMAX(head, val); \ + return (SPLAY_ROOT(head)); \ +} + +/* Main splay operation. + * Moves node close to the key of elm to top + */ +#define SPLAY_GENERATE(name, type, field, cmp) \ +struct type * \ +name##_SPLAY_INSERT(struct name *head, struct type *elm) \ +{ \ + if (SPLAY_EMPTY(head)) { \ + SPLAY_LEFT(elm, field) = SPLAY_RIGHT(elm, field) = NULL; \ + } else { \ + int __comp; \ + name##_SPLAY(head, elm); \ + __comp = (cmp)(elm, (head)->sph_root); \ + if(__comp < 0) { \ + SPLAY_LEFT(elm, field) = SPLAY_LEFT((head)->sph_root, field);\ + SPLAY_RIGHT(elm, field) = (head)->sph_root; \ + SPLAY_LEFT((head)->sph_root, field) = NULL; \ + } else if (__comp > 0) { \ + SPLAY_RIGHT(elm, field) = SPLAY_RIGHT((head)->sph_root, field);\ + SPLAY_LEFT(elm, field) = (head)->sph_root; \ + SPLAY_RIGHT((head)->sph_root, field) = NULL; \ + } else \ + return ((head)->sph_root); \ + } \ + (head)->sph_root = (elm); \ + return (NULL); \ +} \ + \ +struct type * \ +name##_SPLAY_REMOVE(struct name *head, struct type *elm) \ +{ \ + struct type *__tmp; \ + if (SPLAY_EMPTY(head)) \ + return (NULL); \ + name##_SPLAY(head, elm); \ + if ((cmp)(elm, (head)->sph_root) == 0) { \ + if (SPLAY_LEFT((head)->sph_root, field) == NULL) { \ + (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);\ + } else { \ + __tmp = SPLAY_RIGHT((head)->sph_root, field); \ + (head)->sph_root = SPLAY_LEFT((head)->sph_root, field);\ + name##_SPLAY(head, elm); \ + SPLAY_RIGHT((head)->sph_root, field) = __tmp; \ + } \ + return (elm); \ + } \ + return (NULL); \ +} \ + \ +void \ +name##_SPLAY(struct name *head, struct type *elm) \ +{ \ + struct type __node, *__left, *__right, *__tmp; \ + int __comp; \ +\ + SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\ + __left = __right = &__node; \ +\ + while ((__comp = (cmp)(elm, (head)->sph_root)) != 0) { \ + if (__comp < 0) { \ + __tmp = SPLAY_LEFT((head)->sph_root, field); \ + if (__tmp == NULL) \ + break; \ + if ((cmp)(elm, __tmp) < 0){ \ + SPLAY_ROTATE_RIGHT(head, __tmp, field); \ + if (SPLAY_LEFT((head)->sph_root, field) == NULL)\ + break; \ + } \ + SPLAY_LINKLEFT(head, __right, field); \ + } else if (__comp > 0) { \ + __tmp = SPLAY_RIGHT((head)->sph_root, field); \ + if (__tmp == NULL) \ + break; \ + if ((cmp)(elm, __tmp) > 0){ \ + SPLAY_ROTATE_LEFT(head, __tmp, field); \ + if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\ + break; \ + } \ + SPLAY_LINKRIGHT(head, __left, field); \ + } \ + } \ + SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ +} \ + \ +/* Splay with either the minimum or the maximum element \ + * Used to find minimum or maximum element in tree. \ + */ \ +void name##_SPLAY_MINMAX(struct name *head, int __comp) \ +{ \ + struct type __node, *__left, *__right, *__tmp; \ +\ + SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\ + __left = __right = &__node; \ +\ + while (1) { \ + if (__comp < 0) { \ + __tmp = SPLAY_LEFT((head)->sph_root, field); \ + if (__tmp == NULL) \ + break; \ + if (__comp < 0){ \ + SPLAY_ROTATE_RIGHT(head, __tmp, field); \ + if (SPLAY_LEFT((head)->sph_root, field) == NULL)\ + break; \ + } \ + SPLAY_LINKLEFT(head, __right, field); \ + } else if (__comp > 0) { \ + __tmp = SPLAY_RIGHT((head)->sph_root, field); \ + if (__tmp == NULL) \ + break; \ + if (__comp > 0) { \ + SPLAY_ROTATE_LEFT(head, __tmp, field); \ + if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\ + break; \ + } \ + SPLAY_LINKRIGHT(head, __left, field); \ + } \ + } \ + SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ +} + +#define SPLAY_NEGINF -1 +#define SPLAY_INF 1 + +#define SPLAY_INSERT(name, x, y) name##_SPLAY_INSERT(x, y) +#define SPLAY_REMOVE(name, x, y) name##_SPLAY_REMOVE(x, y) +#define SPLAY_FIND(name, x, y) name##_SPLAY_FIND(x, y) +#define SPLAY_NEXT(name, x, y) name##_SPLAY_NEXT(x, y) +#define SPLAY_MIN(name, x) (SPLAY_EMPTY(x) ? NULL \ + : name##_SPLAY_MIN_MAX(x, SPLAY_NEGINF)) +#define SPLAY_MAX(name, x) (SPLAY_EMPTY(x) ? NULL \ + : name##_SPLAY_MIN_MAX(x, SPLAY_INF)) + +#define SPLAY_FOREACH(x, name, head) \ + for ((x) = SPLAY_MIN(name, head); \ + (x) != NULL; \ + (x) = SPLAY_NEXT(name, head, x)) + +/* Macros that define a red-black tree */ +#define RB_HEAD(name, type) \ +struct name { \ + struct type *rbh_root; /* root of the tree */ \ +} + +#define RB_INITIALIZER(root) \ + { NULL } + +#define RB_INIT(root) do { \ + (root)->rbh_root = NULL; \ +} while (/*CONSTCOND*/ 0) + +#define RB_BLACK 0 +#define RB_RED 1 +#define RB_ENTRY(type) \ +struct { \ + struct type *rbe_left; /* left element */ \ + struct type *rbe_right; /* right element */ \ + struct type *rbe_parent; /* parent element */ \ + int rbe_color; /* node color */ \ +} + +#define RB_LEFT(elm, field) (elm)->field.rbe_left +#define RB_RIGHT(elm, field) (elm)->field.rbe_right +#define RB_PARENT(elm, field) (elm)->field.rbe_parent +#define RB_COLOR(elm, field) (elm)->field.rbe_color +#define RB_ROOT(head) (head)->rbh_root +#define RB_EMPTY(head) (RB_ROOT(head) == NULL) + +#define RB_SET(elm, parent, field) do { \ + RB_PARENT(elm, field) = parent; \ + RB_LEFT(elm, field) = RB_RIGHT(elm, field) = NULL; \ + RB_COLOR(elm, field) = RB_RED; \ +} while (/*CONSTCOND*/ 0) + +#define RB_SET_BLACKRED(black, red, field) do { \ + RB_COLOR(black, field) = RB_BLACK; \ + RB_COLOR(red, field) = RB_RED; \ +} while (/*CONSTCOND*/ 0) + +#ifndef RB_AUGMENT +#define RB_AUGMENT(x) do {} while (0) +#endif + +#define RB_ROTATE_LEFT(head, elm, tmp, field) do { \ + (tmp) = RB_RIGHT(elm, field); \ + if ((RB_RIGHT(elm, field) = RB_LEFT(tmp, field)) != NULL) { \ + RB_PARENT(RB_LEFT(tmp, field), field) = (elm); \ + } \ + RB_AUGMENT(elm); \ + if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field)) != NULL) { \ + if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \ + RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \ + else \ + RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \ + } else \ + (head)->rbh_root = (tmp); \ + RB_LEFT(tmp, field) = (elm); \ + RB_PARENT(elm, field) = (tmp); \ + RB_AUGMENT(tmp); \ + if ((RB_PARENT(tmp, field))) \ + RB_AUGMENT(RB_PARENT(tmp, field)); \ +} while (/*CONSTCOND*/ 0) + +#define RB_ROTATE_RIGHT(head, elm, tmp, field) do { \ + (tmp) = RB_LEFT(elm, field); \ + if ((RB_LEFT(elm, field) = RB_RIGHT(tmp, field)) != NULL) { \ + RB_PARENT(RB_RIGHT(tmp, field), field) = (elm); \ + } \ + RB_AUGMENT(elm); \ + if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field)) != NULL) { \ + if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \ + RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \ + else \ + RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \ + } else \ + (head)->rbh_root = (tmp); \ + RB_RIGHT(tmp, field) = (elm); \ + RB_PARENT(elm, field) = (tmp); \ + RB_AUGMENT(tmp); \ + if ((RB_PARENT(tmp, field))) \ + RB_AUGMENT(RB_PARENT(tmp, field)); \ +} while (/*CONSTCOND*/ 0) + +/* Generates prototypes and inline functions */ +#define RB_PROTOTYPE(name, type, field, cmp) \ + RB_PROTOTYPE_INTERNAL(name, type, field, cmp,) +#define RB_PROTOTYPE_STATIC(name, type, field, cmp) \ + RB_PROTOTYPE_INTERNAL(name, type, field, cmp, __unused static) +#define RB_PROTOTYPE_INTERNAL(name, type, field, cmp, attr) \ +attr void name##_RB_INSERT_COLOR(struct name *, struct type *); \ +attr void name##_RB_REMOVE_COLOR(struct name *, struct type *, struct type *);\ +attr struct type *name##_RB_REMOVE(struct name *, struct type *); \ +attr struct type *name##_RB_INSERT(struct name *, struct type *); \ +attr struct type *name##_RB_FIND(struct name *, struct type *); \ +attr struct type *name##_RB_NFIND(struct name *, struct type *); \ +attr struct type *name##_RB_NEXT(struct type *); \ +attr struct type *name##_RB_PREV(struct type *); \ +attr struct type *name##_RB_MINMAX(struct name *, int); \ + \ + +/* Main rb operation. + * Moves node close to the key of elm to top + */ +#define RB_GENERATE(name, type, field, cmp) \ + RB_GENERATE_INTERNAL(name, type, field, cmp,) +#define RB_GENERATE_STATIC(name, type, field, cmp) \ + RB_GENERATE_INTERNAL(name, type, field, cmp, __unused static) +#define RB_GENERATE_INTERNAL(name, type, field, cmp, attr) \ +attr void \ +name##_RB_INSERT_COLOR(struct name *head, struct type *elm) \ +{ \ + struct type *parent, *gparent, *tmp; \ + while ((parent = RB_PARENT(elm, field)) != NULL && \ + RB_COLOR(parent, field) == RB_RED) { \ + gparent = RB_PARENT(parent, field); \ + if (parent == RB_LEFT(gparent, field)) { \ + tmp = RB_RIGHT(gparent, field); \ + if (tmp && RB_COLOR(tmp, field) == RB_RED) { \ + RB_COLOR(tmp, field) = RB_BLACK; \ + RB_SET_BLACKRED(parent, gparent, field);\ + elm = gparent; \ + continue; \ + } \ + if (RB_RIGHT(parent, field) == elm) { \ + RB_ROTATE_LEFT(head, parent, tmp, field);\ + tmp = parent; \ + parent = elm; \ + elm = tmp; \ + } \ + RB_SET_BLACKRED(parent, gparent, field); \ + RB_ROTATE_RIGHT(head, gparent, tmp, field); \ + } else { \ + tmp = RB_LEFT(gparent, field); \ + if (tmp && RB_COLOR(tmp, field) == RB_RED) { \ + RB_COLOR(tmp, field) = RB_BLACK; \ + RB_SET_BLACKRED(parent, gparent, field);\ + elm = gparent; \ + continue; \ + } \ + if (RB_LEFT(parent, field) == elm) { \ + RB_ROTATE_RIGHT(head, parent, tmp, field);\ + tmp = parent; \ + parent = elm; \ + elm = tmp; \ + } \ + RB_SET_BLACKRED(parent, gparent, field); \ + RB_ROTATE_LEFT(head, gparent, tmp, field); \ + } \ + } \ + RB_COLOR(head->rbh_root, field) = RB_BLACK; \ +} \ + \ +attr void \ +name##_RB_REMOVE_COLOR(struct name *head, struct type *parent, struct type *elm) \ +{ \ + struct type *tmp; \ + while ((elm == NULL || RB_COLOR(elm, field) == RB_BLACK) && \ + elm != RB_ROOT(head)) { \ + if (RB_LEFT(parent, field) == elm) { \ + tmp = RB_RIGHT(parent, field); \ + if (RB_COLOR(tmp, field) == RB_RED) { \ + RB_SET_BLACKRED(tmp, parent, field); \ + RB_ROTATE_LEFT(head, parent, tmp, field);\ + tmp = RB_RIGHT(parent, field); \ + } \ + if ((RB_LEFT(tmp, field) == NULL || \ + RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\ + (RB_RIGHT(tmp, field) == NULL || \ + RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\ + RB_COLOR(tmp, field) = RB_RED; \ + elm = parent; \ + parent = RB_PARENT(elm, field); \ + } else { \ + if (RB_RIGHT(tmp, field) == NULL || \ + RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK) {\ + struct type *oleft; \ + if ((oleft = RB_LEFT(tmp, field)) \ + != NULL) \ + RB_COLOR(oleft, field) = RB_BLACK;\ + RB_COLOR(tmp, field) = RB_RED; \ + RB_ROTATE_RIGHT(head, tmp, oleft, field);\ + tmp = RB_RIGHT(parent, field); \ + } \ + RB_COLOR(tmp, field) = RB_COLOR(parent, field);\ + RB_COLOR(parent, field) = RB_BLACK; \ + if (RB_RIGHT(tmp, field)) \ + RB_COLOR(RB_RIGHT(tmp, field), field) = RB_BLACK;\ + RB_ROTATE_LEFT(head, parent, tmp, field);\ + elm = RB_ROOT(head); \ + break; \ + } \ + } else { \ + tmp = RB_LEFT(parent, field); \ + if (RB_COLOR(tmp, field) == RB_RED) { \ + RB_SET_BLACKRED(tmp, parent, field); \ + RB_ROTATE_RIGHT(head, parent, tmp, field);\ + tmp = RB_LEFT(parent, field); \ + } \ + if ((RB_LEFT(tmp, field) == NULL || \ + RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\ + (RB_RIGHT(tmp, field) == NULL || \ + RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\ + RB_COLOR(tmp, field) = RB_RED; \ + elm = parent; \ + parent = RB_PARENT(elm, field); \ + } else { \ + if (RB_LEFT(tmp, field) == NULL || \ + RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) {\ + struct type *oright; \ + if ((oright = RB_RIGHT(tmp, field)) \ + != NULL) \ + RB_COLOR(oright, field) = RB_BLACK;\ + RB_COLOR(tmp, field) = RB_RED; \ + RB_ROTATE_LEFT(head, tmp, oright, field);\ + tmp = RB_LEFT(parent, field); \ + } \ + RB_COLOR(tmp, field) = RB_COLOR(parent, field);\ + RB_COLOR(parent, field) = RB_BLACK; \ + if (RB_LEFT(tmp, field)) \ + RB_COLOR(RB_LEFT(tmp, field), field) = RB_BLACK;\ + RB_ROTATE_RIGHT(head, parent, tmp, field);\ + elm = RB_ROOT(head); \ + break; \ + } \ + } \ + } \ + if (elm) \ + RB_COLOR(elm, field) = RB_BLACK; \ +} \ + \ +attr struct type * \ +name##_RB_REMOVE(struct name *head, struct type *elm) \ +{ \ + struct type *child, *parent, *old = elm; \ + int color; \ + if (RB_LEFT(elm, field) == NULL) \ + child = RB_RIGHT(elm, field); \ + else if (RB_RIGHT(elm, field) == NULL) \ + child = RB_LEFT(elm, field); \ + else { \ + struct type *left; \ + elm = RB_RIGHT(elm, field); \ + while ((left = RB_LEFT(elm, field)) != NULL) \ + elm = left; \ + child = RB_RIGHT(elm, field); \ + parent = RB_PARENT(elm, field); \ + color = RB_COLOR(elm, field); \ + if (child) \ + RB_PARENT(child, field) = parent; \ + if (parent) { \ + if (RB_LEFT(parent, field) == elm) \ + RB_LEFT(parent, field) = child; \ + else \ + RB_RIGHT(parent, field) = child; \ + RB_AUGMENT(parent); \ + } else \ + RB_ROOT(head) = child; \ + if (RB_PARENT(elm, field) == old) \ + parent = elm; \ + (elm)->field = (old)->field; \ + if (RB_PARENT(old, field)) { \ + if (RB_LEFT(RB_PARENT(old, field), field) == old)\ + RB_LEFT(RB_PARENT(old, field), field) = elm;\ + else \ + RB_RIGHT(RB_PARENT(old, field), field) = elm;\ + RB_AUGMENT(RB_PARENT(old, field)); \ + } else \ + RB_ROOT(head) = elm; \ + RB_PARENT(RB_LEFT(old, field), field) = elm; \ + if (RB_RIGHT(old, field)) \ + RB_PARENT(RB_RIGHT(old, field), field) = elm; \ + if (parent) { \ + left = parent; \ + do { \ + RB_AUGMENT(left); \ + } while ((left = RB_PARENT(left, field)) != NULL); \ + } \ + goto color; \ + } \ + parent = RB_PARENT(elm, field); \ + color = RB_COLOR(elm, field); \ + if (child) \ + RB_PARENT(child, field) = parent; \ + if (parent) { \ + if (RB_LEFT(parent, field) == elm) \ + RB_LEFT(parent, field) = child; \ + else \ + RB_RIGHT(parent, field) = child; \ + RB_AUGMENT(parent); \ + } else \ + RB_ROOT(head) = child; \ +color: \ + if (color == RB_BLACK) \ + name##_RB_REMOVE_COLOR(head, parent, child); \ + return (old); \ +} \ + \ +/* Inserts a node into the RB tree */ \ +attr struct type * \ +name##_RB_INSERT(struct name *head, struct type *elm) \ +{ \ + struct type *tmp; \ + struct type *parent = NULL; \ + int comp = 0; \ + tmp = RB_ROOT(head); \ + while (tmp) { \ + parent = tmp; \ + comp = (cmp)(elm, parent); \ + if (comp < 0) \ + tmp = RB_LEFT(tmp, field); \ + else if (comp > 0) \ + tmp = RB_RIGHT(tmp, field); \ + else \ + return (tmp); \ + } \ + RB_SET(elm, parent, field); \ + if (parent != NULL) { \ + if (comp < 0) \ + RB_LEFT(parent, field) = elm; \ + else \ + RB_RIGHT(parent, field) = elm; \ + RB_AUGMENT(parent); \ + } else \ + RB_ROOT(head) = elm; \ + name##_RB_INSERT_COLOR(head, elm); \ + return (NULL); \ +} \ + \ +/* Finds the node with the same key as elm */ \ +attr struct type * \ +name##_RB_FIND(struct name *head, struct type *elm) \ +{ \ + struct type *tmp = RB_ROOT(head); \ + int comp; \ + while (tmp) { \ + comp = cmp(elm, tmp); \ + if (comp < 0) \ + tmp = RB_LEFT(tmp, field); \ + else if (comp > 0) \ + tmp = RB_RIGHT(tmp, field); \ + else \ + return (tmp); \ + } \ + return (NULL); \ +} \ + \ +/* Finds the first node greater than or equal to the search key */ \ +attr struct type * \ +name##_RB_NFIND(struct name *head, struct type *elm) \ +{ \ + struct type *tmp = RB_ROOT(head); \ + struct type *res = NULL; \ + int comp; \ + while (tmp) { \ + comp = cmp(elm, tmp); \ + if (comp < 0) { \ + res = tmp; \ + tmp = RB_LEFT(tmp, field); \ + } \ + else if (comp > 0) \ + tmp = RB_RIGHT(tmp, field); \ + else \ + return (tmp); \ + } \ + return (res); \ +} \ + \ +/* ARGSUSED */ \ +attr struct type * \ +name##_RB_NEXT(struct type *elm) \ +{ \ + if (RB_RIGHT(elm, field)) { \ + elm = RB_RIGHT(elm, field); \ + while (RB_LEFT(elm, field)) \ + elm = RB_LEFT(elm, field); \ + } else { \ + if (RB_PARENT(elm, field) && \ + (elm == RB_LEFT(RB_PARENT(elm, field), field))) \ + elm = RB_PARENT(elm, field); \ + else { \ + while (RB_PARENT(elm, field) && \ + (elm == RB_RIGHT(RB_PARENT(elm, field), field)))\ + elm = RB_PARENT(elm, field); \ + elm = RB_PARENT(elm, field); \ + } \ + } \ + return (elm); \ +} \ + \ +/* ARGSUSED */ \ +attr struct type * \ +name##_RB_PREV(struct type *elm) \ +{ \ + if (RB_LEFT(elm, field)) { \ + elm = RB_LEFT(elm, field); \ + while (RB_RIGHT(elm, field)) \ + elm = RB_RIGHT(elm, field); \ + } else { \ + if (RB_PARENT(elm, field) && \ + (elm == RB_RIGHT(RB_PARENT(elm, field), field))) \ + elm = RB_PARENT(elm, field); \ + else { \ + while (RB_PARENT(elm, field) && \ + (elm == RB_LEFT(RB_PARENT(elm, field), field)))\ + elm = RB_PARENT(elm, field); \ + elm = RB_PARENT(elm, field); \ + } \ + } \ + return (elm); \ +} \ + \ +attr struct type * \ +name##_RB_MINMAX(struct name *head, int val) \ +{ \ + struct type *tmp = RB_ROOT(head); \ + struct type *parent = NULL; \ + while (tmp) { \ + parent = tmp; \ + if (val < 0) \ + tmp = RB_LEFT(tmp, field); \ + else \ + tmp = RB_RIGHT(tmp, field); \ + } \ + return (parent); \ +} + +#define RB_NEGINF -1 +#define RB_INF 1 + +#define RB_INSERT(name, x, y) name##_RB_INSERT(x, y) +#define RB_REMOVE(name, x, y) name##_RB_REMOVE(x, y) +#define RB_FIND(name, x, y) name##_RB_FIND(x, y) +#define RB_NFIND(name, x, y) name##_RB_NFIND(x, y) +#define RB_NEXT(name, x, y) name##_RB_NEXT(y) +#define RB_PREV(name, x, y) name##_RB_PREV(y) +#define RB_MIN(name, x) name##_RB_MINMAX(x, RB_NEGINF) +#define RB_MAX(name, x) name##_RB_MINMAX(x, RB_INF) + +#define RB_FOREACH(x, name, head) \ + for ((x) = RB_MIN(name, head); \ + (x) != NULL; \ + (x) = name##_RB_NEXT(x)) + +#define RB_FOREACH_FROM(x, name, y) \ + for ((x) = (y); \ + ((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL); \ + (x) = (y)) + +#define RB_FOREACH_SAFE(x, name, head, y) \ + for ((x) = RB_MIN(name, head); \ + ((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL); \ + (x) = (y)) + +#define RB_FOREACH_REVERSE(x, name, head) \ + for ((x) = RB_MAX(name, head); \ + (x) != NULL; \ + (x) = name##_RB_PREV(x)) + +#define RB_FOREACH_REVERSE_FROM(x, name, y) \ + for ((x) = (y); \ + ((x) != NULL) && ((y) = name##_RB_PREV(x), (x) != NULL); \ + (x) = (y)) + +#define RB_FOREACH_REVERSE_SAFE(x, name, head, y) \ + for ((x) = RB_MAX(name, head); \ + ((x) != NULL) && ((y) = name##_RB_PREV(x), (x) != NULL); \ + (x) = (y)) + +#endif /* _SYS_TREE_H_ */ diff --git a/reactos/base/services/nfsd/upcall.c b/reactos/base/services/nfsd/upcall.c new file mode 100644 index 00000000000..c259e073afa --- /dev/null +++ b/reactos/base/services/nfsd/upcall.c @@ -0,0 +1,212 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include +#include +#include + +#include "upcall.h" +#include "daemon_debug.h" +#include "util.h" + +extern const nfs41_upcall_op nfs41_op_mount; +extern const nfs41_upcall_op nfs41_op_unmount; +extern const nfs41_upcall_op nfs41_op_open; +extern const nfs41_upcall_op nfs41_op_close; +extern const nfs41_upcall_op nfs41_op_read; +extern const nfs41_upcall_op nfs41_op_write; +extern const nfs41_upcall_op nfs41_op_lock; +extern const nfs41_upcall_op nfs41_op_unlock; +extern const nfs41_upcall_op nfs41_op_readdir; +extern const nfs41_upcall_op nfs41_op_getattr; +extern const nfs41_upcall_op nfs41_op_setattr; +extern const nfs41_upcall_op nfs41_op_getexattr; +extern const nfs41_upcall_op nfs41_op_setexattr; +extern const nfs41_upcall_op nfs41_op_symlink; +extern const nfs41_upcall_op nfs41_op_volume; +extern const nfs41_upcall_op nfs41_op_getacl; +extern const nfs41_upcall_op nfs41_op_setacl; + +static const nfs41_upcall_op *g_upcall_op_table[] = { + &nfs41_op_mount, + &nfs41_op_unmount, + &nfs41_op_open, + &nfs41_op_close, + &nfs41_op_read, + &nfs41_op_write, + &nfs41_op_lock, + &nfs41_op_unlock, + &nfs41_op_readdir, + &nfs41_op_getattr, + &nfs41_op_setattr, + &nfs41_op_getexattr, + &nfs41_op_setexattr, + &nfs41_op_symlink, + &nfs41_op_volume, + &nfs41_op_getacl, + &nfs41_op_setacl, + NULL, + NULL +}; +#ifdef __REACTOS__ +static const uint32_t g_upcall_op_table_size = (sizeof(g_upcall_op_table) / sizeof(g_upcall_op_table[0])); +#else +static const uint32_t g_upcall_op_table_size = ARRAYSIZE(g_upcall_op_table); +#endif + +int upcall_parse( + IN unsigned char *buffer, + IN uint32_t length, + OUT nfs41_upcall *upcall) +{ + int status; + const nfs41_upcall_op *op; + DWORD version; + + ZeroMemory(upcall, sizeof(nfs41_upcall)); + if (!length) { + eprintf("empty upcall\n"); + upcall->status = status = 102; + goto out; + } + + dprintf(2, "received %d bytes upcall data: processing upcall\n", length); + print_hexbuf(4, (unsigned char *)"upcall buffer: ", buffer, length); + + /* parse common elements */ + status = safe_read(&buffer, &length, &version, sizeof(uint32_t)); + if (status) goto out; + status = safe_read(&buffer, &length, &upcall->xid, sizeof(uint64_t)); + if (status) goto out; + status = safe_read(&buffer, &length, &upcall->opcode, sizeof(uint32_t)); + if (status) goto out; + status = safe_read(&buffer, &length, &upcall->root_ref, sizeof(HANDLE)); + if (status) goto out; + status = safe_read(&buffer, &length, &upcall->state_ref, sizeof(HANDLE)); + if (status) goto out; + + dprintf(2, "time=%ld version=%d xid=%d opcode=%s session=0x%x open_state=0x%x\n", + time(NULL), version, upcall->xid, opcode2string(upcall->opcode), upcall->root_ref, + upcall->state_ref); + if (version != NFS41D_VERSION) { + eprintf("received version %d expecting version %d\n", version, NFS41D_VERSION); + upcall->status = status = NFSD_VERSION_MISMATCH; + goto out; + } + if (upcall->opcode >= g_upcall_op_table_size) { + status = ERROR_NOT_SUPPORTED; + eprintf("unrecognized upcall opcode %d!\n", upcall->opcode); + goto out; + } + if (upcall->root_ref != INVALID_HANDLE_VALUE) + nfs41_root_ref(upcall->root_ref); + if (upcall->state_ref != INVALID_HANDLE_VALUE) + nfs41_open_state_ref(upcall->state_ref); + + /* parse the operation's arguments */ + op = g_upcall_op_table[upcall->opcode]; + if (op && op->parse) { + status = op->parse(buffer, length, upcall); + if (status) { + eprintf("parsing of upcall '%s' failed with %d.\n", + opcode2string(upcall->opcode), status); + goto out; + } + } +out: + return status; +} + +int upcall_handle( + IN nfs41_upcall *upcall) +{ + int status = NO_ERROR; + const nfs41_upcall_op *op; + + op = g_upcall_op_table[upcall->opcode]; + if (op == NULL || op->handle == NULL) { + status = ERROR_NOT_SUPPORTED; + eprintf("upcall '%s' missing handle function!\n", + opcode2string(upcall->opcode)); + goto out; + } + + upcall->status = op->handle(upcall); +out: + return status; +} +#pragma warning (disable : 4706) /* assignment within conditional expression */ +void upcall_marshall( + IN nfs41_upcall *upcall, + OUT unsigned char *buffer, + IN uint32_t length, + OUT uint32_t *length_out) +{ + const nfs41_upcall_op *op; + unsigned char *orig_buf = buffer; + const uint32_t total = length, orig_len = length; + + /* marshall common elements */ +write_downcall: + length = orig_len; + buffer = orig_buf; + safe_write(&buffer, &length, &upcall->xid, sizeof(upcall->xid)); + safe_write(&buffer, &length, &upcall->opcode, sizeof(upcall->opcode)); + safe_write(&buffer, &length, &upcall->status, sizeof(upcall->status)); + safe_write(&buffer, &length, &upcall->last_error, sizeof(upcall->last_error)); + + if (upcall->status) + goto out; + + /* marshall the operation's results */ + op = g_upcall_op_table[upcall->opcode]; + if (op && op->marshall) { + if ((upcall->status = op->marshall(buffer, &length, upcall))) + goto write_downcall; + } +out: + *length_out = total - length; +} + +void upcall_cancel( + IN nfs41_upcall *upcall) +{ + const nfs41_upcall_op *op = g_upcall_op_table[upcall->opcode]; + if (op && op->cancel) + op->cancel(upcall); +} + +void upcall_cleanup( + IN nfs41_upcall *upcall) +{ + const nfs41_upcall_op *op = g_upcall_op_table[upcall->opcode]; + if (op && op->cleanup && upcall->status != NFSD_VERSION_MISMATCH) + op->cleanup(upcall); + + if (upcall->state_ref && upcall->state_ref != INVALID_HANDLE_VALUE) { + nfs41_open_state_deref(upcall->state_ref); + upcall->state_ref = NULL; + } + if (upcall->root_ref && upcall->root_ref != INVALID_HANDLE_VALUE) { + nfs41_root_deref(upcall->root_ref); + upcall->root_ref = NULL; + } +} diff --git a/reactos/base/services/nfsd/upcall.h b/reactos/base/services/nfsd/upcall.h new file mode 100644 index 00000000000..4eeaf5e21a6 --- /dev/null +++ b/reactos/base/services/nfsd/upcall.h @@ -0,0 +1,248 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#ifndef __NFS41_DAEMON_UPCALL_H__ +#define __NFS41_DAEMON_UPCALL_H__ + +#include "nfs41_ops.h" +#include "from_kernel.h" + +#define NFSD_VERSION_MISMATCH 116 + +/* structures for upcall arguments */ +typedef struct __mount_upcall_args { + const char *hostname; + const char *path; + DWORD sec_flavor; + DWORD rsize; + DWORD wsize; + DWORD lease_time; + FILE_FS_ATTRIBUTE_INFORMATION FsAttrs; +} mount_upcall_args; + +typedef struct __open_upcall_args { + nfs41_abs_path symlink; + FILE_BASIC_INFO basic_info; + FILE_STANDARD_INFO std_info; + const char *path; + ULONG access_mask; + ULONG access_mode; + ULONG file_attrs; + ULONG disposition; + ULONG create_opts; + LONG open_owner_id; + DWORD mode; + ULONGLONG changeattr; + HANDLE srv_open; + DWORD deleg_type; + PFILE_FULL_EA_INFORMATION ea; + BOOLEAN created; + BOOLEAN symlink_embedded; +} open_upcall_args; + +typedef struct __close_upcall_args { + HANDLE srv_open; + const char *path; + BOOLEAN remove; + BOOLEAN renamed; +} close_upcall_args; + +typedef struct __readwrite_upcall_args { + unsigned char *buffer; + ULONGLONG offset; + ULONG len; + ULONG out_len; + ULONGLONG ctime; +} readwrite_upcall_args; + +typedef struct __lock_upcall_args { + uint64_t offset; + uint64_t length; + BOOLEAN exclusive; + BOOLEAN blocking; + BOOLEAN acquired; +} lock_upcall_args; + +typedef struct __unlock_upcall_args { + uint32_t count; + unsigned char *buf; + uint32_t buf_len; +} unlock_upcall_args; + +typedef struct __getattr_upcall_args { + FILE_BASIC_INFO basic_info; + FILE_STANDARD_INFO std_info; + FILE_ATTRIBUTE_TAG_INFO tag_info; + FILE_INTERNAL_INFORMATION intr_info; + FILE_NETWORK_OPEN_INFORMATION network_info; + int query_class; + int buf_len; + int query_reply_len; + ULONGLONG ctime; +} getattr_upcall_args; + +typedef struct __setattr_upcall_args { + const char *path; + nfs41_root *root; + nfs41_open_state *state; + unsigned char *buf; + uint32_t buf_len; + int set_class; + ULONGLONG ctime; +} setattr_upcall_args; + +typedef struct __getexattr_upcall_args { + const char *path; + unsigned char *buf; + uint32_t buf_len; + ULONG eaindex; + unsigned char *ealist; + uint32_t ealist_len; + uint32_t overflow; + BOOLEAN single; + BOOLEAN restart; +} getexattr_upcall_args; + + +typedef struct __setexattr_upcall_args { + const char *path; + unsigned char *buf; + uint32_t buf_len; + uint32_t mode; + ULONGLONG ctime; +} setexattr_upcall_args; + +typedef struct __readdir_upcall_args { + const char *filter; + nfs41_root *root; + nfs41_open_state *state; + int buf_len; + int query_class; + int query_reply_len; + BOOLEAN initial; + BOOLEAN restart; + BOOLEAN single; + unsigned char *kbuf; +} readdir_upcall_args; + +typedef struct __symlink_upcall_args { + nfs41_abs_path target_get; + char *target_set; + const char *path; + BOOLEAN set; +} symlink_upcall_args; + +typedef struct __volume_upcall_args { + FS_INFORMATION_CLASS query; + int len; + union { + FILE_FS_SIZE_INFORMATION size; + FILE_FS_FULL_SIZE_INFORMATION fullsize; + FILE_FS_ATTRIBUTE_INFORMATION attribute; + } info; +} volume_upcall_args; + +typedef struct __getacl_upcall_args { + SECURITY_INFORMATION query; + PSECURITY_DESCRIPTOR sec_desc; + DWORD sec_desc_len; +} getacl_upcall_args; + +typedef struct __setacl_upcall_args { + SECURITY_INFORMATION query; + PSECURITY_DESCRIPTOR sec_desc; + ULONGLONG ctime; +} setacl_upcall_args; + +typedef union __upcall_args { + mount_upcall_args mount; + open_upcall_args open; + close_upcall_args close; + readwrite_upcall_args rw; + lock_upcall_args lock; + unlock_upcall_args unlock; + getattr_upcall_args getattr; + getexattr_upcall_args getexattr; + setattr_upcall_args setattr; + setexattr_upcall_args setexattr; + readdir_upcall_args readdir; + symlink_upcall_args symlink; + volume_upcall_args volume; + getacl_upcall_args getacl; + setacl_upcall_args setacl; +} upcall_args; + +typedef struct __nfs41_upcall { + uint64_t xid; + uint32_t opcode; + uint32_t status; + uint32_t last_error; + upcall_args args; + + uid_t uid; + gid_t gid; + + /* store referenced pointers with the upcall for + * automatic dereferencing on upcall_cleanup(); + * see upcall_root_ref() and upcall_open_state_ref() */ + nfs41_root *root_ref; + nfs41_open_state *state_ref; +} nfs41_upcall; + + +/* upcall operation interface */ +typedef int (*upcall_parse_proc)(unsigned char*, uint32_t, nfs41_upcall*); +typedef int (*upcall_handle_proc)(nfs41_upcall*); +typedef int (*upcall_marshall_proc)(unsigned char*, uint32_t*, nfs41_upcall*); +typedef void (*upcall_cancel_proc)(nfs41_upcall*); +typedef void (*upcall_cleanup_proc)(nfs41_upcall*); + +typedef struct __nfs41_upcall_op { + upcall_parse_proc parse; + upcall_handle_proc handle; + upcall_marshall_proc marshall; + upcall_cancel_proc cancel; + upcall_cleanup_proc cleanup; +} nfs41_upcall_op; + + +/* upcall.c */ +int upcall_parse( + IN unsigned char *buffer, + IN uint32_t length, + OUT nfs41_upcall *upcall); + +int upcall_handle( + IN nfs41_upcall *upcall); + +void upcall_marshall( + IN nfs41_upcall *upcall, + OUT unsigned char *buffer, + IN uint32_t length, + OUT uint32_t *length_out); + +void upcall_cancel( + IN nfs41_upcall *upcall); + +void upcall_cleanup( + IN nfs41_upcall *upcall); + +#endif /* !__NFS41_DAEMON_UPCALL_H__ */ diff --git a/reactos/base/services/nfsd/util.c b/reactos/base/services/nfsd/util.c new file mode 100644 index 00000000000..c565c154009 --- /dev/null +++ b/reactos/base/services/nfsd/util.c @@ -0,0 +1,448 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include +#include +#include +#include +#include /* for Crypt*() functions */ + +#include "daemon_debug.h" +#include "util.h" +#include "nfs41_ops.h" + + +int safe_read(unsigned char **pos, uint32_t *remaining, void *dest, uint32_t dest_len) +{ + if (*remaining < dest_len) + return ERROR_BUFFER_OVERFLOW; + + CopyMemory(dest, *pos, dest_len); + *pos += dest_len; + *remaining -= dest_len; + return 0; +} + +int safe_write(unsigned char **pos, uint32_t *remaining, void *src, uint32_t src_len) +{ + if (*remaining < src_len) + return ERROR_BUFFER_OVERFLOW; + + CopyMemory(*pos, src, src_len); + *pos += src_len; + *remaining -= src_len; + return 0; +} + +int get_name(unsigned char **pos, uint32_t *remaining, const char **out_name) +{ + int status; + USHORT len; + + status = safe_read(pos, remaining, &len, sizeof(USHORT)); + if (status) goto out; + if (*remaining < len) { + status = ERROR_BUFFER_OVERFLOW; + goto out; + } + *out_name = (const char*)*pos; + *pos += len; + *remaining -= len; +out: + return status; +} + +const char* strip_path( + IN const char *path, + OUT uint32_t *len_out) +{ + const char *name = strrchr(path, '\\'); + name = name ? name + 1 : path; + if (len_out) + *len_out = (uint32_t)strlen(name); + return name; +} + +uint32_t max_read_size( + IN const nfs41_session *session, + IN const nfs41_fh *fh) +{ + const uint32_t maxresponse = session->fore_chan_attrs.ca_maxresponsesize; + return (uint32_t)min(fh->superblock->maxread, maxresponse - READ_OVERHEAD); +} + +uint32_t max_write_size( + IN const nfs41_session *session, + IN const nfs41_fh *fh) +{ + const uint32_t maxrequest = session->fore_chan_attrs.ca_maxrequestsize; + return (uint32_t)min(fh->superblock->maxwrite, maxrequest - WRITE_OVERHEAD); +} + +bool_t verify_write( + IN nfs41_write_verf *verf, + IN OUT enum stable_how4 *stable) +{ + if (verf->committed != UNSTABLE4) { + *stable = verf->committed; + dprintf(3, "verify_write: committed to stable storage\n"); + return 1; + } + + if (*stable != UNSTABLE4) { + memcpy(verf->expected, verf->verf, NFS4_VERIFIER_SIZE); + *stable = UNSTABLE4; + dprintf(3, "verify_write: first unstable write, saving verifier\n"); + return 1; + } + + if (memcmp(verf->expected, verf->verf, NFS4_VERIFIER_SIZE) == 0) { + dprintf(3, "verify_write: verifier matches expected\n"); + return 1; + } + + dprintf(2, "verify_write: verifier changed; writes have been lost!\n"); + return 0; +} + +bool_t verify_commit( + IN nfs41_write_verf *verf) +{ + if (memcmp(verf->expected, verf->verf, NFS4_VERIFIER_SIZE) == 0) { + dprintf(3, "verify_commit: verifier matches expected\n"); + return 1; + } + dprintf(2, "verify_commit: verifier changed; writes have been lost!\n"); + return 0; +} + +ULONG nfs_file_info_to_attributes( + IN const nfs41_file_info *info) +{ + ULONG attrs = 0; + if (info->type == NF4DIR) + attrs |= FILE_ATTRIBUTE_DIRECTORY; + else if (info->type == NF4LNK) { + attrs |= FILE_ATTRIBUTE_REPARSE_POINT; + if (info->symlink_dir) + attrs |= FILE_ATTRIBUTE_DIRECTORY; + } else if (info->type != NF4REG) + dprintf(1, "unhandled file type %d, defaulting to NF4REG\n", + info->type); + + if (info->mode == 0444) /* XXX: 0444 for READONLY */ + attrs |= FILE_ATTRIBUTE_READONLY; + + if (info->hidden) attrs |= FILE_ATTRIBUTE_HIDDEN; + if (info->system) attrs |= FILE_ATTRIBUTE_SYSTEM; + if (info->archive) attrs |= FILE_ATTRIBUTE_ARCHIVE; + + // FILE_ATTRIBUTE_NORMAL attribute is only set if no other attributes are present. + // all other override this value. + return attrs ? attrs : FILE_ATTRIBUTE_NORMAL; +} + +void nfs_to_basic_info( + IN const nfs41_file_info *info, + OUT PFILE_BASIC_INFO basic_out) +{ + nfs_time_to_file_time(&info->time_create, &basic_out->CreationTime); + nfs_time_to_file_time(&info->time_access, &basic_out->LastAccessTime); + nfs_time_to_file_time(&info->time_modify, &basic_out->LastWriteTime); + /* XXX: was using 'change' attr, but that wasn't giving a time */ + nfs_time_to_file_time(&info->time_modify, &basic_out->ChangeTime); + basic_out->FileAttributes = nfs_file_info_to_attributes(info); +} + +void nfs_to_standard_info( + IN const nfs41_file_info *info, + OUT PFILE_STANDARD_INFO std_out) +{ + const ULONG FileAttributes = nfs_file_info_to_attributes(info); + + std_out->AllocationSize.QuadPart = + std_out->EndOfFile.QuadPart = (LONGLONG)info->size; + std_out->NumberOfLinks = info->numlinks; + std_out->DeletePending = FALSE; + std_out->Directory = FileAttributes & FILE_ATTRIBUTE_DIRECTORY ? + TRUE : FALSE; +} + +void nfs_to_network_openinfo( + IN const nfs41_file_info *info, + OUT PFILE_NETWORK_OPEN_INFORMATION net_out) +{ + + nfs_time_to_file_time(&info->time_create, &net_out->CreationTime); + nfs_time_to_file_time(&info->time_access, &net_out->LastAccessTime); + nfs_time_to_file_time(&info->time_modify, &net_out->LastWriteTime); + /* XXX: was using 'change' attr, but that wasn't giving a time */ + nfs_time_to_file_time(&info->time_modify, &net_out->ChangeTime); + net_out->AllocationSize.QuadPart = + net_out->EndOfFile.QuadPart = (LONGLONG)info->size; + net_out->FileAttributes = nfs_file_info_to_attributes(info); +} + +void get_file_time( + OUT PLARGE_INTEGER file_time) +{ + GetSystemTimeAsFileTime((LPFILETIME)file_time); +} + +void get_nfs_time( + OUT nfstime4 *nfs_time) +{ + LARGE_INTEGER file_time; + get_file_time(&file_time); + file_time_to_nfs_time(&file_time, nfs_time); +} + +bool_t multi_addr_find( + IN const multi_addr4 *addrs, + IN const netaddr4 *addr, + OUT OPTIONAL uint32_t *index_out) +{ + uint32_t i; + for (i = 0; i < addrs->count; i++) { + const netaddr4 *saddr = &addrs->arr[i]; + if (!strncmp(saddr->netid, addr->netid, NFS41_NETWORK_ID_LEN) && + !strncmp(saddr->uaddr, addr->uaddr, NFS41_UNIVERSAL_ADDR_LEN)) { + if (index_out) *index_out = i; + return 1; + } + } + return 0; +} + +int nfs_to_windows_error(int status, int default_error) +{ + /* make sure this is actually an nfs error */ + if (status < 0 || (status > 70 && status < 10001) || status > 10087) { + eprintf("nfs_to_windows_error called with non-nfs " + "error code %d; returning the error as is\n", status); + return status; + } + + switch (status) { + case NFS4_OK: return NO_ERROR; + case NFS4ERR_PERM: return ERROR_ACCESS_DENIED; + case NFS4ERR_NOENT: return ERROR_FILE_NOT_FOUND; + case NFS4ERR_IO: return ERROR_NET_WRITE_FAULT; + case NFS4ERR_ACCESS: return ERROR_ACCESS_DENIED; + case NFS4ERR_EXIST: return ERROR_FILE_EXISTS; + case NFS4ERR_XDEV: return ERROR_NOT_SAME_DEVICE; + case NFS4ERR_INVAL: return ERROR_INVALID_PARAMETER; + case NFS4ERR_FBIG: return ERROR_FILE_TOO_LARGE; + case NFS4ERR_NOSPC: return ERROR_DISK_FULL; + case NFS4ERR_ROFS: return ERROR_NETWORK_ACCESS_DENIED; + case NFS4ERR_MLINK: return ERROR_TOO_MANY_LINKS; + case NFS4ERR_NAMETOOLONG: return ERROR_FILENAME_EXCED_RANGE; + case NFS4ERR_STALE: return ERROR_NETNAME_DELETED; + case NFS4ERR_NOTEMPTY: return ERROR_NOT_EMPTY; + case NFS4ERR_DENIED: return ERROR_LOCK_FAILED; + case NFS4ERR_TOOSMALL: return ERROR_BUFFER_OVERFLOW; + case NFS4ERR_LOCKED: return ERROR_LOCK_VIOLATION; + case NFS4ERR_SHARE_DENIED: return ERROR_SHARING_VIOLATION; + case NFS4ERR_LOCK_RANGE: return ERROR_NOT_LOCKED; + case NFS4ERR_ATTRNOTSUPP: return ERROR_NOT_SUPPORTED; + case NFS4ERR_OPENMODE: return ERROR_ACCESS_DENIED; + case NFS4ERR_LOCK_NOTSUPP: return ERROR_ATOMIC_LOCKS_NOT_SUPPORTED; + + case NFS4ERR_BADCHAR: + case NFS4ERR_BADNAME: return ERROR_INVALID_NAME; + + case NFS4ERR_NOTDIR: + case NFS4ERR_ISDIR: + case NFS4ERR_SYMLINK: + case NFS4ERR_WRONG_TYPE: return ERROR_INVALID_PARAMETER; + + case NFS4ERR_EXPIRED: + case NFS4ERR_NOFILEHANDLE: + case NFS4ERR_OLD_STATEID: + case NFS4ERR_BAD_STATEID: + case NFS4ERR_ADMIN_REVOKED: return ERROR_FILE_INVALID; + + case NFS4ERR_WRONGSEC: return ERROR_ACCESS_DENIED; + + default: + dprintf(1, "nfs error %s not mapped to windows error; " + "returning default error %d\n", + nfs_error_string(status), default_error); + return default_error; + } +} + +int map_symlink_errors(int status) +{ + switch (status) { + case NFS4ERR_BADCHAR: + case NFS4ERR_BADNAME: return ERROR_INVALID_REPARSE_DATA; + case NFS4ERR_WRONG_TYPE: return ERROR_NOT_A_REPARSE_POINT; + case NFS4ERR_ACCESS: return ERROR_ACCESS_DENIED; + case NFS4ERR_NOTEMPTY: return ERROR_NOT_EMPTY; + default: return nfs_to_windows_error(status, ERROR_BAD_NET_RESP); + } +} + +bool_t next_component( + IN const char *path, + IN const char *path_end, + OUT nfs41_component *component) +{ + const char *component_end; + component->name = next_non_delimiter(path, path_end); + component_end = next_delimiter(component->name, path_end); + component->len = (unsigned short)(component_end - component->name); + return component->len > 0; +} + +bool_t last_component( + IN const char *path, + IN const char *path_end, + OUT nfs41_component *component) +{ + const char *component_end = prev_delimiter(path_end, path); + component->name = prev_non_delimiter(component_end, path); + component->name = prev_delimiter(component->name, path); + component->name = next_non_delimiter(component->name, component_end); + component->len = (unsigned short)(component_end - component->name); + return component->len > 0; +} + +bool_t is_last_component( + IN const char *path, + IN const char *path_end) +{ + path = next_delimiter(path, path_end); + return next_non_delimiter(path, path_end) == path_end; +} + +void abs_path_copy( + OUT nfs41_abs_path *dst, + IN const nfs41_abs_path *src) +{ + dst->len = src->len; + StringCchCopyNA(dst->path, NFS41_MAX_PATH_LEN, src->path, dst->len); +} + +void path_fh_init( + OUT nfs41_path_fh *file, + IN nfs41_abs_path *path) +{ + file->path = path; + last_component(path->path, path->path + path->len, &file->name); +} + +void fh_copy( + OUT nfs41_fh *dst, + IN const nfs41_fh *src) +{ + dst->fileid = src->fileid; + dst->superblock = src->superblock; + dst->len = src->len; + memcpy(dst->fh, src->fh, dst->len); +} + +void path_fh_copy( + OUT nfs41_path_fh *dst, + IN const nfs41_path_fh *src) +{ + dst->path = src->path; + if (dst->path) { + const size_t name_start = src->name.name - src->path->path; + dst->name.name = dst->path->path + name_start; + dst->name.len = src->name.len; + } else { + dst->name.name = NULL; + dst->name.len = 0; + } + fh_copy(&dst->fh, &src->fh); +} + +int create_silly_rename( + IN nfs41_abs_path *path, + IN const nfs41_fh *fh, + OUT nfs41_component *silly) +{ + HCRYPTPROV context; + HCRYPTHASH hash; + PBYTE buffer; + DWORD length; + const char *end = path->path + NFS41_MAX_PATH_LEN; + const unsigned short extra_len = 2 + 16; //md5 is 16 + char name[NFS41_MAX_COMPONENT_LEN+1]; + unsigned char fhmd5[17] = { 0 }; + char *tmp; + int status = NO_ERROR, i; + + if (path->len + extra_len >= NFS41_MAX_PATH_LEN) { + status = ERROR_FILENAME_EXCED_RANGE; + goto out; + } + + /* set up the md5 hash generator */ + if (!CryptAcquireContext(&context, NULL, NULL, + PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { + status = GetLastError(); + eprintf("CryptAcquireContext() failed with %d\n", status); + goto out; + } + if (!CryptCreateHash(context, CALG_MD5, 0, 0, &hash)) { + status = GetLastError(); + eprintf("CryptCreateHash() failed with %d\n", status); + goto out_context; + } + + if (!CryptHashData(hash, (const BYTE*)fh->fh, (DWORD)fh->len, 0)) { + status = GetLastError(); + eprintf("CryptHashData() failed with %d\n", status); + goto out_hash; + } + + /* extract the hash buffer */ + buffer = (PBYTE)fhmd5; + length = 16; + if (!CryptGetHashParam(hash, HP_HASHVAL, buffer, &length, 0)) { + status = GetLastError(); + eprintf("CryptGetHashParam(val) failed with %d\n", status); + goto out_hash; + } + + last_component(path->path, path->path + path->len, silly); + StringCchCopyNA(name, NFS41_MAX_COMPONENT_LEN+1, silly->name, silly->len); + + tmp = (char*)silly->name; + StringCchPrintf(tmp, end - tmp, ".%s.", name); + tmp += silly->len + 2; + + for (i = 0; i < 16; i++, tmp++) + StringCchPrintf(tmp, end - tmp, "%x", fhmd5[i]); + + path->len = path->len + extra_len; + silly->len = silly->len + extra_len; + +out_hash: + CryptDestroyHash(hash); +out_context: + CryptReleaseContext(context, 0); +out: + return status; +} diff --git a/reactos/base/services/nfsd/util.h b/reactos/base/services/nfsd/util.h new file mode 100644 index 00000000000..735c91d7d3c --- /dev/null +++ b/reactos/base/services/nfsd/util.h @@ -0,0 +1,288 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#ifndef __NFS41_DAEMON_UTIL_H__ +#define __NFS41_DAEMON_UTIL_H__ + +#include "nfs41_types.h" +#include "from_kernel.h" + +extern DWORD NFS41D_VERSION; +struct __nfs41_session; +struct __nfs41_write_verf; +enum stable_how4; + +int safe_read(unsigned char **pos, uint32_t *remaining, void *dest, uint32_t dest_len); +int safe_write(unsigned char **pos, uint32_t *remaining, void *dest, uint32_t dest_len); +int get_name(unsigned char **pos, uint32_t *remaining, const char **out_name); + +const char* strip_path( + IN const char *path, + OUT uint32_t *len_out OPTIONAL); + +uint32_t max_read_size( + IN const struct __nfs41_session *session, + IN const nfs41_fh *fh); +uint32_t max_write_size( + IN const struct __nfs41_session *session, + IN const nfs41_fh *fh); + +bool_t verify_write( + IN nfs41_write_verf *verf, + IN OUT enum stable_how4 *stable); +bool_t verify_commit( + IN nfs41_write_verf *verf); + +/* bitmap4 */ +static __inline bool_t bitmap_isset( + IN const bitmap4 *mask, + IN uint32_t word, + IN uint32_t flag) +{ + return mask->count > word && mask->arr[word] & flag; +} +static __inline void bitmap_set( + IN bitmap4 *mask, + IN uint32_t word, + IN uint32_t flag) +{ + if (mask->count > word) + mask->arr[word] |= flag; + else { + mask->count = word + 1; + mask->arr[word] = flag; + } +} +static __inline void bitmap_unset( + IN bitmap4 *mask, + IN uint32_t word, + IN uint32_t flag) +{ + if (mask->count > word) { + mask->arr[word] &= ~flag; + while (mask->count && mask->arr[mask->count-1] == 0) + mask->count--; + } +} +static __inline void bitmap_intersect( + IN bitmap4 *dst, + IN const bitmap4 *src) +{ + uint32_t i, count = 0; + for (i = 0; i < 3; i++) { + dst->arr[i] &= src->arr[i]; + if (dst->arr[i]) + count = i+1; + } + dst->count = min(dst->count, count); +} + +ULONG nfs_file_info_to_attributes( + IN const nfs41_file_info *info); +void nfs_to_basic_info( + IN const nfs41_file_info *info, + OUT PFILE_BASIC_INFO basic_out); +void nfs_to_standard_info( + IN const nfs41_file_info *info, + OUT PFILE_STANDARD_INFO std_out); +void nfs_to_network_openinfo( + IN const nfs41_file_info *info, + OUT PFILE_NETWORK_OPEN_INFORMATION std_out); + +/* http://msdn.microsoft.com/en-us/library/ms724290%28VS.85%29.aspx: + * A file time is a 64-bit value that represents the number of + * 100-nanosecond intervals that have elapsed since 12:00 A.M. + * January 1, 1601 Coordinated Universal Time (UTC). */ +#define FILETIME_EPOCH 116444736000000000LL + +static __inline void file_time_to_nfs_time( + IN const PLARGE_INTEGER file_time, + OUT nfstime4 *nfs_time) +{ + LONGLONG diff = file_time->QuadPart - FILETIME_EPOCH; + nfs_time->seconds = diff / 10000000; + nfs_time->nseconds = (uint32_t)((diff % 10000000)*100); +} + +static __inline void nfs_time_to_file_time( + IN const nfstime4 *nfs_time, + OUT PLARGE_INTEGER file_time) +{ + file_time->QuadPart = FILETIME_EPOCH + + nfs_time->seconds * 10000000 + + nfs_time->nseconds / 100; +} + +void get_file_time( + OUT PLARGE_INTEGER file_time); +void get_nfs_time( + OUT nfstime4 *nfs_time); + +static __inline void nfstime_normalize( + IN OUT nfstime4 *nfstime) +{ + /* return time in normalized form (0 <= nsec < 1s) */ + while ((int32_t)nfstime->nseconds < 0) { + nfstime->nseconds += 1000000000; + nfstime->seconds--; + } +} +static __inline void nfstime_diff( + IN const nfstime4 *lhs, + IN const nfstime4 *rhs, + OUT nfstime4 *result) +{ + /* result = lhs - rhs */ + result->seconds = lhs->seconds - rhs->seconds; + result->nseconds = lhs->nseconds - rhs->nseconds; + nfstime_normalize(result); +} +static __inline void nfstime_abs( + IN const nfstime4 *nt, + OUT nfstime4 *result) +{ + if (nt->seconds < 0) { + const nfstime4 zero = { 0, 0 }; + nfstime_diff(&zero, nt, result); /* result = 0 - nt */ + } else if (result != nt) + memcpy(result, nt, sizeof(nfstime4)); +} + + +int create_silly_rename( + IN nfs41_abs_path *path, + IN const nfs41_fh *fh, + OUT nfs41_component *silly); + +bool_t multi_addr_find( + IN const multi_addr4 *addrs, + IN const netaddr4 *addr, + OUT OPTIONAL uint32_t *index_out); + +/* nfs_to_windows_error + * Returns a windows ERROR_ code corresponding to the given NFS4ERR_ status. + * If the status is outside the range of valid NFS4ERR_ values, it is returned + * unchanged. Otherwise, if the status does not match a value in the mapping, + * a debug warning is generated and the default_error value is returned. + */ +int nfs_to_windows_error(int status, int default_error); + +int map_symlink_errors(int status); + +#ifndef __REACTOS__ +__inline uint32_t align8(uint32_t offset) { +#else +FORCEINLINE uint32_t align8(uint32_t offset) { +#endif + return 8 + ((offset - 1) & ~7); +} +#ifndef __REACTOS__ +__inline uint32_t align4(uint32_t offset) { +#else +FORCEINLINE uint32_t align4(uint32_t offset) { +#endif + return 4 + ((offset - 1) & ~3); +} + +/* path parsing */ +#ifndef __REACTOS__ +__inline int is_delimiter(char c) { +#else +FORCEINLINE int is_delimiter(char c) { +#endif + return c == '\\' || c == '/' || c == '\0'; +} +#ifndef __REACTOS__ +__inline const char* next_delimiter(const char *pos, const char *end) { +#else +FORCEINLINE const char* next_delimiter(const char *pos, const char *end) { +#endif + while (pos < end && !is_delimiter(*pos)) + pos++; + return pos; +} +#ifndef __REACTOS__ +__inline const char* prev_delimiter(const char *pos, const char *start) { +#else +FORCEINLINE const char* prev_delimiter(const char *pos, const char *start) { +#endif + while (pos > start && !is_delimiter(*pos)) + pos--; + return pos; +} +#ifndef __REACTOS__ +__inline const char* next_non_delimiter(const char *pos, const char *end) { +#else +FORCEINLINE const char* next_non_delimiter(const char *pos, const char *end) { +#endif + while (pos < end && is_delimiter(*pos)) + pos++; + return pos; +} +#ifndef __REACTOS__ +__inline const char* prev_non_delimiter(const char *pos, const char *start) { +#else +FORCEINLINE const char* prev_non_delimiter(const char *pos, const char *start) { +#endif + while (pos > start && is_delimiter(*pos)) + pos--; + return pos; +} + +bool_t next_component( + IN const char *path, + IN const char *path_end, + OUT nfs41_component *component); + +bool_t last_component( + IN const char *path, + IN const char *path_end, + OUT nfs41_component *component); + +bool_t is_last_component( + IN const char *path, + IN const char *path_end); + +void abs_path_copy( + OUT nfs41_abs_path *dst, + IN const nfs41_abs_path *src); + +void path_fh_init( + OUT nfs41_path_fh *file, + IN nfs41_abs_path *path); + +void fh_copy( + OUT nfs41_fh *dst, + IN const nfs41_fh *src); + +void path_fh_copy( + OUT nfs41_path_fh *dst, + IN const nfs41_path_fh *src); + +#ifndef __REACTOS__ +__inline int valid_handle(HANDLE handle) { +#else +FORCEINLINE int valid_handle(HANDLE handle) { +#endif + return handle != INVALID_HANDLE_VALUE && handle != 0; +} + +#endif /* !__NFS41_DAEMON_UTIL_H__ */ diff --git a/reactos/base/services/nfsd/volume.c b/reactos/base/services/nfsd/volume.c new file mode 100644 index 00000000000..74736775cf4 --- /dev/null +++ b/reactos/base/services/nfsd/volume.c @@ -0,0 +1,176 @@ +/* NFSv4.1 client for Windows + * Copyright © 2012 The Regents of the University of Michigan + * + * Olga Kornievskaia + * Casey Bodley + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * without any warranty; without even the implied warranty of merchantability + * or fitness for a particular purpose. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA + */ + +#include +#include +#include +#include + +#include "nfs41_ops.h" +#include "from_kernel.h" +#include "upcall.h" +#include "util.h" +#include "daemon_debug.h" + + +/* windows volume queries want size in 'units', so we have to + * convert the nfs space_* attributes from bytes to units */ +#define SECTORS_PER_UNIT 8 +#define BYTES_PER_SECTOR 512 +#define BYTES_PER_UNIT (SECTORS_PER_UNIT * BYTES_PER_SECTOR) + +#define TO_UNITS(bytes) (bytes / BYTES_PER_UNIT) + +#define VOLUME_CACHE_EXPIRATION 20 + + +/* NFS41_VOLUME_QUERY */ +static int parse_volume(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall) +{ + int status; + volume_upcall_args *args = &upcall->args.volume; + + status = safe_read(&buffer, &length, &args->query, sizeof(FS_INFORMATION_CLASS)); + if (status) goto out; + + dprintf(1, "parsing NFS41_VOLUME_QUERY: query=%d\n", args->query); +out: + return status; +} + +static int get_volume_size_info( + IN nfs41_open_state *state, + IN const char *query, + OUT OPTIONAL PLONGLONG total_out, + OUT OPTIONAL PLONGLONG user_out, + OUT OPTIONAL PLONGLONG avail_out) +{ + nfs41_file_info info = { 0 }; + nfs41_superblock *superblock = state->file.fh.superblock; + int status = ERROR_NOT_FOUND; + + AcquireSRWLockShared(&superblock->lock); + /* check superblock for cached attributes */ + if (time(NULL) <= superblock->cache_expiration) { + info.space_total = superblock->space_total; + info.space_avail = superblock->space_avail; + info.space_free = superblock->space_free; + status = NO_ERROR; + + dprintf(2, "%s cached: %llu user, %llu free of %llu total\n", + query, info.space_avail, info.space_free, info.space_total); + } + ReleaseSRWLockShared(&superblock->lock); + + if (status) { + bitmap4 attr_request = { 2, { 0, FATTR4_WORD1_SPACE_AVAIL | + FATTR4_WORD1_SPACE_FREE | FATTR4_WORD1_SPACE_TOTAL } }; + + /* query the space_ attributes of the filesystem */ + status = nfs41_getattr(state->session, &state->file, + &attr_request, &info); + if (status) { + eprintf("nfs41_getattr() failed with %s\n", + nfs_error_string(status)); + status = nfs_to_windows_error(status, ERROR_BAD_NET_RESP); + goto out; + } + + AcquireSRWLockExclusive(&superblock->lock); + superblock->space_total = info.space_total; + superblock->space_avail = info.space_avail; + superblock->space_free = info.space_free; + superblock->cache_expiration = time(NULL) + VOLUME_CACHE_EXPIRATION; + ReleaseSRWLockExclusive(&superblock->lock); + + dprintf(2, "%s: %llu user, %llu free of %llu total\n", + query, info.space_avail, info.space_free, info.space_total); + } + + if (total_out) *total_out = TO_UNITS(info.space_total); + if (user_out) *user_out = TO_UNITS(info.space_avail); + if (avail_out) *avail_out = TO_UNITS(info.space_free); +out: + return status; +} + +static int handle_volume(nfs41_upcall *upcall) +{ + volume_upcall_args *args = &upcall->args.volume; + int status = NO_ERROR; + + switch (args->query) { + case FileFsSizeInformation: + args->len = sizeof(args->info.size); + args->info.size.SectorsPerAllocationUnit = SECTORS_PER_UNIT; + args->info.size.BytesPerSector = BYTES_PER_SECTOR; + + status = get_volume_size_info(upcall->state_ref, + "FileFsSizeInformation", + &args->info.size.TotalAllocationUnits.QuadPart, + &args->info.size.AvailableAllocationUnits.QuadPart, + NULL); + break; + + case FileFsFullSizeInformation: + args->len = sizeof(args->info.fullsize); + args->info.fullsize.SectorsPerAllocationUnit = SECTORS_PER_UNIT; + args->info.fullsize.BytesPerSector = BYTES_PER_SECTOR; + + status = get_volume_size_info(upcall->state_ref, + "FileFsFullSizeInformation", + &args->info.fullsize.TotalAllocationUnits.QuadPart, + &args->info.fullsize.CallerAvailableAllocationUnits.QuadPart, + &args->info.fullsize.ActualAvailableAllocationUnits.QuadPart); + break; + + case FileFsAttributeInformation: + args->len = sizeof(args->info.attribute); + nfs41_superblock_fs_attributes(upcall->state_ref->file.fh.superblock, + &args->info.attribute); + break; + + default: + eprintf("unhandled fs query class %d\n", args->query); + status = ERROR_INVALID_PARAMETER; + break; + } + return status; +} + +static int marshall_volume(unsigned char *buffer, uint32_t *length, nfs41_upcall *upcall) +{ + int status; + volume_upcall_args *args = &upcall->args.volume; + + status = safe_write(&buffer, length, &args->len, sizeof(args->len)); + if (status) goto out; + status = safe_write(&buffer, length, &args->info, args->len); +out: + return status; +} + + +const nfs41_upcall_op nfs41_op_volume = { + parse_volume, + handle_volume, + marshall_volume +};