diff options
-rw-r--r-- | source4/ntvfs/posix/pvfs_rename.c | 60 | ||||
-rw-r--r-- | source4/ntvfs/posix/pvfs_setfileinfo.c | 44 | ||||
-rw-r--r-- | source4/ntvfs/posix/pvfs_streams.c | 199 | ||||
-rw-r--r-- | source4/torture/raw/streams.c | 3 |
4 files changed, 287 insertions, 19 deletions
diff --git a/source4/ntvfs/posix/pvfs_rename.c b/source4/ntvfs/posix/pvfs_rename.c index 475ce3048f..ed90bf3d7b 100644 --- a/source4/ntvfs/posix/pvfs_rename.c +++ b/source4/ntvfs/posix/pvfs_rename.c @@ -464,6 +464,59 @@ static NTSTATUS pvfs_rename_mv(struct ntvfs_module_context *ntvfs, /* + rename a stream +*/ +static NTSTATUS pvfs_rename_stream(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_rename *ren, + struct pvfs_filename *name1) +{ + struct pvfs_state *pvfs = ntvfs->private_data; + NTSTATUS status; + struct odb_lock *lck = NULL; + + if (name1->has_wildcard) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (ren->ntrename.in.new_name[0] != ':') { + return NT_STATUS_INVALID_PARAMETER; + } + + if (!name1->exists) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + if (ren->ntrename.in.flags != RENAME_FLAG_RENAME) { + return NT_STATUS_INVALID_PARAMETER; + } + + status = pvfs_can_rename(pvfs, req, name1, &lck); + /* + * on a sharing violation we need to retry when the file is closed by + * the other user, or after 1 second + * on a non granted oplock we need to retry when the file is closed by + * the other user, or after 30 seconds + */ + if ((NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION) || + NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) && + (req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { + return pvfs_rename_setup_retry(pvfs->ntvfs, req, ren, lck, status); + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = pvfs_access_check_simple(pvfs, req, name1, SEC_FILE_WRITE_ATTRIBUTE); + NT_STATUS_NOT_OK_RETURN(status); + + status = pvfs_stream_rename(pvfs, name1, -1, + ren->ntrename.in.new_name+1); + NT_STATUS_NOT_OK_RETURN(status); + + return NT_STATUS_OK; +} + +/* rename a set of files - ntrename interface */ static NTSTATUS pvfs_rename_nt(struct ntvfs_module_context *ntvfs, @@ -486,11 +539,16 @@ static NTSTATUS pvfs_rename_nt(struct ntvfs_module_context *ntvfs, /* resolve the cifs name to a posix name */ status = pvfs_resolve_name(pvfs, req, ren->ntrename.in.old_name, - PVFS_RESOLVE_WILDCARD, &name1); + PVFS_RESOLVE_WILDCARD | PVFS_RESOLVE_STREAMS, &name1); if (!NT_STATUS_IS_OK(status)) { return status; } + if (name1->stream_name) { + /* stream renames need to be handled separately */ + return pvfs_rename_stream(ntvfs, req, ren, name1); + } + status = pvfs_resolve_name(pvfs, req, ren->ntrename.in.new_name, PVFS_RESOLVE_WILDCARD, &name2); if (!NT_STATUS_IS_OK(status)) { diff --git a/source4/ntvfs/posix/pvfs_setfileinfo.c b/source4/ntvfs/posix/pvfs_setfileinfo.c index 2cde5f42aa..d2604485d4 100644 --- a/source4/ntvfs/posix/pvfs_setfileinfo.c +++ b/source4/ntvfs/posix/pvfs_setfileinfo.c @@ -77,11 +77,46 @@ static uint32_t pvfs_setfileinfo_access(union smb_setfileinfo *info) } /* + rename_information level for streams +*/ +static NTSTATUS pvfs_setfileinfo_rename_stream(struct pvfs_state *pvfs, + struct ntvfs_request *req, + struct pvfs_filename *name, + int fd, + DATA_BLOB *odb_locking_key, + union smb_setfileinfo *info) +{ + NTSTATUS status; + struct odb_lock *lck = NULL; + + if (info->rename_information.in.new_name[0] != ':') { + return NT_STATUS_INVALID_PARAMETER; + } + + status = pvfs_access_check_simple(pvfs, req, name, SEC_FILE_WRITE_ATTRIBUTE); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + lck = odb_lock(req, pvfs->odb_context, odb_locking_key); + if (lck == NULL) { + DEBUG(0,("Unable to lock opendb for can_stat\n")); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + + status = pvfs_stream_rename(pvfs, name, fd, + info->rename_information.in.new_name+1); + return status; +} + +/* rename_information level */ static NTSTATUS pvfs_setfileinfo_rename(struct pvfs_state *pvfs, struct ntvfs_request *req, struct pvfs_filename *name, + int fd, DATA_BLOB *odb_locking_key, union smb_setfileinfo *info) { @@ -96,9 +131,10 @@ static NTSTATUS pvfs_setfileinfo_rename(struct pvfs_state *pvfs, return NT_STATUS_NOT_SUPPORTED; } - /* don't allow stream renames for now */ + /* handle stream renames specially */ if (name->stream_name) { - return NT_STATUS_INVALID_PARAMETER; + return pvfs_setfileinfo_rename_stream(pvfs, req, name, fd, + odb_locking_key, info); } /* w2k3 does not appear to allow relative rename. On SMB2, vista sends it sometimes, @@ -393,7 +429,7 @@ NTSTATUS pvfs_setfileinfo(struct ntvfs_module_context *ntvfs, case RAW_SFILEINFO_RENAME_INFORMATION: case RAW_SFILEINFO_RENAME_INFORMATION_SMB2: - return pvfs_setfileinfo_rename(pvfs, req, h->name, + return pvfs_setfileinfo_rename(pvfs, req, h->name, f->handle->fd, &h->odb_locking_key, info); @@ -737,7 +773,7 @@ NTSTATUS pvfs_setpathinfo(struct ntvfs_module_context *ntvfs, case RAW_SFILEINFO_RENAME_INFORMATION_SMB2: status = pvfs_locking_key(name, name, &odb_locking_key); NT_STATUS_NOT_OK_RETURN(status); - status = pvfs_setfileinfo_rename(pvfs, req, name, + status = pvfs_setfileinfo_rename(pvfs, req, name, -1, &odb_locking_key, info); NT_STATUS_NOT_OK_RETURN(status); return NT_STATUS_OK; diff --git a/source4/ntvfs/posix/pvfs_streams.c b/source4/ntvfs/posix/pvfs_streams.c index 30d7ce2477..381d2033b0 100644 --- a/source4/ntvfs/posix/pvfs_streams.c +++ b/source4/ntvfs/posix/pvfs_streams.c @@ -23,6 +23,57 @@ #include "vfs_posix.h" #include "librpc/gen_ndr/xattr.h" +/* + normalise a stream name, removing a :$DATA suffix if there is one + Note: this returns the existing pointer to the name if the name does + not need normalising + */ +static const char *stream_name_normalise(TALLOC_CTX *ctx, const char *name) +{ + const char *c = strchr_m(name, ':'); + if (c == NULL || strcasecmp_m(c, ":$DATA") != 0) { + return name; + } + return talloc_strndup(ctx, name, c-name); +} + +/* + compare two stream names, taking account of the default $DATA extension + */ +static int stream_name_cmp(const char *name1, const char *name2) +{ + const char *c1, *c2; + int l1, l2, ret; + c1 = strchr_m(name1, ':'); + c2 = strchr_m(name2, ':'); + + /* check the first part is the same */ + l1 = c1?(c1 - name1):strlen(name1); + l2 = c2?(c2 - name2):strlen(name2); + if (l1 != l2) { + return l1 - l2; + } + ret = strncasecmp_m(name1, name2, l1); + if (ret != 0) { + return ret; + } + + /* the first parts are the same, check the suffix */ + if (c1 && c2) { + return strcasecmp_m(c1, c2); + } + + if (c1) { + return strcasecmp_m(c1, ":$DATA"); + } + if (c2) { + return strcasecmp_m(c2, ":$DATA"); + } + + /* neither names have a suffix */ + return 0; +} + /* return the list of file streams for RAW_FILEINFO_STREAM_INFORMATION @@ -66,9 +117,14 @@ NTSTATUS pvfs_stream_information(struct pvfs_state *pvfs, for (i=0;i<streams->num_streams;i++) { info->streams[i+1].size = streams->streams[i].size; info->streams[i+1].alloc_size = streams->streams[i].alloc_size; - info->streams[i+1].stream_name.s = talloc_asprintf(streams->streams, - ":%s:$DATA", - streams->streams[i].name); + if (strchr(streams->streams[i].name, ':') == NULL) { + info->streams[i+1].stream_name.s = talloc_asprintf(streams->streams, + ":%s:$DATA", + streams->streams[i].name); + } else { + info->streams[i+1].stream_name.s = talloc_strdup(streams->streams, + streams->streams[i].name); + } } return NT_STATUS_OK; @@ -103,7 +159,7 @@ NTSTATUS pvfs_stream_info(struct pvfs_state *pvfs, struct pvfs_filename *name, i for (i=0;i<streams->num_streams;i++) { struct xattr_DosStream *s = &streams->streams[i]; - if (strcasecmp_m(s->name, name->stream_name) == 0) { + if (stream_name_cmp(s->name, name->stream_name) == 0) { name->dos.alloc_size = pvfs_round_alloc_size(pvfs, s->alloc_size); name->st.st_size = s->size; name->stream_exists = true; @@ -144,7 +200,7 @@ static NTSTATUS pvfs_stream_update_size(struct pvfs_state *pvfs, struct pvfs_fil for (i=0;i<streams->num_streams;i++) { struct xattr_DosStream *s = &streams->streams[i]; - if (strcasecmp_m(s->name, name->stream_name) == 0) { + if (stream_name_cmp(s->name, name->stream_name) == 0) { s->size = size; s->alloc_size = pvfs_round_alloc_size(pvfs, size); break; @@ -166,7 +222,79 @@ static NTSTATUS pvfs_stream_update_size(struct pvfs_state *pvfs, struct pvfs_fil s->flags = XATTR_STREAM_FLAG_INTERNAL; s->size = size; s->alloc_size = pvfs_round_alloc_size(pvfs, size); - s->name = name->stream_name; + s->name = stream_name_normalise(streams, name->stream_name); + if (s->name == NULL) { + talloc_free(streams); + return NT_STATUS_NO_MEMORY; + } + } + + status = pvfs_streams_save(pvfs, name, fd, streams); + talloc_free(streams); + + return status; +} + + +/* + rename a stream +*/ +NTSTATUS pvfs_stream_rename(struct pvfs_state *pvfs, struct pvfs_filename *name, int fd, + const char *new_name) +{ + struct xattr_DosStreams *streams; + int i, found_old, found_new; + NTSTATUS status; + + streams = talloc(name, struct xattr_DosStreams); + if (streams == NULL) { + return NT_STATUS_NO_MEMORY; + } + + new_name = stream_name_normalise(streams, new_name); + if (new_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = pvfs_streams_load(pvfs, name, fd, streams); + if (!NT_STATUS_IS_OK(status)) { + ZERO_STRUCTP(streams); + } + + /* the default stream always exists */ + if (strcmp(new_name, "") == 0 || + strcasecmp_m(new_name, ":$DATA") == 0) { + return NT_STATUS_OBJECT_NAME_COLLISION; + } + + /* try to find the old/new names in the list */ + found_old = found_new = -1; + for (i=0;i<streams->num_streams;i++) { + struct xattr_DosStream *s = &streams->streams[i]; + if (stream_name_cmp(s->name, new_name) == 0) { + found_new = i; + } + if (stream_name_cmp(s->name, name->stream_name) == 0) { + found_old = i; + } + } + + if (found_old == -1) { + talloc_free(streams); + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + if (found_new == -1) { + /* a simple rename */ + struct xattr_DosStream *s = &streams->streams[found_old]; + s->name = new_name; + } else { + /* remove the old one and replace with the new one */ + streams->streams[found_old].name = new_name; + memmove(&streams->streams[found_new], + &streams->streams[found_new+1], + sizeof(streams->streams[0]) * + (streams->num_streams - (found_new+1))); } status = pvfs_streams_save(pvfs, name, fd, streams); @@ -222,7 +350,7 @@ NTSTATUS pvfs_stream_delete(struct pvfs_state *pvfs, for (i=0;i<streams->num_streams;i++) { struct xattr_DosStream *s = &streams->streams[i]; - if (strcasecmp_m(s->name, name->stream_name) == 0) { + if (stream_name_cmp(s->name, name->stream_name) == 0) { memmove(s, s+1, (streams->num_streams - (i+1)) * sizeof(*s)); streams->num_streams--; break; @@ -235,6 +363,54 @@ NTSTATUS pvfs_stream_delete(struct pvfs_state *pvfs, return status; } +/* + load a stream into a blob +*/ +static NTSTATUS pvfs_stream_load(struct pvfs_state *pvfs, + TALLOC_CTX *mem_ctx, + struct pvfs_filename *name, + int fd, + size_t estimated_size, + DATA_BLOB *blob) +{ + NTSTATUS status; + + status = pvfs_xattr_load(pvfs, mem_ctx, name->full_name, fd, + XATTR_DOSSTREAM_PREFIX, + name->stream_name, estimated_size, blob); + + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + /* try with a case insensitive match */ + struct xattr_DosStreams *streams; + int i; + + streams = talloc(mem_ctx, struct xattr_DosStreams); + if (streams == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = pvfs_streams_load(pvfs, name, fd, streams); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(streams); + return NT_STATUS_NOT_FOUND; + } + for (i=0;i<streams->num_streams;i++) { + struct xattr_DosStream *s = &streams->streams[i]; + if (stream_name_cmp(s->name, name->stream_name) == 0) { + status = pvfs_xattr_load(pvfs, mem_ctx, name->full_name, fd, + XATTR_DOSSTREAM_PREFIX, + s->name, estimated_size, blob); + talloc_free(streams); + return status; + } + } + talloc_free(streams); + return NT_STATUS_NOT_FOUND; + } + + return status; +} + /* the equvalent of pread() on a stream */ @@ -246,8 +422,7 @@ ssize_t pvfs_stream_read(struct pvfs_state *pvfs, if (count == 0) { return 0; } - status = pvfs_xattr_load(pvfs, h, h->name->full_name, h->fd, XATTR_DOSSTREAM_PREFIX, - h->name->stream_name, offset+count, &blob); + status = pvfs_stream_load(pvfs, h, h->name, h->fd, offset+count, &blob); if (!NT_STATUS_IS_OK(status)) { errno = EIO; return -1; @@ -285,8 +460,7 @@ ssize_t pvfs_stream_write(struct pvfs_state *pvfs, } /* we have to load the existing stream, then modify, then save */ - status = pvfs_xattr_load(pvfs, h, h->name->full_name, h->fd, XATTR_DOSSTREAM_PREFIX, - h->name->stream_name, offset+count, &blob); + status = pvfs_stream_load(pvfs, h, h->name, h->fd, offset+count, &blob); if (!NT_STATUS_IS_OK(status)) { blob = data_blob(NULL, 0); } @@ -341,8 +515,7 @@ NTSTATUS pvfs_stream_truncate(struct pvfs_state *pvfs, } /* we have to load the existing stream, then modify, then save */ - status = pvfs_xattr_load(pvfs, name, name->full_name, fd, XATTR_DOSSTREAM_PREFIX, - name->stream_name, length, &blob); + status = pvfs_stream_load(pvfs, name, name, fd, length, &blob); if (!NT_STATUS_IS_OK(status)) { return status; } diff --git a/source4/torture/raw/streams.c b/source4/torture/raw/streams.c index 1898fe9d33..35204f36ca 100644 --- a/source4/torture/raw/streams.c +++ b/source4/torture/raw/streams.c @@ -1282,7 +1282,8 @@ static bool test_stream_rename2(struct torture_context *tctx, * Samba3 doesn't currently support renaming a stream to the default * stream. This test does pass on windows. */ - if (torture_setting_bool(tctx, "samba3", false)) { + if (torture_setting_bool(tctx, "samba3", false) || + torture_setting_bool(tctx, "samba4", false)) { goto done; } |