Commit 80f3fda9 authored by Alexandre Julliard's avatar Alexandre Julliard

server: Add support for maintaining a client-side directory cache.

parent fcf0829b
......@@ -1452,6 +1452,21 @@ enum server_fd_type
struct get_directory_cache_entry_request
{
struct request_header __header;
obj_handle_t handle;
};
struct get_directory_cache_entry_reply
{
struct reply_header __header;
int entry;
/* VARARG(free,ints); */
char __pad_12[4];
};
struct flush_request
{
struct request_header __header;
......@@ -5401,6 +5416,7 @@ enum request
REQ_alloc_file_handle,
REQ_get_handle_unix_name,
REQ_get_handle_fd,
REQ_get_directory_cache_entry,
REQ_flush,
REQ_lock_file,
REQ_unlock_file,
......@@ -5679,6 +5695,7 @@ union generic_request
struct alloc_file_handle_request alloc_file_handle_request;
struct get_handle_unix_name_request get_handle_unix_name_request;
struct get_handle_fd_request get_handle_fd_request;
struct get_directory_cache_entry_request get_directory_cache_entry_request;
struct flush_request flush_request;
struct lock_file_request lock_file_request;
struct unlock_file_request unlock_file_request;
......@@ -5955,6 +5972,7 @@ union generic_reply
struct alloc_file_handle_reply alloc_file_handle_reply;
struct get_handle_unix_name_reply get_handle_unix_name_reply;
struct get_handle_fd_reply get_handle_fd_reply;
struct get_directory_cache_entry_reply get_directory_cache_entry_reply;
struct flush_reply flush_reply;
struct lock_file_reply lock_file_reply;
struct unlock_file_reply unlock_file_reply;
......@@ -6184,6 +6202,6 @@ union generic_reply
struct terminate_job_reply terminate_job_reply;
};
#define SERVER_PROTOCOL_VERSION 502
#define SERVER_PROTOCOL_VERSION 503
#endif /* __WINE_WINE_SERVER_PROTOCOL_H */
......@@ -141,6 +141,8 @@ struct dir
struct list change_records; /* data for the change */
struct list in_entry; /* entry in the inode dirs list */
struct inode *inode; /* inode of the associated directory */
struct process *client_process; /* client process that has a cache for this directory */
int client_entry; /* entry in client process cache */
};
static struct fd *dir_get_fd( struct object *obj );
......@@ -149,6 +151,7 @@ static int dir_set_sd( struct object *obj, const struct security_descriptor *sd,
unsigned int set_info );
static void dir_dump( struct object *obj, int verbose );
static struct object_type *dir_get_type( struct object *obj );
static int dir_close_handle( struct object *obj, struct process *process, obj_handle_t handle );
static void dir_destroy( struct object *obj );
static const struct object_ops dir_ops =
......@@ -169,7 +172,7 @@ static const struct object_ops dir_ops =
no_link_name, /* link_name */
NULL, /* unlink_name */
no_open_file, /* open_file */
fd_close_handle, /* close_handle */
dir_close_handle, /* close_handle */
dir_destroy /* destroy */
};
......@@ -192,6 +195,86 @@ static const struct fd_ops dir_fd_ops =
static struct list change_list = LIST_INIT(change_list);
/* per-process structure to keep track of cache entries on the client size */
struct dir_cache
{
unsigned int size;
unsigned int count;
unsigned char state[1];
};
enum dir_cache_state
{
DIR_CACHE_STATE_FREE,
DIR_CACHE_STATE_INUSE,
DIR_CACHE_STATE_RELEASED
};
/* return an array of cache entries that can be freed on the client side */
static int *get_free_dir_cache_entries( struct process *process, data_size_t *size )
{
int *ret;
struct dir_cache *cache = process->dir_cache;
unsigned int i, j, count;
if (!cache) return NULL;
for (i = count = 0; i < cache->count && count < *size / sizeof(*ret); i++)
if (cache->state[i] == DIR_CACHE_STATE_RELEASED) count++;
if (!count) return NULL;
if ((ret = malloc( count * sizeof(*ret) )))
{
for (i = j = 0; j < count; i++)
{
if (cache->state[i] != DIR_CACHE_STATE_RELEASED) continue;
cache->state[i] = DIR_CACHE_STATE_FREE;
ret[j++] = i;
}
*size = count * sizeof(*ret);
}
return ret;
}
/* allocate a new client-side directory cache entry */
static int alloc_dir_cache_entry( struct dir *dir, struct process *process )
{
unsigned int i = 0;
struct dir_cache *cache = process->dir_cache;
if (cache)
for (i = 0; i < cache->count; i++)
if (cache->state[i] == DIR_CACHE_STATE_FREE) goto found;
if (!cache || cache->count == cache->size)
{
unsigned int size = cache ? cache->size * 2 : 256;
if (!(cache = realloc( cache, offsetof( struct dir_cache, state[size] ))))
{
set_error( STATUS_NO_MEMORY );
return -1;
}
process->dir_cache = cache;
cache->size = size;
}
cache->count = i + 1;
found:
cache->state[i] = DIR_CACHE_STATE_INUSE;
return i;
}
/* release a directory cache entry; it will be freed on the client side on the next cache request */
static void release_dir_cache_entry( struct dir *dir )
{
struct dir_cache *cache;
if (!dir->client_process) return;
cache = dir->client_process->dir_cache;
cache->state[dir->client_entry] = DIR_CACHE_STATE_RELEASED;
release_object( dir->client_process );
dir->client_process = NULL;
}
static void dnotify_adjust_changes( struct dir *dir )
{
#if defined(F_SETSIG) && defined(F_NOTIFY)
......@@ -385,6 +468,15 @@ static struct change_record *get_first_change_record( struct dir *dir )
return LIST_ENTRY( ptr, struct change_record, entry );
}
static int dir_close_handle( struct object *obj, struct process *process, obj_handle_t handle )
{
struct dir *dir = (struct dir *)obj;
if (!fd_close_handle( obj, process, handle )) return 0;
if (obj->handle_count == 1) release_dir_cache_entry( dir ); /* closing last handle, release cache */
return 1; /* ok to close */
}
static void dir_destroy( struct object *obj )
{
struct change_record *record;
......@@ -402,6 +494,7 @@ static void dir_destroy( struct object *obj )
while ((record = get_first_change_record( dir ))) free( record );
release_dir_cache_entry( dir );
release_object( dir->fd );
if (inotify_fd && list_empty( &change_list ))
......@@ -1104,6 +1197,7 @@ struct object *create_dir_obj( struct fd *fd, unsigned int access, mode_t mode )
dir->fd = fd;
dir->mode = mode;
dir->uid = ~(uid_t)0;
dir->client_process = NULL;
set_fd_user( fd, &dir_fd_ops, &dir->obj );
dir_add_to_existing_notify( dir );
......@@ -1111,6 +1205,32 @@ struct object *create_dir_obj( struct fd *fd, unsigned int access, mode_t mode )
return &dir->obj;
}
/* retrieve (or allocate) the client-side directory cache entry */
DECL_HANDLER(get_directory_cache_entry)
{
struct dir *dir;
int *free_entries;
data_size_t free_size;
if (!(dir = get_dir_obj( current->process, req->handle, 0 ))) return;
if (!dir->client_process)
{
if ((dir->client_entry = alloc_dir_cache_entry( dir, current->process )) == -1) goto done;
dir->client_process = (struct process *)grab_object( current->process );
}
if (dir->client_process == current->process) reply->entry = dir->client_entry;
else set_error( STATUS_SHARING_VIOLATION );
done: /* allow freeing entries even on failure */
free_size = get_reply_max_size();
free_entries = get_free_dir_cache_entries( current->process, &free_size );
if (free_entries) set_reply_data_ptr( free_entries, free_size );
release_object( dir );
}
/* enable change notifications for a directory */
DECL_HANDLER(read_directory_changes)
{
......
......@@ -526,6 +526,7 @@ struct thread *create_process( int fd, struct thread *parent_thread, int inherit
process->idle_event = NULL;
process->peb = 0;
process->ldt_copy = 0;
process->dir_cache = NULL;
process->winstation = 0;
process->desktop = 0;
process->token = NULL;
......@@ -630,6 +631,7 @@ static void process_destroy( struct object *obj )
if (process->idle_event) release_object( process->idle_event );
if (process->id) free_ptid( process->id );
if (process->token) release_object( process->token );
free( process->dir_cache );
}
/* dump a process on stdout for debugging purposes */
......
......@@ -90,6 +90,7 @@ struct process
struct list dlls; /* list of loaded dlls */
client_ptr_t peb; /* PEB address in client address space */
client_ptr_t ldt_copy; /* pointer to LDT copy in client addr space */
struct dir_cache *dir_cache; /* map of client-side directory cache */
unsigned int trace_data; /* opaque data used by the process tracing mechanism */
struct list rawinput_devices;/* list of registered rawinput devices */
const struct rawinput_device *rawinput_mouse; /* rawinput mouse device, if any */
......
......@@ -1200,6 +1200,15 @@ enum server_fd_type
};
/* Retrieve (or allocate) the client-side directory cache entry */
@REQ(get_directory_cache_entry)
obj_handle_t handle; /* handle to the directory */
@REPLY
int entry; /* cache entry on the client side */
VARARG(free,ints); /* entries that can be freed */
@END
/* Flush a file buffers */
@REQ(flush)
int blocking; /* whether it's a blocking flush */
......
......@@ -156,6 +156,7 @@ DECL_HANDLER(open_file_object);
DECL_HANDLER(alloc_file_handle);
DECL_HANDLER(get_handle_unix_name);
DECL_HANDLER(get_handle_fd);
DECL_HANDLER(get_directory_cache_entry);
DECL_HANDLER(flush);
DECL_HANDLER(lock_file);
DECL_HANDLER(unlock_file);
......@@ -433,6 +434,7 @@ static const req_handler req_handlers[REQ_NB_REQUESTS] =
(req_handler)req_alloc_file_handle,
(req_handler)req_get_handle_unix_name,
(req_handler)req_get_handle_fd,
(req_handler)req_get_directory_cache_entry,
(req_handler)req_flush,
(req_handler)req_lock_file,
(req_handler)req_unlock_file,
......@@ -975,6 +977,10 @@ C_ASSERT( FIELD_OFFSET(struct get_handle_fd_reply, cacheable) == 12 );
C_ASSERT( FIELD_OFFSET(struct get_handle_fd_reply, access) == 16 );
C_ASSERT( FIELD_OFFSET(struct get_handle_fd_reply, options) == 20 );
C_ASSERT( sizeof(struct get_handle_fd_reply) == 24 );
C_ASSERT( FIELD_OFFSET(struct get_directory_cache_entry_request, handle) == 12 );
C_ASSERT( sizeof(struct get_directory_cache_entry_request) == 16 );
C_ASSERT( FIELD_OFFSET(struct get_directory_cache_entry_reply, entry) == 8 );
C_ASSERT( sizeof(struct get_directory_cache_entry_reply) == 16 );
C_ASSERT( FIELD_OFFSET(struct flush_request, blocking) == 12 );
C_ASSERT( FIELD_OFFSET(struct flush_request, async) == 16 );
C_ASSERT( sizeof(struct flush_request) == 56 );
......
......@@ -1717,6 +1717,17 @@ static void dump_get_handle_fd_reply( const struct get_handle_fd_reply *req )
fprintf( stderr, ", options=%08x", req->options );
}
static void dump_get_directory_cache_entry_request( const struct get_directory_cache_entry_request *req )
{
fprintf( stderr, " handle=%04x", req->handle );
}
static void dump_get_directory_cache_entry_reply( const struct get_directory_cache_entry_reply *req )
{
fprintf( stderr, " entry=%d", req->entry );
dump_varargs_ints( ", free=", cur_size );
}
static void dump_flush_request( const struct flush_request *req )
{
fprintf( stderr, " blocking=%d", req->blocking );
......@@ -4344,6 +4355,7 @@ static const dump_func req_dumpers[REQ_NB_REQUESTS] = {
(dump_func)dump_alloc_file_handle_request,
(dump_func)dump_get_handle_unix_name_request,
(dump_func)dump_get_handle_fd_request,
(dump_func)dump_get_directory_cache_entry_request,
(dump_func)dump_flush_request,
(dump_func)dump_lock_file_request,
(dump_func)dump_unlock_file_request,
......@@ -4618,6 +4630,7 @@ static const dump_func reply_dumpers[REQ_NB_REQUESTS] = {
(dump_func)dump_alloc_file_handle_reply,
(dump_func)dump_get_handle_unix_name_reply,
(dump_func)dump_get_handle_fd_reply,
(dump_func)dump_get_directory_cache_entry_reply,
(dump_func)dump_flush_reply,
(dump_func)dump_lock_file_reply,
NULL,
......@@ -4892,6 +4905,7 @@ static const char * const req_names[REQ_NB_REQUESTS] = {
"alloc_file_handle",
"get_handle_unix_name",
"get_handle_fd",
"get_directory_cache_entry",
"flush",
"lock_file",
"unlock_file",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment