diff --git a/daemon/open.c b/daemon/open.c index 6006610..de3850e 100644 --- a/daemon/open.c +++ b/daemon/open.c @@ -256,6 +256,23 @@ static int open_or_delegate( } +static int parse_abs_path(unsigned char **buffer, uint32_t *length, nfs41_abs_path *path) +{ + int status = safe_read(buffer, length, &path->len, sizeof(USHORT)); + if (status) goto out; + if (path->len == 0) + goto out; + if (path->len >= NFS41_MAX_PATH_LEN) { + status = ERROR_BUFFER_OVERFLOW; + goto out; + } + status = safe_read(buffer, length, path->path, path->len); + if (status) goto out; + path->len--; /* subtract 1 for null */ +out: + return status; +} + /* NFS41_OPEN */ static int parse_open(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall) { @@ -280,15 +297,18 @@ static int parse_open(unsigned char *buffer, uint32_t length, nfs41_upcall *upca if (status) goto out; status = safe_read(&buffer, &length, &args->srv_open, sizeof(HANDLE)); if (status) goto out; + status = parse_abs_path(&buffer, &length, &args->symlink); + if (status) goto out; status = safe_read(&buffer, &length, &args->ea, sizeof(HANDLE)); if (status) goto out; dprintf(1, "parsing NFS41_OPEN: filename='%s' access mask=%d " "access mode=%d\n\tfile attrs=0x%x create attrs=0x%x " "(kernel) disposition=%d\n\topen_owner_id=%d mode=%o " - "srv_open=%p ea=%p\n", args->path, args->access_mask, args->access_mode, - args->file_attrs, args->create_opts, args->disposition, - args->open_owner_id, args->mode, args->srv_open, args->ea); + "srv_open=%p symlink=%s ea=%p\n", args->path, args->access_mask, + args->access_mode, args->file_attrs, args->create_opts, + args->disposition, args->open_owner_id, args->mode, args->srv_open, + args->symlink.path, args->ea); print_disposition(2, args->disposition); print_access_mask(2, args->access_mask); print_share_mode(2, args->access_mode); @@ -590,6 +610,29 @@ static int handle_open(nfs41_upcall *upcall) state->file.fh.superblock = state->parent.fh.superblock; status = NO_ERROR; + } else if (args->symlink.len) { + /* handle cygwin symlinks */ + nfs41_file_info createattrs; + createattrs.attrmask.count = 2; + createattrs.attrmask.arr[0] = 0; + createattrs.attrmask.arr[1] = FATTR4_WORD1_MODE; + createattrs.mode = 0777; + + dprintf(1, "creating cygwin symlink %s -> %s\n", + state->file.name.name, args->symlink.path); + + status = nfs41_create(state->session, NF4LNK, &createattrs, + args->symlink.path, &state->parent, &state->file, &info); + if (status) { + eprintf("nfs41_create() for symlink=%s failed with %s\n", + args->symlink.path, nfs_error_string(status)); + status = map_symlink_errors(status); + goto out_free_state; + } + nfs_to_basic_info(&info, &args->basic_info); + nfs_to_standard_info(&info, &args->std_info); + args->mode = info.mode; + args->changeattr = info.change; } else if (open_for_attributes(state->type, args->access_mask, args->disposition, status)) { if (status) { diff --git a/daemon/symlink.c b/daemon/symlink.c index 0a33e3f..9709e4f 100644 --- a/daemon/symlink.c +++ b/daemon/symlink.c @@ -208,18 +208,6 @@ out: return status; } -static int map_symlink_errors(int status) -{ - switch (status) { - case NFS4ERR_BADCHAR: - case NFS4ERR_BADNAME: return ERROR_INVALID_REPARSE_DATA; - case NFS4ERR_WRONG_TYPE: return ERROR_NOT_A_REPARSE_POINT; - case NFS4ERR_ACCESS: return ERROR_ACCESS_DENIED; - case NFS4ERR_NOTEMPTY: return ERROR_NOT_EMPTY; - default: return nfs_to_windows_error(status, ERROR_BAD_NET_RESP); - } -} - static int handle_symlink(nfs41_upcall *upcall) { symlink_upcall_args *args = &upcall->args.symlink; @@ -250,7 +238,7 @@ static int handle_symlink(nfs41_upcall *upcall) } /* create the symlink */ - createattrs.attrmask.count = 1; + createattrs.attrmask.count = 2; createattrs.attrmask.arr[0] = 0; createattrs.attrmask.arr[1] = FATTR4_WORD1_MODE; createattrs.mode = 0777; diff --git a/daemon/util.c b/daemon/util.c index a1cabed..35cfc5b 100644 --- a/daemon/util.c +++ b/daemon/util.c @@ -290,6 +290,18 @@ int nfs_to_windows_error(int status, int default_error) } } +int map_symlink_errors(int status) +{ + switch (status) { + case NFS4ERR_BADCHAR: + case NFS4ERR_BADNAME: return ERROR_INVALID_REPARSE_DATA; + case NFS4ERR_WRONG_TYPE: return ERROR_NOT_A_REPARSE_POINT; + case NFS4ERR_ACCESS: return ERROR_ACCESS_DENIED; + case NFS4ERR_NOTEMPTY: return ERROR_NOT_EMPTY; + default: return nfs_to_windows_error(status, ERROR_BAD_NET_RESP); + } +} + bool_t next_component( IN const char *path, IN const char *path_end, diff --git a/daemon/util.h b/daemon/util.h index f9baaed..fa0d32c 100644 --- a/daemon/util.h +++ b/daemon/util.h @@ -185,6 +185,7 @@ bool_t multi_addr_find( */ int nfs_to_windows_error(int status, int default_error); +int map_symlink_errors(int status); __inline uint32_t align8(uint32_t offset) { return 8 + ((offset - 1) & ~7); diff --git a/sys/nfs41_driver.c b/sys/nfs41_driver.c index 67b8baf..6bba83a 100644 --- a/sys/nfs41_driver.c +++ b/sys/nfs41_driver.c @@ -527,6 +527,13 @@ NTSTATUS marshall_unicode_as_utf8( ULONG ActualCount; NTSTATUS status; + if (str->Length == 0) { + status = STATUS_SUCCESS; + ActualCount = 0; + ansi.MaximumLength = 1; + goto out_copy; + } + /* query the number of bytes required for the utf8 encoding */ status = RtlUnicodeToUTF8N(NULL, 0xffff, &ActualCount, str->Buffer, str->Length); @@ -547,6 +554,7 @@ NTSTATUS marshall_unicode_as_utf8( goto out; } +out_copy: RtlCopyMemory(*pos, &ansi.MaximumLength, sizeof(ansi.MaximumLength)); *pos += sizeof(ansi.MaximumLength); (*pos)[ActualCount] = '\0'; @@ -683,7 +691,8 @@ NTSTATUS marshal_nfs41_open( else tmp += *len; header_len = *len + length_as_utf8(entry->u.Open.filename) + - 5 * sizeof(ULONG) + sizeof(LONG) + sizeof(DWORD) + 2 * sizeof(HANDLE); + 7 * sizeof(ULONG) + 2 * sizeof(HANDLE) + + length_as_utf8(&entry->u.Open.symlink); if (header_len > buf_len) { status = STATUS_INSUFFICIENT_RESOURCES; goto out; @@ -709,6 +718,8 @@ NTSTATUS marshal_nfs41_open( tmp += sizeof(DWORD); RtlCopyMemory(tmp, &entry->u.Open.srv_open, sizeof(HANDLE)); tmp += sizeof(HANDLE); + status = marshall_unicode_as_utf8(&tmp, &entry->u.Open.symlink); + if (status) goto out; __try { if (entry->u.Open.EaMdl) { @@ -3408,6 +3419,7 @@ NTSTATUS map_open_errors( case ERROR_ACCESS_DENIED: if (len > 0) return STATUS_ACCESS_DENIED; else return STATUS_SUCCESS; + case ERROR_INVALID_REPARSE_DATA: case ERROR_INVALID_NAME: return STATUS_OBJECT_NAME_INVALID; case ERROR_FILE_EXISTS: return STATUS_OBJECT_NAME_COLLISION; case ERROR_FILE_INVALID: return STATUS_FILE_INVALID; @@ -3455,7 +3467,7 @@ static BOOLEAN create_should_pass_ea( IN PFILE_FULL_EA_INFORMATION ea, IN ULONG disposition) { - /* don't pass cygwin EAs (until we support NfsSymlinkTargetName) */ + /* don't pass cygwin EAs */ if (AnsiStrEq(&NfsV3Attributes, ea->EaName, ea->EaNameLength) || AnsiStrEq(&NfsActOnLink, ea->EaName, ea->EaNameLength) || AnsiStrEq(&NfsSymlinkTargetName, ea->EaName, ea->EaNameLength)) @@ -3654,7 +3666,12 @@ NTSTATUS nfs41_Create( if (params->FileAttributes & FILE_ATTRIBUTE_READONLY) entry->u.Open.mode = 0444; } - + if (entry->u.Open.disp == FILE_CREATE && ea && + AnsiStrEq(&NfsSymlinkTargetName, ea->EaName, ea->EaNameLength)) { + /* for a cygwin symlink, given as a unicode string */ + entry->u.Open.symlink.Buffer = (PWCH)(ea->EaName + ea->EaNameLength + 1); + entry->u.Open.symlink.MaximumLength = entry->u.Open.symlink.Length = ea->EaValueLength; + } if (ea && create_should_pass_ea(ea, params->Disposition)) { /* lock the extended attribute buffer for read access in user space */ entry->u.Open.EaMdl = IoAllocateMdl(ea, @@ -6353,10 +6370,12 @@ NTSTATUS check_nfs41_getreparse_args( status = STATUS_INVALID_PARAMETER; goto out; } + /* ifs reparse tests expect STATUS_INVALID_PARAMETER, + * but 'dir' passes a buffer here when querying symlinks if (FsCtl->pInputBuffer != NULL) { status = STATUS_INVALID_PARAMETER; goto out; - } + } */ if (!FsCtl->pOutputBuffer) { status = STATUS_INVALID_USER_BUFFER; goto out;