diff --git a/daemon/open.c b/daemon/open.c index 49862c2..c5008b7 100644 --- a/daemon/open.c +++ b/daemon/open.c @@ -119,7 +119,7 @@ out: return status; } -static BOOLEAN do_lookup(uint32_t type, ULONG access_mask, ULONG disposition) +static BOOLEAN do_lookup(uint32_t type, ULONG access_mask, ULONG disposition) { if (type == NF4DIR) { if (disposition == FILE_OPEN || disposition == FILE_OVERWRITE) { @@ -132,10 +132,10 @@ static BOOLEAN do_lookup(uint32_t type, ULONG access_mask, ULONG disposition) } if ((access_mask & FILE_READ_DATA) || - (access_mask & FILE_WRITE_DATA) || - (access_mask & FILE_APPEND_DATA) || - (access_mask & FILE_EXECUTE)) - return FALSE; + (access_mask & FILE_WRITE_DATA) || + (access_mask & FILE_APPEND_DATA) || + (access_mask & FILE_EXECUTE)) + return FALSE; else { dprintf(1, "Open call that wants to manage attributes\n"); return TRUE; @@ -274,7 +274,35 @@ int handle_open(nfs41_upcall *upcall) state->type = info.type; } else if (status != ERROR_FILE_NOT_FOUND) goto out_free_state; - if (do_lookup(state->type, args->access_mask, args->disposition)) { + + /* XXX: this is a hard-coded check for the open arguments we see from + * the CreateSymbolicLink() system call. we respond to this by deferring + * the CREATE until we get the upcall to set the symlink. this approach + * is troublesome for two reasons: + * -an application might use these exact arguments to create a normal + * file, and we would return success without actually creating it + * -an application could create a symlink by sending the FSCTL to set + * the reparse point manually, and their open might be different. in + * this case we'd create the file on open, and need to remove it + * before creating the symlink */ + if (args->disposition == FILE_CREATE && + args->access_mask == (FILE_WRITE_ATTRIBUTES | SYNCHRONIZE | DELETE) && + args->access_mode == 0 && + args->create_opts & FILE_OPEN_REPARSE_POINT) { + /* fail if the file already exists */ + uint32_t create; + status = map_disposition_2_nfsopen(args->disposition, status, + &create, &upcall->last_error); + if (status) + goto out_free_state; + + /* defer the call to CREATE until we get the symlink set upcall */ + dprintf(1, "trying to create a symlink, deferring create\n"); + + /* because of WRITE_ATTR access, be prepared for a setattr upcall; + * will crash if the superblock is null, so use the parent's */ + state->file.fh.superblock = state->parent.fh.superblock; + } else if (do_lookup(state->type, args->access_mask, args->disposition)) { if (status) { dprintf(1, "nfs41_lookup failed with %d\n", status); goto out_free_state; diff --git a/daemon/symlink.c b/daemon/symlink.c index fc6c932..fb7bcd6 100644 --- a/daemon/symlink.c +++ b/daemon/symlink.c @@ -188,6 +188,12 @@ int handle_symlink(nfs41_upcall *upcall) int status = NO_ERROR; if (args->set) { + if (state->file.fh.len) { + /* the check in handle_open() didn't catch that we're creating + * a symlink, so we have to remove the file it already created */ + nfs41_remove(state->session, &state->parent, &state->file.name); + } + /* create the symlink */ status = nfs41_create(state->session, NF4LNK, 0777, args->target_set, &state->parent, &state->file); diff --git a/daemon/util.c b/daemon/util.c index 61b87ab..6a3f207 100644 --- a/daemon/util.c +++ b/daemon/util.c @@ -311,6 +311,7 @@ int nfs_to_windows_error(int status, int default_error) case NFS4ERR_SYMLINK: case NFS4ERR_WRONG_TYPE: return ERROR_INVALID_PARAMETER; + case NFS4ERR_NOFILEHANDLE: case NFS4ERR_OLD_STATEID: case NFS4ERR_BAD_STATEID: case NFS4ERR_ADMIN_REVOKED: return ERROR_FILE_INVALID; diff --git a/sys/nfs41_driver.c b/sys/nfs41_driver.c index 834ebfa..669ab7c 100644 --- a/sys/nfs41_driver.c +++ b/sys/nfs41_driver.c @@ -3638,6 +3638,7 @@ static NTSTATUS map_setfile_error(DWORD error) case ERROR_FILE_NOT_FOUND: return STATUS_OBJECT_NAME_NOT_FOUND; case ERROR_PATH_NOT_FOUND: return STATUS_OBJECT_PATH_NOT_FOUND; case ERROR_ACCESS_DENIED: return STATUS_ACCESS_DENIED; + case ERROR_FILE_INVALID: return STATUS_FILE_INVALID; case ERROR_NOT_SAME_DEVICE: return STATUS_NOT_SAME_DEVICE; case ERROR_NOT_SUPPORTED: return STATUS_NOT_IMPLEMENTED; case ERROR_NETWORK_ACCESS_DENIED: return STATUS_NETWORK_ACCESS_DENIED;