mirror of
https://github.com/samba-team/samba.git
synced 2024-12-22 13:34:15 +03:00
476672b647
Signed-off-by: Michael Adam <obnox@samba.org> Reviewed-by: Jeremy Allison <jra@samba.org>
301 lines
8.1 KiB
C
301 lines
8.1 KiB
C
/*
|
|
Unix SMB/CIFS implementation.
|
|
|
|
POSIX NTVFS backend - notify
|
|
|
|
Copyright (C) Andrew Tridgell 2006
|
|
|
|
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 "vfs_posix.h"
|
|
#include "lib/messaging/irpc.h"
|
|
#include "messaging/messaging.h"
|
|
#include "../lib/util/dlinklist.h"
|
|
#include "lib/events/events.h"
|
|
|
|
/* pending notifies buffer, hung off struct pvfs_file for open directories
|
|
that have used change notify */
|
|
struct pvfs_notify_buffer {
|
|
struct pvfs_file *f;
|
|
uint32_t num_changes;
|
|
struct notify_changes *changes;
|
|
uint32_t max_buffer_size;
|
|
uint32_t current_buffer_size;
|
|
bool overflowed;
|
|
|
|
/* a list of requests waiting for events on this handle */
|
|
struct notify_pending {
|
|
struct notify_pending *next, *prev;
|
|
struct ntvfs_request *req;
|
|
union smb_notify *info;
|
|
} *pending;
|
|
};
|
|
|
|
/*
|
|
send a notify on the next event run.
|
|
*/
|
|
static void pvfs_notify_send_next(struct tevent_context *ev, struct tevent_timer *te,
|
|
struct timeval t, void *ptr)
|
|
{
|
|
struct ntvfs_request *req = talloc_get_type(ptr, struct ntvfs_request);
|
|
req->async_states->send_fn(req);
|
|
}
|
|
|
|
|
|
/*
|
|
send a reply to a pending notify request
|
|
*/
|
|
static void pvfs_notify_send(struct pvfs_notify_buffer *notify_buffer,
|
|
NTSTATUS status, bool immediate)
|
|
{
|
|
struct notify_pending *pending = notify_buffer->pending;
|
|
struct ntvfs_request *req;
|
|
union smb_notify *info;
|
|
|
|
if (notify_buffer->current_buffer_size > notify_buffer->max_buffer_size &&
|
|
notify_buffer->num_changes != 0) {
|
|
/* on buffer overflow return no changes and destroys the notify buffer */
|
|
notify_buffer->num_changes = 0;
|
|
while (notify_buffer->pending) {
|
|
pvfs_notify_send(notify_buffer, NT_STATUS_OK, immediate);
|
|
}
|
|
notify_buffer->overflowed = true;
|
|
return;
|
|
}
|
|
|
|
/* see if there is anyone waiting */
|
|
if (notify_buffer->pending == NULL) {
|
|
return;
|
|
}
|
|
|
|
DLIST_REMOVE(notify_buffer->pending, pending);
|
|
|
|
req = pending->req;
|
|
info = pending->info;
|
|
|
|
info->nttrans.out.num_changes = notify_buffer->num_changes;
|
|
info->nttrans.out.changes = talloc_steal(req, notify_buffer->changes);
|
|
notify_buffer->num_changes = 0;
|
|
notify_buffer->overflowed = false;
|
|
notify_buffer->changes = NULL;
|
|
notify_buffer->current_buffer_size = 0;
|
|
|
|
talloc_free(pending);
|
|
|
|
if (info->nttrans.out.num_changes != 0) {
|
|
status = NT_STATUS_OK;
|
|
}
|
|
|
|
req->async_states->status = status;
|
|
|
|
if (immediate) {
|
|
req->async_states->send_fn(req);
|
|
return;
|
|
}
|
|
|
|
/* we can't call pvfs_notify_send() directly here, as that
|
|
would free the request, and the ntvfs modules above us
|
|
could use it, so call it on the next event */
|
|
tevent_add_timer(req->ctx->event_ctx,
|
|
req, timeval_zero(), pvfs_notify_send_next, req);
|
|
}
|
|
|
|
/*
|
|
destroy a notify buffer. Called when the handle is closed
|
|
*/
|
|
static int pvfs_notify_destructor(struct pvfs_notify_buffer *n)
|
|
{
|
|
notify_remove(n->f->pvfs->notify_context, n);
|
|
n->f->notify_buffer = NULL;
|
|
pvfs_notify_send(n, NT_STATUS_OK, true);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
called when a async notify event comes in
|
|
*/
|
|
static void pvfs_notify_callback(void *private_data, const struct notify_event *ev)
|
|
{
|
|
struct pvfs_notify_buffer *n = talloc_get_type(private_data, struct pvfs_notify_buffer);
|
|
size_t len;
|
|
struct notify_changes *n2;
|
|
char *new_path;
|
|
|
|
if (n->overflowed) {
|
|
return;
|
|
}
|
|
|
|
n2 = talloc_realloc(n, n->changes, struct notify_changes, n->num_changes+1);
|
|
if (n2 == NULL) {
|
|
/* nothing much we can do for this */
|
|
return;
|
|
}
|
|
n->changes = n2;
|
|
|
|
new_path = talloc_strdup(n->changes, ev->path);
|
|
if (new_path == NULL) {
|
|
return;
|
|
}
|
|
string_replace(new_path, '/', '\\');
|
|
|
|
n->changes[n->num_changes].action = ev->action;
|
|
n->changes[n->num_changes].name.s = new_path;
|
|
n->num_changes++;
|
|
|
|
/*
|
|
work out how much room this will take in the buffer
|
|
*/
|
|
len = 12 + strlen_m(ev->path)*2;
|
|
if (len & 3) {
|
|
len += 4 - (len & 3);
|
|
}
|
|
n->current_buffer_size += len;
|
|
|
|
/* send what we have, unless its the first part of a rename */
|
|
if (ev->action != NOTIFY_ACTION_OLD_NAME) {
|
|
pvfs_notify_send(n, NT_STATUS_OK, true);
|
|
}
|
|
}
|
|
|
|
/*
|
|
setup a notify buffer on a directory handle
|
|
*/
|
|
static NTSTATUS pvfs_notify_setup(struct pvfs_state *pvfs, struct pvfs_file *f,
|
|
uint32_t buffer_size, uint32_t filter, bool recursive)
|
|
{
|
|
NTSTATUS status;
|
|
struct notify_entry e;
|
|
|
|
/* We may not fill in all the elements in this entry -
|
|
* structure may in future be shared with Samba3 */
|
|
ZERO_STRUCT(e);
|
|
|
|
/* We may not fill in all the elements in this entry -
|
|
* structure may in future be shared with Samba3 */
|
|
ZERO_STRUCT(e);
|
|
|
|
f->notify_buffer = talloc_zero(f, struct pvfs_notify_buffer);
|
|
NT_STATUS_HAVE_NO_MEMORY(f->notify_buffer);
|
|
|
|
f->notify_buffer->max_buffer_size = buffer_size;
|
|
f->notify_buffer->f = f;
|
|
|
|
e.filter = filter;
|
|
e.path = f->handle->name->full_name;
|
|
if (recursive) {
|
|
e.subdir_filter = filter;
|
|
} else {
|
|
e.subdir_filter = 0;
|
|
}
|
|
|
|
status = notify_add(pvfs->notify_context, &e,
|
|
pvfs_notify_callback, f->notify_buffer);
|
|
NT_STATUS_NOT_OK_RETURN(status);
|
|
|
|
talloc_set_destructor(f->notify_buffer, pvfs_notify_destructor);
|
|
|
|
return NT_STATUS_OK;
|
|
}
|
|
|
|
/*
|
|
called from the pvfs_wait code when either an event has come in, or
|
|
the notify request has been cancelled
|
|
*/
|
|
static void pvfs_notify_end(void *private_data, enum pvfs_wait_notice reason)
|
|
{
|
|
struct pvfs_notify_buffer *notify_buffer = talloc_get_type(private_data,
|
|
struct pvfs_notify_buffer);
|
|
if (reason == PVFS_WAIT_CANCEL) {
|
|
pvfs_notify_send(notify_buffer, NT_STATUS_CANCELLED, false);
|
|
} else {
|
|
pvfs_notify_send(notify_buffer, NT_STATUS_OK, true);
|
|
}
|
|
}
|
|
|
|
/* change notify request - always async. This request blocks until the
|
|
event buffer is non-empty */
|
|
NTSTATUS pvfs_notify(struct ntvfs_module_context *ntvfs,
|
|
struct ntvfs_request *req,
|
|
union smb_notify *info)
|
|
{
|
|
struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
|
|
struct pvfs_state);
|
|
struct pvfs_file *f;
|
|
NTSTATUS status;
|
|
struct notify_pending *pending;
|
|
|
|
if (info->nttrans.level != RAW_NOTIFY_NTTRANS) {
|
|
return ntvfs_map_notify(ntvfs, req, info);
|
|
}
|
|
|
|
f = pvfs_find_fd(pvfs, req, info->nttrans.in.file.ntvfs);
|
|
if (!f) {
|
|
return NT_STATUS_INVALID_HANDLE;
|
|
}
|
|
|
|
/* this request doesn't make sense unless its async */
|
|
if (!(req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) {
|
|
return NT_STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
/* its only valid for directories */
|
|
if (f->handle->fd != -1) {
|
|
return NT_STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
/* if the handle doesn't currently have a notify buffer then
|
|
create one */
|
|
if (f->notify_buffer == NULL) {
|
|
status = pvfs_notify_setup(pvfs, f,
|
|
info->nttrans.in.buffer_size,
|
|
info->nttrans.in.completion_filter,
|
|
info->nttrans.in.recursive);
|
|
NT_STATUS_NOT_OK_RETURN(status);
|
|
}
|
|
|
|
/* we update the max_buffer_size on each call, but we do not
|
|
update the recursive flag or filter */
|
|
f->notify_buffer->max_buffer_size = info->nttrans.in.buffer_size;
|
|
|
|
pending = talloc(f->notify_buffer, struct notify_pending);
|
|
NT_STATUS_HAVE_NO_MEMORY(pending);
|
|
|
|
pending->req = talloc_reference(pending, req);
|
|
NT_STATUS_HAVE_NO_MEMORY(pending->req);
|
|
pending->info = info;
|
|
|
|
DLIST_ADD_END(f->notify_buffer->pending, pending);
|
|
|
|
/* if the buffer is empty then start waiting */
|
|
if (f->notify_buffer->num_changes == 0 &&
|
|
!f->notify_buffer->overflowed) {
|
|
struct pvfs_wait *wait_handle;
|
|
wait_handle = pvfs_wait_message(pvfs, req, -1,
|
|
timeval_zero(),
|
|
pvfs_notify_end,
|
|
f->notify_buffer);
|
|
NT_STATUS_HAVE_NO_MEMORY(wait_handle);
|
|
talloc_steal(req, wait_handle);
|
|
return NT_STATUS_OK;
|
|
}
|
|
|
|
req->async_states->state |= NTVFS_ASYNC_STATE_ASYNC;
|
|
pvfs_notify_send(f->notify_buffer, NT_STATUS_OK, false);
|
|
|
|
return NT_STATUS_OK;
|
|
}
|