Import the nfsd deamon from the nfs41 project.
CORE-8204
svn path=/trunk/; revision=75114
add_subdirectory(audiosrv)
add_subdirectory(dhcpcsvc)
add_subdirectory(eventlog)
+add_subdirectory(nfsd)
add_subdirectory(rpcss)
add_subdirectory(schedsvc)
add_subdirectory(shsvcs)
--- /dev/null
+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)
--- /dev/null
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <strsafe.h>
+#include <sddl.h>
+
+#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:"<null>");
+ 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:"<null>");
+ 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:"<null>");
+ 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
--- /dev/null
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <strsafe.h>
+
+#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;
+}
--- /dev/null
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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;
+}
--- /dev/null
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <stdio.h>
+
+#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");
+}
--- /dev/null
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <stdlib.h>
+# include <crtdbg.h>
+#else
+# include <stdlib.h>
+#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
--- /dev/null
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <devioctl.h>
+#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;
+}
--- /dev/null
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 */
--- /dev/null
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <stdio.h>
+#include <strsafe.h>
+
+#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
+};
--- /dev/null
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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
--- /dev/null
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <stdio.h>
+#include <strsafe.h>
+
+#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
+};
--- /dev/null
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <strsafe.h>
+#include <winldap.h>
+#include <stdlib.h> /* for strtoul() */
+#include <errno.h>
+#include <time.h>
+
+#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;
+}
--- /dev/null
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 */
--- /dev/null
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 */
--- /dev/null
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <stdio.h>
+
+#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
+};
--- /dev/null
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <strsafe.h>
+#include <time.h>
+
+#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;
+}
--- /dev/null
+#
+# 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
+
--- /dev/null
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <strsafe.h>
+#include <stdio.h>
+
+#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
+};
--- /dev/null
+# 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"
--- /dev/null
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <strsafe.h>
+#include <time.h>
+#include <assert.h>
+
+#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;
+}
--- /dev/null
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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__ */
--- /dev/null
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <strsafe.h>
+
+#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;
+}
--- /dev/null
+#
+# The network configuration file. This file is currently only used in
+# conjunction with the TI-RPC code in the libtirpc library.
+#
+# Entries consist of:
+#
+# <network_id> <semantics> <flags> <protofamily> <protoname> \
+# <device> <nametoaddr_libs>
+#
+# The <device> and <nametoaddr_libs> 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 - - -
--- /dev/null
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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__ */
--- /dev/null
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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__ */
--- /dev/null
+/* NFSv4.1 client for Windows
+ * Copyright © 2012 The Regents of the University of Michigan
+ *
+ * Olga Kornievskaia <aglo@umich.edu>
+ * Casey Bodley <cbodley@umich.edu>
+ *
+ * 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 <windows.h>
+#include <stdio.h>
+#include <time.h>
+#include <