From 80f3fda93484a11b0ee1803625278c8c15ca86d9 Mon Sep 17 00:00:00 2001 From: Alexandre Julliard Date: Mon, 2 May 2016 14:39:16 +0900 Subject: [PATCH] server: Add support for maintaining a client-side directory cache. Signed-off-by: Alexandre Julliard --- include/wine/server_protocol.h | 20 ++++++- server/change.c | 122 ++++++++++++++++++++++++++++++++++++++++- server/process.c | 2 + server/process.h | 1 + server/protocol.def | 9 +++ server/request.h | 6 ++ server/trace.c | 14 +++++ 7 files changed, 172 insertions(+), 2 deletions(-) diff --git a/include/wine/server_protocol.h b/include/wine/server_protocol.h index a60fd25acf9..3b4d3372a24 100644 --- a/include/wine/server_protocol.h +++ b/include/wine/server_protocol.h @@ -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 */ diff --git a/server/change.c b/server/change.c index 36a1997673f..7c0ca990bfa 100644 --- a/server/change.c +++ b/server/change.c @@ -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) { diff --git a/server/process.c b/server/process.c index 52bd46298c4..48ada997fda 100644 --- a/server/process.c +++ b/server/process.c @@ -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 */ diff --git a/server/process.h b/server/process.h index 34b6ea66d60..eaa4adb4b61 100644 --- a/server/process.h +++ b/server/process.h @@ -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 */ diff --git a/server/protocol.def b/server/protocol.def index a5a45eb6253..35b14f2c06e 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -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 */ diff --git a/server/request.h b/server/request.h index 5fd07fdf268..4f2979ba3a2 100644 --- a/server/request.h +++ b/server/request.h @@ -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 ); diff --git a/server/trace.c b/server/trace.c index efbd0e24750..9cd884eda58 100644 --- a/server/trace.c +++ b/server/trace.c @@ -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", -- 2.11.4.GIT