diff options
Diffstat (limited to 'source4/ntvfs')
-rw-r--r-- | source4/ntvfs/common/opendb.c | 26 | ||||
-rw-r--r-- | source4/ntvfs/common/opendb.h | 13 | ||||
-rw-r--r-- | source4/ntvfs/common/opendb_tdb.c | 321 | ||||
-rw-r--r-- | source4/ntvfs/posix/config.mk | 1 | ||||
-rw-r--r-- | source4/ntvfs/posix/pvfs_lock.c | 14 | ||||
-rw-r--r-- | source4/ntvfs/posix/pvfs_open.c | 336 | ||||
-rw-r--r-- | source4/ntvfs/posix/pvfs_oplock.c | 239 | ||||
-rw-r--r-- | source4/ntvfs/posix/pvfs_qfileinfo.c | 2 | ||||
-rw-r--r-- | source4/ntvfs/posix/pvfs_rename.c | 108 | ||||
-rw-r--r-- | source4/ntvfs/posix/pvfs_setfileinfo.c | 116 | ||||
-rw-r--r-- | source4/ntvfs/posix/pvfs_unlink.c | 94 | ||||
-rw-r--r-- | source4/ntvfs/posix/pvfs_write.c | 5 | ||||
-rw-r--r-- | source4/ntvfs/posix/vfs_posix.c | 4 | ||||
-rw-r--r-- | source4/ntvfs/posix/vfs_posix.h | 22 |
14 files changed, 1093 insertions, 208 deletions
diff --git a/source4/ntvfs/common/opendb.c b/source4/ntvfs/common/opendb.c index 4ac10806e1..36144d0406 100644 --- a/source4/ntvfs/common/opendb.c +++ b/source4/ntvfs/common/opendb.c @@ -93,15 +93,16 @@ _PUBLIC_ DATA_BLOB odb_get_key(TALLOC_CTX *mem_ctx, struct odb_lock *lck) Note that the path is only used by the delete on close logic, not for comparing with other filenames */ -_PUBLIC_ NTSTATUS odb_open_file(struct odb_lock *lck, void *file_handle, - uint32_t stream_id, uint32_t share_access, +_PUBLIC_ NTSTATUS odb_open_file(struct odb_lock *lck, + void *file_handle, const char *path, + uint32_t stream_id, uint32_t share_access, uint32_t access_mask, bool delete_on_close, - const char *path, + uint32_t open_disposition, bool break_to_none, uint32_t oplock_level, uint32_t *oplock_granted) { - return ops->odb_open_file(lck, file_handle, stream_id, share_access, - access_mask, delete_on_close, path, oplock_level, - oplock_granted); + return ops->odb_open_file(lck, file_handle, path, stream_id, share_access, + access_mask, delete_on_close, open_disposition, + break_to_none, oplock_level, oplock_granted); } @@ -165,10 +166,12 @@ _PUBLIC_ NTSTATUS odb_get_delete_on_close(struct odb_context *odb, create_options and access_mask */ _PUBLIC_ NTSTATUS odb_can_open(struct odb_lock *lck, - uint32_t share_access, uint32_t create_options, - uint32_t access_mask) + uint32_t stream_id, uint32_t share_access, + uint32_t access_mask, bool delete_on_close, + uint32_t open_disposition, bool break_to_none) { - return ops->odb_can_open(lck, share_access, create_options, access_mask); + return ops->odb_can_open(lck, stream_id, share_access, access_mask, + delete_on_close, open_disposition, break_to_none); } _PUBLIC_ NTSTATUS odb_update_oplock(struct odb_lock *lck, void *file_handle, @@ -176,3 +179,8 @@ _PUBLIC_ NTSTATUS odb_update_oplock(struct odb_lock *lck, void *file_handle, { return ops->odb_update_oplock(lck, file_handle, oplock_level); } + +_PUBLIC_ NTSTATUS odb_break_oplocks(struct odb_lock *lck) +{ + return ops->odb_break_oplocks(lck); +} diff --git a/source4/ntvfs/common/opendb.h b/source4/ntvfs/common/opendb.h index c34a07d6fa..9591bcf6b9 100644 --- a/source4/ntvfs/common/opendb.h +++ b/source4/ntvfs/common/opendb.h @@ -25,10 +25,11 @@ struct opendb_ops { struct odb_lock *(*odb_lock)(TALLOC_CTX *mem_ctx, struct odb_context *odb, DATA_BLOB *file_key); DATA_BLOB (*odb_get_key)(TALLOC_CTX *mem_ctx, struct odb_lock *lck); - NTSTATUS (*odb_open_file)(struct odb_lock *lck, void *file_handle, - uint32_t stream_id, uint32_t share_access, + NTSTATUS (*odb_open_file)(struct odb_lock *lck, + void *file_handle, const char *path, + uint32_t stream_id, uint32_t share_access, uint32_t access_mask, bool delete_on_close, - const char *path, + uint32_t open_disposition, bool break_to_none, uint32_t oplock_level, uint32_t *oplock_granted); NTSTATUS (*odb_open_file_pending)(struct odb_lock *lck, void *private); NTSTATUS (*odb_close_file)(struct odb_lock *lck, void *file_handle); @@ -39,10 +40,12 @@ struct opendb_ops { DATA_BLOB *key, bool *del_on_close, int *open_count, char **path); NTSTATUS (*odb_can_open)(struct odb_lock *lck, - uint32_t share_access, uint32_t create_options, - uint32_t access_mask); + uint32_t stream_id, uint32_t share_access, + uint32_t access_mask, bool delete_on_close, + uint32_t open_disposition, bool break_to_none); NTSTATUS (*odb_update_oplock)(struct odb_lock *lck, void *file_handle, uint32_t oplock_level); + NTSTATUS (*odb_break_oplocks)(struct odb_lock *lck); }; struct opendb_oplock_break { diff --git a/source4/ntvfs/common/opendb_tdb.c b/source4/ntvfs/common/opendb_tdb.c index 3d4a760e2e..a51c823a63 100644 --- a/source4/ntvfs/common/opendb_tdb.c +++ b/source4/ntvfs/common/opendb_tdb.c @@ -146,7 +146,10 @@ static DATA_BLOB odb_tdb_get_key(TALLOC_CTX *mem_ctx, struct odb_lock *lck) return NT_STATUS_OK on no conflict */ -static NTSTATUS share_conflict(struct opendb_entry *e1, struct opendb_entry *e2) +static NTSTATUS share_conflict(struct opendb_entry *e1, + uint32_t stream_id, + uint32_t share_access, + uint32_t access_mask) { /* if either open involves no read.write or delete access then it can't conflict */ @@ -157,18 +160,18 @@ static NTSTATUS share_conflict(struct opendb_entry *e1, struct opendb_entry *e2) SEC_STD_DELETE))) { return NT_STATUS_OK; } - if (!(e2->access_mask & (SEC_FILE_WRITE_DATA | - SEC_FILE_APPEND_DATA | - SEC_FILE_READ_DATA | - SEC_FILE_EXECUTE | - SEC_STD_DELETE))) { + if (!(access_mask & (SEC_FILE_WRITE_DATA | + SEC_FILE_APPEND_DATA | + SEC_FILE_READ_DATA | + SEC_FILE_EXECUTE | + SEC_STD_DELETE))) { return NT_STATUS_OK; } /* data IO access masks. This is skipped if the two open handles are on different streams (as in that case the masks don't interact) */ - if (e1->stream_id != e2->stream_id) { + if (e1->stream_id != stream_id) { return NT_STATUS_OK; } @@ -176,20 +179,20 @@ static NTSTATUS share_conflict(struct opendb_entry *e1, struct opendb_entry *e2) if (((am) & (right)) && !((sa) & (share))) return NT_STATUS_SHARING_VIOLATION CHECK_MASK(e1->access_mask, SEC_FILE_WRITE_DATA | SEC_FILE_APPEND_DATA, - e2->share_access, NTCREATEX_SHARE_ACCESS_WRITE); - CHECK_MASK(e2->access_mask, SEC_FILE_WRITE_DATA | SEC_FILE_APPEND_DATA, + share_access, NTCREATEX_SHARE_ACCESS_WRITE); + CHECK_MASK(access_mask, SEC_FILE_WRITE_DATA | SEC_FILE_APPEND_DATA, e1->share_access, NTCREATEX_SHARE_ACCESS_WRITE); CHECK_MASK(e1->access_mask, SEC_FILE_READ_DATA | SEC_FILE_EXECUTE, - e2->share_access, NTCREATEX_SHARE_ACCESS_READ); - CHECK_MASK(e2->access_mask, SEC_FILE_READ_DATA | SEC_FILE_EXECUTE, + share_access, NTCREATEX_SHARE_ACCESS_READ); + CHECK_MASK(access_mask, SEC_FILE_READ_DATA | SEC_FILE_EXECUTE, e1->share_access, NTCREATEX_SHARE_ACCESS_READ); CHECK_MASK(e1->access_mask, SEC_STD_DELETE, - e2->share_access, NTCREATEX_SHARE_ACCESS_DELETE); - CHECK_MASK(e2->access_mask, SEC_STD_DELETE, + share_access, NTCREATEX_SHARE_ACCESS_DELETE); + CHECK_MASK(access_mask, SEC_STD_DELETE, e1->share_access, NTCREATEX_SHARE_ACCESS_DELETE); - +#undef CHECK_MASK return NT_STATUS_OK; } @@ -284,6 +287,125 @@ static NTSTATUS odb_oplock_break_send(struct odb_context *odb, return NT_STATUS_OK; } +static bool access_attributes_only(uint32_t access_mask, + uint32_t open_disposition, + bool break_to_none) +{ + switch (open_disposition) { + case NTCREATEX_DISP_SUPERSEDE: + case NTCREATEX_DISP_OVERWRITE_IF: + case NTCREATEX_DISP_OVERWRITE: + return false; + default: + break; + } + + if (break_to_none) { + return false; + } + +#define CHECK_MASK(m,g) ((m) && (((m) & ~(g))==0) && (((m) & (g)) != 0)) + return CHECK_MASK(access_mask, + SEC_STD_SYNCHRONIZE | + SEC_FILE_READ_ATTRIBUTE | + SEC_FILE_WRITE_ATTRIBUTE); +#undef CHECK_MASK +} + +static NTSTATUS odb_tdb_open_can_internal(struct odb_context *odb, + const struct opendb_file *file, + uint32_t stream_id, uint32_t share_access, + uint32_t access_mask, bool delete_on_close, + uint32_t open_disposition, bool break_to_none, + bool *_attrs_only) +{ + NTSTATUS status; + uint32_t i; + bool attrs_only = false; + + /* see if anyone has an oplock, which we need to break */ + for (i=0;i<file->num_entries;i++) { + if (file->entries[i].oplock_level == OPLOCK_BATCH) { + bool oplock_return = OPLOCK_BREAK_TO_LEVEL_II; + /* if this is an attribute only access + * it doesn't conflict with a BACTCH oplock + * but we'll not grant the oplock below + */ + attrs_only = access_attributes_only(access_mask, + open_disposition, + break_to_none); + if (attrs_only) { + break; + } + /* a batch oplock caches close calls, which + means the client application might have + already closed the file. We have to allow + this close to propogate by sending a oplock + break request and suspending this call + until the break is acknowledged or the file + is closed */ + if (break_to_none) { + oplock_return = OPLOCK_BREAK_TO_NONE; + } + odb_oplock_break_send(odb, &file->entries[i], + oplock_return); + return NT_STATUS_OPLOCK_NOT_GRANTED; + } + } + + if (file->delete_on_close) { + /* while delete on close is set, no new opens are allowed */ + return NT_STATUS_DELETE_PENDING; + } + + if (file->num_entries != 0 && delete_on_close) { + return NT_STATUS_SHARING_VIOLATION; + } + + /* check for sharing violations */ + for (i=0;i<file->num_entries;i++) { + status = share_conflict(&file->entries[i], stream_id, + share_access, access_mask); + NT_STATUS_NOT_OK_RETURN(status); + } + + /* we now know the open could succeed, but we need to check + for any exclusive oplocks. We can't grant a second open + till these are broken. Note that we check for batch oplocks + before checking for sharing violations, and check for + exclusive oplocks afterwards. */ + for (i=0;i<file->num_entries;i++) { + if (file->entries[i].oplock_level == OPLOCK_EXCLUSIVE) { + bool oplock_return = OPLOCK_BREAK_TO_LEVEL_II; + /* if this is an attribute only access + * it doesn't conflict with an EXCLUSIVE oplock + * but we'll not grant the oplock below + */ + attrs_only = access_attributes_only(access_mask, + open_disposition, + break_to_none); + if (attrs_only) { + break; + } + /* + * send an oplock break to the holder of the + * oplock and tell caller to retry later + */ + if (break_to_none) { + oplock_return = OPLOCK_BREAK_TO_NONE; + } + odb_oplock_break_send(odb, &file->entries[i], + oplock_return); + return NT_STATUS_OPLOCK_NOT_GRANTED; + } + } + + if (_attrs_only) { + *_attrs_only = attrs_only; + } + return NT_STATUS_OK; +} + /* register an open file in the open files database. This implements the share_access rules @@ -291,17 +413,18 @@ static NTSTATUS odb_oplock_break_send(struct odb_context *odb, Note that the path is only used by the delete on close logic, not for comparing with other filenames */ -static NTSTATUS odb_tdb_open_file(struct odb_lock *lck, void *file_handle, - uint32_t stream_id, uint32_t share_access, +static NTSTATUS odb_tdb_open_file(struct odb_lock *lck, + void *file_handle, const char *path, + uint32_t stream_id, uint32_t share_access, uint32_t access_mask, bool delete_on_close, - const char *path, + uint32_t open_disposition, bool break_to_none, uint32_t oplock_level, uint32_t *oplock_granted) { struct odb_context *odb = lck->odb; struct opendb_entry e; - int i; struct opendb_file file; NTSTATUS status; + bool attrs_only = false; if (odb->oplocks == false) { oplock_level = OPLOCK_NONE; @@ -317,6 +440,13 @@ static NTSTATUS odb_tdb_open_file(struct odb_lock *lck, void *file_handle, } /* see if it conflicts */ + status = odb_tdb_open_can_internal(odb, &file, stream_id, + share_access, access_mask, + delete_on_close, open_disposition, + break_to_none, &attrs_only); + NT_STATUS_NOT_OK_RETURN(status); + + /* see if it conflicts */ e.server = odb->ntvfs_ctx->server_id; e.file_handle = file_handle; e.stream_id = stream_id; @@ -324,61 +454,37 @@ static NTSTATUS odb_tdb_open_file(struct odb_lock *lck, void *file_handle, e.access_mask = access_mask; e.delete_on_close = delete_on_close; e.oplock_level = OPLOCK_NONE; - - /* see if anyone has an oplock, which we need to break */ - for (i=0;i<file.num_entries;i++) { - if (file.entries[i].oplock_level == OPLOCK_BATCH) { - /* a batch oplock caches close calls, which - means the client application might have - already closed the file. We have to allow - this close to propogate by sending a oplock - break request and suspending this call - until the break is acknowledged or the file - is closed */ - odb_oplock_break_send(odb, &file.entries[i], - OPLOCK_BREAK_TO_LEVEL_II/*TODO*/); - return NT_STATUS_OPLOCK_NOT_GRANTED; - } - } - - if (file.delete_on_close || - (file.num_entries != 0 && delete_on_close)) { - /* while delete on close is set, no new opens are allowed */ - return NT_STATUS_DELETE_PENDING; - } - - /* check for sharing violations */ - for (i=0;i<file.num_entries;i++) { - status = share_conflict(&file.entries[i], &e); - NT_STATUS_NOT_OK_RETURN(status); - } - - /* we now know the open could succeed, but we need to check - for any exclusive oplocks. We can't grant a second open - till these are broken. Note that we check for batch oplocks - before checking for sharing violations, and check for - exclusive oplocks afterwards. */ - for (i=0;i<file.num_entries;i++) { - if (file.entries[i].oplock_level == OPLOCK_EXCLUSIVE) { - odb_oplock_break_send(odb, &file.entries[i], - OPLOCK_BREAK_TO_NONE/*TODO*/); - return NT_STATUS_OPLOCK_NOT_GRANTED; - } - } /* - possibly grant an exclusive or batch oplock if this is the only client - with the file open. We don't yet grant levelII oplocks. + possibly grant an exclusive, batch or level2 oplock */ - if (oplock_granted != NULL) { - if ((oplock_level == OPLOCK_BATCH || - oplock_level == OPLOCK_EXCLUSIVE) && - file.num_entries == 0) { - (*oplock_granted) = oplock_level; + if (oplock_granted) { + if (attrs_only) { + e.oplock_level = OPLOCK_NONE; + *oplock_granted = NO_OPLOCK_RETURN; + } else if (oplock_level == OPLOCK_EXCLUSIVE) { + if (file.num_entries == 0) { + e.oplock_level = OPLOCK_EXCLUSIVE; + *oplock_granted = EXCLUSIVE_OPLOCK_RETURN; + } else { + e.oplock_level = OPLOCK_LEVEL_II; + *oplock_granted = LEVEL_II_OPLOCK_RETURN; + } + } else if (oplock_level == OPLOCK_BATCH) { + if (file.num_entries == 0) { + e.oplock_level = OPLOCK_BATCH; + *oplock_granted = BATCH_OPLOCK_RETURN; + } else { + e.oplock_level = OPLOCK_LEVEL_II; + *oplock_granted = LEVEL_II_OPLOCK_RETURN; + } + } else if (oplock_level == OPLOCK_LEVEL_II) { + e.oplock_level = OPLOCK_LEVEL_II; + *oplock_granted = LEVEL_II_OPLOCK_RETURN; } else { - (*oplock_granted) = OPLOCK_NONE; + e.oplock_level = OPLOCK_NONE; + *oplock_granted = NO_OPLOCK_RETURN; } - e.oplock_level = (*oplock_granted); } /* it doesn't conflict, so add it to the end */ @@ -503,6 +609,43 @@ static NTSTATUS odb_tdb_update_oplock(struct odb_lock *lck, void *file_handle, return odb_push_record(lck, &file); } +/* + send oplocks breaks to none to all level2 holders +*/ +static NTSTATUS odb_tdb_break_oplocks(struct odb_lock *lck) +{ + struct odb_context *odb = lck->odb; + NTSTATUS status; + struct opendb_file file; + int i; + bool modified = true; + + status = odb_pull_record(lck, &file); + if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + return NT_STATUS_OK; + } + NT_STATUS_NOT_OK_RETURN(status); + + /* see if anyone has an oplock, which we need to break */ + for (i=0;i<file.num_entries;i++) { + if (file.entries[i].oplock_level == OPLOCK_LEVEL_II) { + /* + * there could be multiple level2 oplocks + * and we just send a break to none to all of them + * without waiting for a release + */ + odb_oplock_break_send(odb, &file.entries[i], + OPLOCK_BREAK_TO_NONE); + file.entries[i].oplock_level = OPLOCK_NONE; + modified = true; + } + } + + if (modified) { + return odb_push_record(lck, &file); + } + return NT_STATUS_OK; +} /* remove a pending opendb entry @@ -624,14 +767,14 @@ static NTSTATUS odb_tdb_get_delete_on_close(struct odb_context *odb, create_options and access_mask */ static NTSTATUS odb_tdb_can_open(struct odb_lock *lck, - uint32_t share_access, uint32_t create_options, - uint32_t access_mask) + uint32_t stream_id, uint32_t share_access, + uint32_t access_mask, bool delete_on_close, + uint32_t open_disposition, bool break_to_none) { struct odb_context *odb = lck->odb; NTSTATUS status; struct opendb_file file; - struct opendb_entry e; - int i; + bool attrs_only = false; status = odb_pull_record(lck, &file); if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { @@ -639,32 +782,11 @@ static NTSTATUS odb_tdb_can_open(struct odb_lock *lck, } NT_STATUS_NOT_OK_RETURN(status); - if ((create_options & NTCREATEX_OPTIONS_DELETE_ON_CLOSE) && - file.num_entries != 0) { - return NT_STATUS_SHARING_VIOLATION; - } - - if (file.delete_on_close) { - return NT_STATUS_DELETE_PENDING; - } - - e.server = odb->ntvfs_ctx->server_id; - e.file_handle = NULL; - e.stream_id = 0; - e.share_access = share_access; - e.access_mask = access_mask; - - for (i=0;i<file.num_entries;i++) { - status = share_conflict(&file.entries[i], &e); - if (!NT_STATUS_IS_OK(status)) { - /* note that we discard the error code - here. We do this as unless we are actually - doing an open (which comes via a different - function), we need to return a sharing - violation */ - return NT_STATUS_SHARING_VIOLATION; - } - } + status = odb_tdb_open_can_internal(odb, &file, stream_id, + share_access, access_mask, + delete_on_close, open_disposition, + break_to_none, &attrs_only); + NT_STATUS_NOT_OK_RETURN(status); return NT_STATUS_OK; } @@ -682,7 +804,8 @@ static const struct opendb_ops opendb_tdb_ops = { .odb_set_delete_on_close = odb_tdb_set_delete_on_close, .odb_get_delete_on_close = odb_tdb_get_delete_on_close, .odb_can_open = odb_tdb_can_open, - .odb_update_oplock = odb_tdb_update_oplock + .odb_update_oplock = odb_tdb_update_oplock, + .odb_break_oplocks = odb_tdb_break_oplocks }; diff --git a/source4/ntvfs/posix/config.mk b/source4/ntvfs/posix/config.mk index 6879940337..88048c2af7 100644 --- a/source4/ntvfs/posix/config.mk +++ b/source4/ntvfs/posix/config.mk @@ -53,6 +53,7 @@ OBJ_FILES = \ pvfs_resolve.o \ pvfs_shortname.o \ pvfs_lock.o \ + pvfs_oplock.o \ pvfs_wait.o \ pvfs_seek.o \ pvfs_ioctl.o \ diff --git a/source4/ntvfs/posix/pvfs_lock.c b/source4/ntvfs/posix/pvfs_lock.c index df85b2b775..baa92880f1 100644 --- a/source4/ntvfs/posix/pvfs_lock.c +++ b/source4/ntvfs/posix/pvfs_lock.c @@ -294,6 +294,10 @@ NTSTATUS pvfs_lock(struct ntvfs_module_context *ntvfs, return ntvfs_map_lock(ntvfs, req, lck); } + if (lck->lockx.in.mode & LOCKING_ANDX_OPLOCK_RELEASE) { + return pvfs_oplock_release(ntvfs, req, lck); + } + f = pvfs_find_fd(pvfs, req, lck->lockx.in.file.ntvfs); if (!f) { return NT_STATUS_INVALID_HANDLE; @@ -303,6 +307,9 @@ NTSTATUS pvfs_lock(struct ntvfs_module_context *ntvfs, return NT_STATUS_FILE_IS_A_DIRECTORY; } + status = pvfs_break_level2_oplocks(f); + NT_STATUS_NOT_OK_RETURN(status); + if (lck->lockx.in.timeout != 0 && (req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) { pending = talloc(f, struct pvfs_pending_lock); @@ -338,13 +345,6 @@ NTSTATUS pvfs_lock(struct ntvfs_module_context *ntvfs, return NT_STATUS_DOS(ERRDOS, ERRnoatomiclocks); } - if (lck->lockx.in.mode & LOCKING_ANDX_OPLOCK_RELEASE) { - DEBUG(0,("received unexpected oplock break\n")); - talloc_free(pending); - return NT_STATUS_NOT_IMPLEMENTED; - } - - /* the unlocks happen first */ locks = lck->lockx.in.locks; diff --git a/source4/ntvfs/posix/pvfs_open.c b/source4/ntvfs/posix/pvfs_open.c index 3a8b17ab16..a01352f60c 100644 --- a/source4/ntvfs/posix/pvfs_open.c +++ b/source4/ntvfs/posix/pvfs_open.c @@ -268,6 +268,7 @@ static NTSTATUS pvfs_open_directory(struct pvfs_state *pvfs, f->handle->seek_offset = 0; f->handle->position = 0; f->handle->mode = 0; + f->handle->oplock = NULL; f->handle->sticky_write_time = false; f->handle->open_completed = false; @@ -296,9 +297,10 @@ static NTSTATUS pvfs_open_directory(struct pvfs_state *pvfs, } /* see if we are allowed to open at the same time as existing opens */ - status = odb_open_file(lck, f->handle, f->handle->name->stream_id, + status = odb_open_file(lck, f->handle, name->full_name, name->stream_id, share_access, access_mask, del_on_close, - name->full_name, OPLOCK_NONE, NULL); + io->generic.in.open_disposition, + false, OPLOCK_NONE, NULL); if (!NT_STATUS_IS_OK(status)) { talloc_free(lck); @@ -349,9 +351,10 @@ static NTSTATUS pvfs_open_directory(struct pvfs_state *pvfs, return NT_STATUS_INTERNAL_DB_CORRUPTION; } - status = odb_open_file(lck, f->handle, f->handle->name->stream_id, + status = odb_open_file(lck, f->handle, name->full_name, name->stream_id, share_access, access_mask, del_on_close, - name->full_name, OPLOCK_NONE, NULL); + io->generic.in.open_disposition, + false, OPLOCK_NONE, NULL); if (!NT_STATUS_IS_OK(status)) { goto cleanup_delete; @@ -669,9 +672,10 @@ static NTSTATUS pvfs_create_file(struct pvfs_state *pvfs, oplock_level = OPLOCK_EXCLUSIVE; } - status = odb_open_file(lck, f->handle, name->stream_id, + status = odb_open_file(lck, f->handle, name->full_name, name->stream_id, share_access, access_mask, del_on_close, - name->full_name, oplock_level, &oplock_granted); + io->generic.in.open_disposition, + false, oplock_level, &oplock_granted); talloc_free(lck); if (!NT_STATUS_IS_OK(status)) { /* bad news, we must have hit a race - we don't delete the file @@ -681,9 +685,6 @@ static NTSTATUS pvfs_create_file(struct pvfs_state *pvfs, return status; } - if (pvfs->flags & PVFS_FLAG_FAKE_OPLOCKS) { - oplock_granted = OPLOCK_BATCH; - } f->ntvfs = h; f->pvfs = pvfs; @@ -702,12 +703,23 @@ static NTSTATUS pvfs_create_file(struct pvfs_state *pvfs, f->handle->seek_offset = 0; f->handle->position = 0; f->handle->mode = 0; + f->handle->oplock = NULL; f->handle->have_opendb_entry = true; f->handle->sticky_write_time = false; f->handle->open_completed = false; DLIST_ADD(pvfs->files.list, f); + if (pvfs->flags & PVFS_FLAG_FAKE_OPLOCKS) { + oplock_granted = OPLOCK_BATCH; + } else if (oplock_granted != OPLOCK_NONE) { + status = pvfs_setup_oplock(f, oplock_granted); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(lck); + return status; + } + } + /* setup a destructor to avoid file descriptor leaks on abnormal termination */ talloc_set_destructor(f, pvfs_fnum_destructor); @@ -748,20 +760,25 @@ cleanup_delete: return status; } - /* - state of a pending open retry + state of a pending retry */ -struct pvfs_open_retry { +struct pvfs_odb_retry { struct ntvfs_module_context *ntvfs; struct ntvfs_request *req; - union smb_open *io; - struct pvfs_wait *wait_handle; DATA_BLOB odb_locking_key; + void *io; + void *private_data; + void (*callback)(struct pvfs_odb_retry *r, + struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + void *io, + void *private_data, + enum pvfs_wait_notice reason); }; -/* destroy a pending open request */ -static int pvfs_retry_destructor(struct pvfs_open_retry *r) +/* destroy a pending request */ +static int pvfs_odb_retry_destructor(struct pvfs_odb_retry *r) { struct pvfs_state *pvfs = r->ntvfs->private_data; if (r->odb_locking_key.data) { @@ -775,15 +792,92 @@ static int pvfs_retry_destructor(struct pvfs_open_retry *r) return 0; } +static void pvfs_odb_retry_callback(void *_r, enum pvfs_wait_notice reason) +{ + struct pvfs_odb_retry *r = talloc_get_type(_r, struct pvfs_odb_retry); + + if (reason == PVFS_WAIT_EVENT) { + /* + * The pending odb entry is already removed. + * We use a null locking key to indicate this + * to the destructor. + */ + data_blob_free(&r->odb_locking_key); + } + + r->callback(r, r->ntvfs, r->req, r->io, r->private_data, reason); +} + /* - retry an open + setup for a retry of a request that was rejected + by odb_open_file() or odb_can_open() */ -static void pvfs_open_retry(void *private, enum pvfs_wait_notice reason) +NTSTATUS pvfs_odb_retry_setup(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + struct odb_lock *lck, + struct timeval end_time, + void *io, + void *private_data, + void (*callback)(struct pvfs_odb_retry *r, + struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + void *io, + void *private_data, + enum pvfs_wait_notice reason)) { - struct pvfs_open_retry *r = private; - struct ntvfs_module_context *ntvfs = r->ntvfs; - struct ntvfs_request *req = r->req; - union smb_open *io = r->io; + struct pvfs_state *pvfs = ntvfs->private_data; + struct pvfs_odb_retry *r; + struct pvfs_wait *wait_handle; + NTSTATUS status; + + r = talloc(req, struct pvfs_odb_retry); + NT_STATUS_HAVE_NO_MEMORY(r); + + r->ntvfs = ntvfs; + r->req = req; + r->io = io; + r->private_data = private_data; + r->callback = callback; + r->odb_locking_key = odb_get_key(r, lck); + if (r->odb_locking_key.data == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* setup a pending lock */ + status = odb_open_file_pending(lck, r); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + talloc_free(lck); + + talloc_set_destructor(r, pvfs_odb_retry_destructor); + + wait_handle = pvfs_wait_message(pvfs, req, + MSG_PVFS_RETRY_OPEN, end_time, + pvfs_odb_retry_callback, r); + if (wait_handle == NULL) { + return NT_STATUS_NO_MEMORY; + } + + talloc_steal(r, wait_handle); + + talloc_steal(pvfs, r); + + return NT_STATUS_OK; +} + +/* + retry an open after a sharing violation +*/ +static void pvfs_retry_open_sharing(struct pvfs_odb_retry *r, + struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + void *_io, + void *private_data, + enum pvfs_wait_notice reason) +{ + union smb_open *io = talloc_get_type(_io, union smb_open); NTSTATUS status; /* w2k3 ignores SMBntcancel for outstanding open requests. It's probably @@ -792,8 +886,6 @@ static void pvfs_open_retry(void *private, enum pvfs_wait_notice reason) return; } - talloc_free(r->wait_handle); - if (reason == PVFS_WAIT_TIMEOUT) { /* if it timed out, then give the failure immediately */ @@ -803,9 +895,6 @@ static void pvfs_open_retry(void *private, enum pvfs_wait_notice reason) return; } - /* the pending odb entry is already removed. We use a null locking - key to indicate this */ - data_blob_free(&r->odb_locking_key); talloc_free(r); /* try the open again, which could trigger another retry setup @@ -919,10 +1008,10 @@ static NTSTATUS pvfs_open_setup_retry(struct ntvfs_module_context *ntvfs, struct ntvfs_request *req, union smb_open *io, struct pvfs_file *f, - struct odb_lock *lck) + struct odb_lock *lck, + NTSTATUS parent_status) { struct pvfs_state *pvfs = ntvfs->private_data; - struct pvfs_open_retry *r; NTSTATUS status; struct timeval end_time; @@ -936,40 +1025,21 @@ static NTSTATUS pvfs_open_setup_retry(struct ntvfs_module_context *ntvfs, } } - r = talloc(req, struct pvfs_open_retry); - if (r == NULL) { - return NT_STATUS_NO_MEMORY; - } - - r->ntvfs = ntvfs; - r->req = req; - r->io = io; - r->odb_locking_key = data_blob_talloc(r, - f->handle->odb_locking_key.data, - f->handle->odb_locking_key.length); - - end_time = timeval_add(&req->statistics.request_time, 0, pvfs->sharing_violation_delay); - - /* setup a pending lock */ - status = odb_open_file_pending(lck, r); - if (!NT_STATUS_IS_OK(status)) { - return status; - } - - talloc_free(lck); + /* the retry should allocate a new file handle */ talloc_free(f); - talloc_set_destructor(r, pvfs_retry_destructor); - - r->wait_handle = pvfs_wait_message(pvfs, req, MSG_PVFS_RETRY_OPEN, end_time, - pvfs_open_retry, r); - if (r->wait_handle == NULL) { - return NT_STATUS_NO_MEMORY; + if (NT_STATUS_EQUAL(parent_status, NT_STATUS_SHARING_VIOLATION)) { + end_time = timeval_add(&req->statistics.request_time, + 0, pvfs->sharing_violation_delay); + } else if (NT_STATUS_EQUAL(parent_status, NT_STATUS_OPLOCK_NOT_GRANTED)) { + end_time = timeval_add(&req->statistics.request_time, + pvfs->oplock_break_timeout, 0); + } else { + return NT_STATUS_INTERNAL_ERROR; } - talloc_steal(pvfs, r); - - return NT_STATUS_OK; + return pvfs_odb_retry_setup(ntvfs, req, lck, end_time, io, NULL, + pvfs_retry_open_sharing); } /* @@ -1133,6 +1203,7 @@ NTSTATUS pvfs_open(struct ntvfs_module_context *ntvfs, f->handle->seek_offset = 0; f->handle->position = 0; f->handle->mode = 0; + f->handle->oplock = NULL; f->handle->have_opendb_entry = false; f->handle->sticky_write_time = false; f->handle->open_completed = false; @@ -1186,15 +1257,21 @@ NTSTATUS pvfs_open(struct ntvfs_module_context *ntvfs, } /* see if we are allowed to open at the same time as existing opens */ - status = odb_open_file(lck, f->handle, f->handle->name->stream_id, + status = odb_open_file(lck, f->handle, name->full_name, name->stream_id, share_access, access_mask, del_on_close, - name->full_name, oplock_level, &oplock_granted); + io->generic.in.open_disposition, + false, oplock_level, &oplock_granted); - /* on a sharing violation we need to retry when the file is closed by - the other user, or after 1 second */ - if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION) && + /* + * 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_open_setup_retry(ntvfs, req, io, f, lck); + return pvfs_open_setup_retry(ntvfs, req, io, f, lck, status); } if (!NT_STATUS_IS_OK(status)) { @@ -1204,6 +1281,12 @@ NTSTATUS pvfs_open(struct ntvfs_module_context *ntvfs, if (pvfs->flags & PVFS_FLAG_FAKE_OPLOCKS) { oplock_granted = OPLOCK_BATCH; + } else if (oplock_granted != OPLOCK_NONE) { + status = pvfs_setup_oplock(f, oplock_granted); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(lck); + return status; + } } f->handle->have_opendb_entry = true; @@ -1424,6 +1507,9 @@ NTSTATUS pvfs_can_delete(struct pvfs_state *pvfs, NTSTATUS status; DATA_BLOB key; struct odb_lock *lck; + uint32_t share_access; + uint32_t access_mask; + bool delete_on_close; status = pvfs_locking_key(name, name, &key); if (!NT_STATUS_IS_OK(status)) { @@ -1436,15 +1522,18 @@ NTSTATUS pvfs_can_delete(struct pvfs_state *pvfs, return NT_STATUS_INTERNAL_DB_CORRUPTION; } - status = odb_can_open(lck, - NTCREATEX_SHARE_ACCESS_READ | - NTCREATEX_SHARE_ACCESS_WRITE | - NTCREATEX_SHARE_ACCESS_DELETE, - NTCREATEX_OPTIONS_DELETE_ON_CLOSE, - SEC_STD_DELETE); + share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + access_mask = SEC_STD_DELETE; + delete_on_close = true; + + status = odb_can_open(lck, name->stream_id, + share_access, access_mask, delete_on_close, + NTCREATEX_DISP_OPEN, false); if (NT_STATUS_IS_OK(status)) { - status = pvfs_access_check_simple(pvfs, req, name, SEC_STD_DELETE); + status = pvfs_access_check_simple(pvfs, req, name, access_mask); } /* @@ -1483,6 +1572,70 @@ NTSTATUS pvfs_can_rename(struct pvfs_state *pvfs, NTSTATUS status; DATA_BLOB key; struct odb_lock *lck; + uint32_t share_access; + uint32_t access_mask; + bool delete_on_close; + + status = pvfs_locking_key(name, name, &key); + if (!NT_STATUS_IS_OK(status)) { + return NT_STATUS_NO_MEMORY; + } + + lck = odb_lock(req, pvfs->odb_context, &key); + if (lck == NULL) { + DEBUG(0,("Unable to lock opendb for can_stat\n")); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + access_mask = SEC_STD_DELETE; + delete_on_close = false; + + status = odb_can_open(lck, name->stream_id, + share_access, access_mask, delete_on_close, + NTCREATEX_DISP_OPEN, false); + + /* + * if it's a sharing violation or we got no oplock + * only keep the lock if the caller requested access + * to the lock + */ + if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION) || + NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) { + if (lckp) { + *lckp = lck; + } else { + talloc_free(lck); + } + } else if (!NT_STATUS_IS_OK(status)) { + talloc_free(lck); + if (lckp) { + *lckp = NULL; + } + } else if (lckp) { + *lckp = lck; + } + + return status; +} + +/* + determine if the file size of a file can be changed, + or if it is prevented by an already open file +*/ +NTSTATUS pvfs_can_update_file_size(struct pvfs_state *pvfs, + struct ntvfs_request *req, + struct pvfs_filename *name, + struct odb_lock **lckp) +{ + NTSTATUS status; + DATA_BLOB key; + struct odb_lock *lck; + uint32_t share_access; + uint32_t access_mask; + bool break_to_none; + bool delete_on_close; status = pvfs_locking_key(name, name, &key); if (!NT_STATUS_IS_OK(status)) { @@ -1495,11 +1648,25 @@ NTSTATUS pvfs_can_rename(struct pvfs_state *pvfs, return NT_STATUS_INTERNAL_DB_CORRUPTION; } - status = odb_can_open(lck, - NTCREATEX_SHARE_ACCESS_READ | - NTCREATEX_SHARE_ACCESS_WRITE, - 0, - SEC_STD_DELETE); + share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE | + NTCREATEX_SHARE_ACCESS_DELETE; + /* + * I would have thought that we would need to pass + * SEC_FILE_WRITE_DATA | SEC_FILE_APPEND_DATA here too + * + * But you only need SEC_FILE_WRITE_ATTRIBUTE permissions + * to set the filesize. + * + * --metze + */ + access_mask = SEC_FILE_WRITE_ATTRIBUTE; + delete_on_close = false; + break_to_none = true; + + status = odb_can_open(lck, name->stream_id, + share_access, access_mask, delete_on_close, + NTCREATEX_DISP_OPEN, break_to_none); /* * if it's a sharing violation or we got no oplock @@ -1536,6 +1703,9 @@ NTSTATUS pvfs_can_stat(struct pvfs_state *pvfs, NTSTATUS status; DATA_BLOB key; struct odb_lock *lck; + uint32_t share_access; + uint32_t access_mask; + bool delete_on_close; status = pvfs_locking_key(name, name, &key); if (!NT_STATUS_IS_OK(status)) { @@ -1548,10 +1718,14 @@ NTSTATUS pvfs_can_stat(struct pvfs_state *pvfs, return NT_STATUS_INTERNAL_DB_CORRUPTION; } - status = odb_can_open(lck, - NTCREATEX_SHARE_ACCESS_READ | - NTCREATEX_SHARE_ACCESS_WRITE, - 0, 0); + share_access = NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + access_mask = SEC_FILE_READ_ATTRIBUTE; + delete_on_close = false; + + status = odb_can_open(lck, name->stream_id, + share_access, access_mask, delete_on_close, + NTCREATEX_DISP_OPEN, false); if (!NT_STATUS_IS_OK(status)) { talloc_free(lck); diff --git a/source4/ntvfs/posix/pvfs_oplock.c b/source4/ntvfs/posix/pvfs_oplock.c new file mode 100644 index 0000000000..cf30ddbc59 --- /dev/null +++ b/source4/ntvfs/posix/pvfs_oplock.c @@ -0,0 +1,239 @@ +/* + Unix SMB/CIFS implementation. + + POSIX NTVFS backend - oplock handling + + Copyright (C) Stefan Metzmacher 2008 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "lib/messaging/messaging.h" +#include "lib/messaging/irpc.h" +#include "vfs_posix.h" + + +struct pvfs_oplock { + struct pvfs_file_handle *handle; + struct pvfs_file *file; + uint32_t level; + struct messaging_context *msg_ctx; +}; + +/* + receive oplock breaks and forward them to the client +*/ +static void pvfs_oplock_break(struct pvfs_oplock *opl, uint8_t level) +{ + NTSTATUS status; + struct pvfs_file *f = opl->file; + struct pvfs_file_handle *h = opl->handle; + struct pvfs_state *pvfs = h->pvfs; + + DEBUG(10,("pvfs_oplock_break: sending oplock break level %d for '%s' %p\n", + level, h->name->original_name, h)); + status = ntvfs_send_oplock_break(pvfs->ntvfs, f->ntvfs, level); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("pvfs_oplock_break: sending oplock break failed: %s\n", + nt_errstr(status))); + } +} + +static void pvfs_oplock_break_dispatch(struct messaging_context *msg, + void *private_data, uint32_t msg_type, + struct server_id src, DATA_BLOB *data) +{ + struct pvfs_oplock *opl = talloc_get_type(private_data, + struct pvfs_oplock); + struct opendb_oplock_break opb; + + ZERO_STRUCT(opb); + + /* we need to check that this one is for us. See + messaging_send_ptr() for the other side of this. + */ + if (data->length == sizeof(struct opendb_oplock_break)) { + struct opendb_oplock_break *p; + p = (struct opendb_oplock_break *)data->data; + opb = *p; + } else { + DEBUG(0,("%s: ignore oplock break with length[%u]\n", + __location__, data->length)); + return; + } + if (opb.file_handle != opl->handle) { + return; + } + + /* + * maybe we should use ntvfs_setup_async() + */ + pvfs_oplock_break(opl, opb.level); +} + +static int pvfs_oplock_destructor(struct pvfs_oplock *opl) +{ + messaging_deregister(opl->msg_ctx, MSG_NTVFS_OPLOCK_BREAK, opl); + return 0; +} + +NTSTATUS pvfs_setup_oplock(struct pvfs_file *f, uint32_t oplock_granted) +{ + NTSTATUS status; + struct pvfs_oplock *opl; + uint32_t level = OPLOCK_NONE; + + f->handle->oplock = NULL; + + switch (oplock_granted) { + case EXCLUSIVE_OPLOCK_RETURN: + level = OPLOCK_EXCLUSIVE; + break; + case BATCH_OPLOCK_RETURN: + level = OPLOCK_BATCH; + break; + case LEVEL_II_OPLOCK_RETURN: + level = OPLOCK_LEVEL_II; + break; + } + + if (level == OPLOCK_NONE) { + return NT_STATUS_OK; + } + + opl = talloc(f->handle, struct pvfs_oplock); + NT_STATUS_HAVE_NO_MEMORY(opl); + + opl->handle = f->handle; + opl->file = f; + opl->level = level; + opl->msg_ctx = f->pvfs->ntvfs->ctx->msg_ctx; + + status = messaging_register(opl->msg_ctx, + opl, + MSG_NTVFS_OPLOCK_BREAK, + pvfs_oplock_break_dispatch); + NT_STATUS_NOT_OK_RETURN(status); + + /* destructor */ + talloc_set_destructor(opl, pvfs_oplock_destructor); + + f->handle->oplock = opl; + + return NT_STATUS_OK; +} + +NTSTATUS pvfs_oplock_release(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, union smb_lock *lck) +{ + struct pvfs_state *pvfs = ntvfs->private_data; + struct pvfs_file *f; + struct pvfs_file_handle *h; + struct odb_lock *olck; + uint8_t oplock_break; + NTSTATUS status; + + f = pvfs_find_fd(pvfs, req, lck->lockx.in.file.ntvfs); + if (!f) { + return NT_STATUS_INVALID_HANDLE; + } + + h = f->handle; + + if (h->fd == -1) { + return NT_STATUS_FILE_IS_A_DIRECTORY; + } + + if (!h->have_opendb_entry) { + return NT_STATUS_FOOBAR; + } + + if (!h->oplock) { + return NT_STATUS_FOOBAR; + } + + olck = odb_lock(h, h->pvfs->odb_context, &h->odb_locking_key); + if (olck == NULL) { + DEBUG(0,("Unable to lock opendb for oplock update\n")); + return NT_STATUS_FOOBAR; + } + + oplock_break = (lck->lockx.in.mode >> 8) & 0xFF; + if (oplock_break == OPLOCK_BREAK_TO_NONE) { + h->oplock->level = OPLOCK_NONE; + } else if (oplock_break == OPLOCK_BREAK_TO_LEVEL_II) { + h->oplock->level = OPLOCK_LEVEL_II; + } else { + /* fallback to level II in case of a invalid value */ + DEBUG(1,("unexpected oplock break level[0x%02X]\n", oplock_break)); + h->oplock->level = OPLOCK_LEVEL_II; + } + status = odb_update_oplock(olck, h, h->oplock->level); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("Unable to update oplock level for '%s' - %s\n", + h->name->full_name, nt_errstr(status))); + talloc_free(olck); + return status; + } + + talloc_free(olck); + + /* after a break to none, we no longer have an oplock attached */ + if (h->oplock->level == OPLOCK_NONE) { + talloc_free(h->oplock); + h->oplock = NULL; + } + + return NT_STATUS_OK; +} + +NTSTATUS pvfs_break_level2_oplocks(struct pvfs_file *f) +{ + struct pvfs_file_handle *h = f->handle; + struct odb_lock *olck; + NTSTATUS status; + + if (h->oplock && h->oplock->level == OPLOCK_EXCLUSIVE) { + return NT_STATUS_OK; + } + + olck = odb_lock(h, h->pvfs->odb_context, &h->odb_locking_key); + if (olck == NULL) { + DEBUG(0,("Unable to lock opendb for oplock update\n")); + return NT_STATUS_FOOBAR; + } + + if (h->oplock && h->oplock->level == OPLOCK_BATCH) { + status = odb_update_oplock(olck, h, OPLOCK_LEVEL_II); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("Unable to update oplock level for '%s' - %s\n", + h->name->full_name, nt_errstr(status))); + talloc_free(olck); + return status; + } + } + + status = odb_break_oplocks(olck); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("Unable to break level2 oplocks to none for '%s' - %s\n", + h->name->full_name, nt_errstr(status))); + talloc_free(olck); + return status; + } + + talloc_free(olck); + + return NT_STATUS_OK; +} diff --git a/source4/ntvfs/posix/pvfs_qfileinfo.c b/source4/ntvfs/posix/pvfs_qfileinfo.c index 2f01c08fb0..8d23d707a4 100644 --- a/source4/ntvfs/posix/pvfs_qfileinfo.c +++ b/source4/ntvfs/posix/pvfs_qfileinfo.c @@ -330,7 +330,7 @@ NTSTATUS pvfs_qpathinfo(struct ntvfs_module_context *ntvfs, status = pvfs_can_stat(pvfs, req, name); if (!NT_STATUS_IS_OK(status)) { - return NT_STATUS_DELETE_PENDING; + return status; } status = pvfs_access_check_simple(pvfs, req, name, diff --git a/source4/ntvfs/posix/pvfs_rename.c b/source4/ntvfs/posix/pvfs_rename.c index 5693e79314..3c60c00247 100644 --- a/source4/ntvfs/posix/pvfs_rename.c +++ b/source4/ntvfs/posix/pvfs_rename.c @@ -181,6 +181,84 @@ static const char *pvfs_resolve_wildcard(TALLOC_CTX *mem_ctx, } /* + retry an rename after a sharing violation +*/ +static void pvfs_retry_rename(struct pvfs_odb_retry *r, + struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + void *_io, + void *private_data, + enum pvfs_wait_notice reason) +{ + union smb_rename *io = talloc_get_type(_io, union smb_rename); + NTSTATUS status = NT_STATUS_INTERNAL_ERROR; + + talloc_free(r); + + switch (reason) { + case PVFS_WAIT_CANCEL: +/*TODO*/ + status = NT_STATUS_CANCELLED; + break; + case PVFS_WAIT_TIMEOUT: + /* if it timed out, then give the failure + immediately */ +/*TODO*/ + status = NT_STATUS_SHARING_VIOLATION; + break; + case PVFS_WAIT_EVENT: + + /* try the open again, which could trigger another retry setup + if it wants to, so we have to unmark the async flag so we + will know if it does a second async reply */ + req->async_states->state &= ~NTVFS_ASYNC_STATE_ASYNC; + + status = pvfs_rename(ntvfs, req, io); + if (req->async_states->state & NTVFS_ASYNC_STATE_ASYNC) { + /* the 2nd try also replied async, so we don't send + the reply yet */ + return; + } + + /* re-mark it async, just in case someone up the chain does + paranoid checking */ + req->async_states->state |= NTVFS_ASYNC_STATE_ASYNC; + break; + } + + /* send the reply up the chain */ + req->async_states->status = status; + req->async_states->send_fn(req); +} + +/* + setup for a rename retry after a sharing violation + or a non granted oplock +*/ +static NTSTATUS pvfs_rename_setup_retry(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_rename *io, + struct odb_lock *lck, + NTSTATUS status) +{ + struct pvfs_state *pvfs = ntvfs->private_data; + struct timeval end_time; + + if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) { + end_time = timeval_add(&req->statistics.request_time, + 0, pvfs->sharing_violation_delay); + } else if (NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) { + end_time = timeval_add(&req->statistics.request_time, + pvfs->oplock_break_timeout, 0); + } else { + return NT_STATUS_INTERNAL_ERROR; + } + + return pvfs_odb_retry_setup(ntvfs, req, lck, end_time, io, NULL, + pvfs_retry_rename); +} + +/* rename one file from a wildcard set */ static NTSTATUS pvfs_rename_one(struct pvfs_state *pvfs, @@ -354,8 +432,19 @@ static NTSTATUS pvfs_rename_mv(struct ntvfs_module_context *ntvfs, } 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)) { - talloc_free(lck); return status; } @@ -377,6 +466,7 @@ static NTSTATUS pvfs_rename_nt(struct ntvfs_module_context *ntvfs, struct pvfs_state *pvfs = ntvfs->private_data; NTSTATUS status; struct pvfs_filename *name1, *name2; + struct odb_lock *lck = NULL; switch (ren->ntrename.in.flags) { case RENAME_FLAG_RENAME: @@ -422,7 +512,18 @@ static NTSTATUS pvfs_rename_nt(struct ntvfs_module_context *ntvfs, return status; } - status = pvfs_can_rename(pvfs, req, name1, NULL); + 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; } @@ -432,6 +533,9 @@ static NTSTATUS pvfs_rename_nt(struct ntvfs_module_context *ntvfs, status = pvfs_access_check_parent(pvfs, req, name2, SEC_DIR_ADD_FILE); NT_STATUS_NOT_OK_RETURN(status); status = pvfs_do_rename(pvfs, name1, name2->full_name); + if (NT_STATUS_IS_OK(status)) { + status = odb_rename(lck, name2->full_name); + } NT_STATUS_NOT_OK_RETURN(status); break; diff --git a/source4/ntvfs/posix/pvfs_setfileinfo.c b/source4/ntvfs/posix/pvfs_setfileinfo.c index fbbb8c2d4b..da1e31e639 100644 --- a/source4/ntvfs/posix/pvfs_setfileinfo.c +++ b/source4/ntvfs/posix/pvfs_setfileinfo.c @@ -152,6 +152,9 @@ static NTSTATUS pvfs_setfileinfo_rename(struct pvfs_state *pvfs, } status = pvfs_can_delete(pvfs, req, name2, NULL); + if (NT_STATUS_EQUAL(status, NT_STATUS_DELETE_PENDING)) { + return NT_STATUS_ACCESS_DENIED; + } if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) { return NT_STATUS_ACCESS_DENIED; } @@ -352,6 +355,9 @@ NTSTATUS pvfs_setfileinfo(struct ntvfs_module_context *ntvfs, case RAW_SFILEINFO_ALLOCATION_INFO: case RAW_SFILEINFO_ALLOCATION_INFORMATION: + status = pvfs_break_level2_oplocks(f); + NT_STATUS_NOT_OK_RETURN(status); + newstats.dos.alloc_size = info->allocation_info.in.alloc_size; if (newstats.dos.alloc_size < newstats.st.st_size) { newstats.st.st_size = newstats.dos.alloc_size; @@ -362,6 +368,9 @@ NTSTATUS pvfs_setfileinfo(struct ntvfs_module_context *ntvfs, case RAW_SFILEINFO_END_OF_FILE_INFO: case RAW_SFILEINFO_END_OF_FILE_INFORMATION: + status = pvfs_break_level2_oplocks(f); + NT_STATUS_NOT_OK_RETURN(status); + newstats.st.st_size = info->end_of_file_info.in.size; break; @@ -463,6 +472,84 @@ NTSTATUS pvfs_setfileinfo(struct ntvfs_module_context *ntvfs, return pvfs_dosattrib_save(pvfs, h->name, h->fd); } +/* + retry an open after a sharing violation +*/ +static void pvfs_retry_setpathinfo(struct pvfs_odb_retry *r, + struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + void *_info, + void *private_data, + enum pvfs_wait_notice reason) +{ + union smb_setfileinfo *info = talloc_get_type(_info, + union smb_setfileinfo); + NTSTATUS status = NT_STATUS_INTERNAL_ERROR; + + talloc_free(r); + + switch (reason) { + case PVFS_WAIT_CANCEL: +/*TODO*/ + status = NT_STATUS_CANCELLED; + break; + case PVFS_WAIT_TIMEOUT: + /* if it timed out, then give the failure + immediately */ +/*TODO*/ + status = NT_STATUS_SHARING_VIOLATION; + break; + case PVFS_WAIT_EVENT: + + /* try the open again, which could trigger another retry setup + if it wants to, so we have to unmark the async flag so we + will know if it does a second async reply */ + req->async_states->state &= ~NTVFS_ASYNC_STATE_ASYNC; + + status = pvfs_setpathinfo(ntvfs, req, info); + if (req->async_states->state & NTVFS_ASYNC_STATE_ASYNC) { + /* the 2nd try also replied async, so we don't send + the reply yet */ + return; + } + + /* re-mark it async, just in case someone up the chain does + paranoid checking */ + req->async_states->state |= NTVFS_ASYNC_STATE_ASYNC; + break; + } + + /* send the reply up the chain */ + req->async_states->status = status; + req->async_states->send_fn(req); +} + +/* + setup for a unlink retry after a sharing violation + or a non granted oplock +*/ +static NTSTATUS pvfs_setpathinfo_setup_retry(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_setfileinfo *info, + struct odb_lock *lck, + NTSTATUS status) +{ + struct pvfs_state *pvfs = ntvfs->private_data; + struct timeval end_time; + + if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) { + end_time = timeval_add(&req->statistics.request_time, + 0, pvfs->sharing_violation_delay); + } else if (NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) { + end_time = timeval_add(&req->statistics.request_time, + pvfs->oplock_break_timeout, 0); + } else { + return NT_STATUS_INTERNAL_ERROR; + } + + return pvfs_odb_retry_setup(ntvfs, req, lck, end_time, info, NULL, + pvfs_retry_setpathinfo); +} /* set info on a pathname @@ -477,6 +564,7 @@ NTSTATUS pvfs_setpathinfo(struct ntvfs_module_context *ntvfs, struct utimbuf unix_times; uint32_t access_needed; uint32_t change_mask = 0; + struct odb_lock *lck = NULL; /* resolve the cifs name to a posix name */ status = pvfs_resolve_name(pvfs, req, info->generic.in.file.path, @@ -551,6 +639,20 @@ NTSTATUS pvfs_setpathinfo(struct ntvfs_module_context *ntvfs, case RAW_SFILEINFO_ALLOCATION_INFO: case RAW_SFILEINFO_ALLOCATION_INFORMATION: + status = pvfs_can_update_file_size(pvfs, req, name, &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_setpathinfo_setup_retry(pvfs->ntvfs, req, info, lck, status); + } + NT_STATUS_NOT_OK_RETURN(status); + if (info->allocation_info.in.alloc_size > newstats.dos.alloc_size) { /* strange. Increasing the allocation size via setpathinfo should be silently ignored */ @@ -566,6 +668,20 @@ NTSTATUS pvfs_setpathinfo(struct ntvfs_module_context *ntvfs, case RAW_SFILEINFO_END_OF_FILE_INFO: case RAW_SFILEINFO_END_OF_FILE_INFORMATION: + status = pvfs_can_update_file_size(pvfs, req, name, &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_setpathinfo_setup_retry(pvfs->ntvfs, req, info, lck, status); + } + NT_STATUS_NOT_OK_RETURN(status); + newstats.st.st_size = info->end_of_file_info.in.size; break; diff --git a/source4/ntvfs/posix/pvfs_unlink.c b/source4/ntvfs/posix/pvfs_unlink.c index bda4014bee..7a2d964b9d 100644 --- a/source4/ntvfs/posix/pvfs_unlink.c +++ b/source4/ntvfs/posix/pvfs_unlink.c @@ -23,6 +23,84 @@ #include "vfs_posix.h" #include "system/dir.h" +/* + retry an open after a sharing violation +*/ +static void pvfs_retry_unlink(struct pvfs_odb_retry *r, + struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + void *_io, + void *private_data, + enum pvfs_wait_notice reason) +{ + union smb_unlink *io = talloc_get_type(_io, union smb_unlink); + NTSTATUS status = NT_STATUS_INTERNAL_ERROR; + + talloc_free(r); + + switch (reason) { + case PVFS_WAIT_CANCEL: +/*TODO*/ + status = NT_STATUS_CANCELLED; + break; + case PVFS_WAIT_TIMEOUT: + /* if it timed out, then give the failure + immediately */ +/*TODO*/ + status = NT_STATUS_SHARING_VIOLATION; + break; + case PVFS_WAIT_EVENT: + + /* try the open again, which could trigger another retry setup + if it wants to, so we have to unmark the async flag so we + will know if it does a second async reply */ + req->async_states->state &= ~NTVFS_ASYNC_STATE_ASYNC; + + status = pvfs_unlink(ntvfs, req, io); + if (req->async_states->state & NTVFS_ASYNC_STATE_ASYNC) { + /* the 2nd try also replied async, so we don't send + the reply yet */ + return; + } + + /* re-mark it async, just in case someone up the chain does + paranoid checking */ + req->async_states->state |= NTVFS_ASYNC_STATE_ASYNC; + break; + } + + /* send the reply up the chain */ + req->async_states->status = status; + req->async_states->send_fn(req); +} + +/* + setup for a unlink retry after a sharing violation + or a non granted oplock +*/ +static NTSTATUS pvfs_unlink_setup_retry(struct ntvfs_module_context *ntvfs, + struct ntvfs_request *req, + union smb_unlink *io, + struct odb_lock *lck, + NTSTATUS status) +{ + struct pvfs_state *pvfs = ntvfs->private_data; + struct timeval end_time; + + if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) { + end_time = timeval_add(&req->statistics.request_time, + 0, pvfs->sharing_violation_delay); + } else if (NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) { + end_time = timeval_add(&req->statistics.request_time, + pvfs->oplock_break_timeout, 0); + } else { + return NT_STATUS_INTERNAL_ERROR; + } + + return pvfs_odb_retry_setup(ntvfs, req, lck, end_time, io, NULL, + pvfs_retry_unlink); +} + /* unlink a file @@ -67,6 +145,7 @@ static NTSTATUS pvfs_unlink_one(struct pvfs_state *pvfs, struct pvfs_filename *name) { NTSTATUS status; + struct odb_lock *lck = NULL; /* make sure its matches the given attributes */ status = pvfs_match_attrib(pvfs, name, @@ -75,7 +154,20 @@ static NTSTATUS pvfs_unlink_one(struct pvfs_state *pvfs, return status; } - status = pvfs_can_delete(pvfs, req, name, NULL); + status = pvfs_can_delete(pvfs, req, name, &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_unlink_setup_retry(pvfs->ntvfs, req, unl, lck, status); + } + if (!NT_STATUS_IS_OK(status)) { return status; } diff --git a/source4/ntvfs/posix/pvfs_write.c b/source4/ntvfs/posix/pvfs_write.c index 5a11f01952..dda8c83407 100644 --- a/source4/ntvfs/posix/pvfs_write.c +++ b/source4/ntvfs/posix/pvfs_write.c @@ -57,7 +57,10 @@ NTSTATUS pvfs_write(struct ntvfs_module_context *ntvfs, wr->writex.in.count, WRITE_LOCK); NT_STATUS_NOT_OK_RETURN(status); - + + status = pvfs_break_level2_oplocks(f); + NT_STATUS_NOT_OK_RETURN(status); + if (f->handle->name->stream_name) { ret = pvfs_stream_write(pvfs, f->handle, diff --git a/source4/ntvfs/posix/vfs_posix.c b/source4/ntvfs/posix/vfs_posix.c index e058e6f546..ca874d1db1 100644 --- a/source4/ntvfs/posix/vfs_posix.c +++ b/source4/ntvfs/posix/vfs_posix.c @@ -91,6 +91,10 @@ static void pvfs_setup_options(struct pvfs_state *pvfs) PVFS_SHARE_DELAY, PVFS_SHARE_DELAY_DEFAULT); + pvfs->oplock_break_timeout = share_int_option(scfg, + PVFS_OPLOCK_TIMEOUT, + PVFS_OPLOCK_TIMEOUT_DEFAULT); + pvfs->share_name = talloc_strdup(pvfs, scfg->name); pvfs->fs_attribs = diff --git a/source4/ntvfs/posix/vfs_posix.h b/source4/ntvfs/posix/vfs_posix.h index 84c456dcfe..4d22a91714 100644 --- a/source4/ntvfs/posix/vfs_posix.h +++ b/source4/ntvfs/posix/vfs_posix.h @@ -29,6 +29,7 @@ #include "dsdb/samdb/samdb.h" struct pvfs_wait; +struct pvfs_oplock; /* this is the private structure for the posix vfs backend. It is used to hold per-connection (per tree connect) state information */ @@ -51,9 +52,12 @@ struct pvfs_state { ntcancel */ struct pvfs_wait *wait_list; - /* the sharing violation timeout */ + /* the sharing violation timeout (nsecs) */ uint_t sharing_violation_delay; + /* the oplock break timeout (secs) */ + uint_t oplock_break_timeout; + /* filesystem attributes (see FS_ATTR_*) */ uint32_t fs_attribs; @@ -154,6 +158,13 @@ struct pvfs_file_handle { bool have_opendb_entry; + /* + * we need to wait for oplock break requests from other processes, + * and we need to remember the pvfs_file so we can correctly + * forward the oplock break to the client + */ + struct pvfs_oplock *oplock; + /* we need this hook back to our parent for lock destruction */ struct pvfs_state *pvfs; @@ -230,10 +241,16 @@ struct pvfs_dir; /* types of notification for pvfs wait events */ enum pvfs_wait_notice {PVFS_WAIT_EVENT, PVFS_WAIT_TIMEOUT, PVFS_WAIT_CANCEL}; +/* + state of a pending retry +*/ +struct pvfs_odb_retry; + #define PVFS_EADB "posix:eadb" #define PVFS_XATTR "posix:xattr" #define PVFS_FAKE_OPLOCKS "posix:fakeoplocks" #define PVFS_SHARE_DELAY "posix:sharedelay" +#define PVFS_OPLOCK_TIMEOUT "posix:oplocktimeout" #define PVFS_ALLOCATION_ROUNDING "posix:allocationrounding" #define PVFS_SEARCH_INACTIVITY "posix:searchinactivity" #define PVFS_ACL "posix:acl" @@ -241,7 +258,8 @@ enum pvfs_wait_notice {PVFS_WAIT_EVENT, PVFS_WAIT_TIMEOUT, PVFS_WAIT_CANCEL}; #define PVFS_XATTR_DEFAULT true #define PVFS_FAKE_OPLOCKS_DEFAULT false -#define PVFS_SHARE_DELAY_DEFAULT 1000000 +#define PVFS_SHARE_DELAY_DEFAULT 1000000 /* nsecs */ +#define PVFS_OPLOCK_TIMEOUT_DEFAULT 30 /* secs */ #define PVFS_ALLOCATION_ROUNDING_DEFAULT 512 #define PVFS_SEARCH_INACTIVITY_DEFAULT 300 |