From b2d01bd2dbfed8b35cc324fad42eac562fcad3b4 Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Mon, 12 Jun 2000 15:53:31 +0000 Subject: totally rewrote the async signal, notification and oplock notification handling in Samba. This was needed due to several limitations and races in the previous code - as a side effect the new code is much cleaner :) in summary: - changed sys_select() to avoid a signal/select race condition. It is a rare race but once we have signals doing notification and oplocks it is important. - changed our main processing loop to take advantage of the new sys_select semantics - split the notify code into implementaion dependent and general parts. Added the following structure that defines an implementation: struct cnotify_fns { void * (*register_notify)(connection_struct *conn, char *path, uint32 flags); BOOL (*check_notify)(connection_struct *conn, uint16 vuid, char *path, uint32 flags, void *data, time_t t); void (*remove_notify)(void *data); }; then I wrote two implementations, one using hash/poll (like our old code) and the other using the new Linux kernel change notify. It should be easy to add other change notify implementations by creating a sructure of the above type. - fixed a bug in change notify where we were returning the wrong error code. - rewrote the core change notify code to be much simpler - moved to real-time signals for leases and change notify Amazingly, it all seems to work. I was very surprised! (This used to be commit 44766c39e0027c762bee8b33b12c621c109a3267) --- source3/smbd/notify.c | 401 +++++++++++++------------------------------ source3/smbd/notify_hash.c | 184 ++++++++++++++++++++ source3/smbd/notify_kernel.c | 170 ++++++++++++++++++ source3/smbd/oplock.c | 55 +++--- source3/smbd/oplock_irix.c | 8 +- source3/smbd/oplock_linux.c | 29 ++-- source3/smbd/process.c | 253 +++++++++++++-------------- source3/smbd/server.c | 12 +- 8 files changed, 647 insertions(+), 465 deletions(-) create mode 100644 source3/smbd/notify_hash.c create mode 100644 source3/smbd/notify_kernel.c (limited to 'source3/smbd') diff --git a/source3/smbd/notify.c b/source3/smbd/notify.c index 3af9da238f..40867a71ee 100644 --- a/source3/smbd/notify.c +++ b/source3/smbd/notify.c @@ -3,8 +3,8 @@ Unix SMB/Netbios implementation. Version 3.0 change notify handling - Copyright (C) Jeremy Allison 1994-1998 Copyright (C) Andrew Tridgell 2000 + Copyright (C) Jeremy Allison 1994-1998 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 @@ -25,221 +25,128 @@ extern int DEBUGLEVEL; -/**************************************************************************** - This is the structure to keep the information needed to - determine if a directory has changed. -*****************************************************************************/ - -typedef struct { - time_t modify_time; /* Info from the directory we're monitoring. */ - time_t status_time; /* Info from the directory we're monitoring. */ - time_t total_time; /* Total time of all directory entries - don't care if it wraps. */ - unsigned int num_entries; /* Zero or the number of files in the directory. */ -} change_hash_data; +static struct cnotify_fns *cnotify; /**************************************************************************** This is the structure to queue to implement NT change notify. It consists of smb_size bytes stored from the transact command (to keep the mid, tid etc around). - Plus the fid to examine and the time to check next. + Plus the fid to examine and notify private data *****************************************************************************/ -typedef struct { - ubi_slNode msg_next; - files_struct *fsp; - connection_struct *conn; - uint32 flags; - time_t next_check_time; - change_hash_data change_data; - char request_buf[smb_size]; -} change_notify_buf; +struct change_notify { + struct change_notify *next, *prev; + files_struct *fsp; + connection_struct *conn; + uint32 flags; + char request_buf[smb_size]; + void *change_data; +}; -static ubi_slList change_notify_queue = { NULL, (ubi_slNodePtr)&change_notify_queue, 0}; +static struct change_notify *change_notify_list; /**************************************************************************** Setup the common parts of the return packet and send it. *****************************************************************************/ - -static void change_notify_reply_packet(char *inbuf, int error_class, uint32 error_code) +static void change_notify_reply_packet(char *inbuf, uint32 error_code) { - char outbuf[smb_size+38]; - - memset(outbuf, '\0', sizeof(outbuf)); - construct_reply_common(inbuf, outbuf); + char outbuf[smb_size+38]; - /* - * If we're returning a 'too much in the directory changed' we need to - * set this is an NT error status flags. If we don't then the (probably - * untested) code in the NT redirector has a bug in that it doesn't re-issue - * the change notify.... Ah - I *love* it when I get so deeply into this I - * can even determine how MS failed to test stuff and why.... :-). JRA. - */ + memset(outbuf, '\0', sizeof(outbuf)); + construct_reply_common(inbuf, outbuf); - if(error_class == 0) /* NT Error. */ - SSVAL(outbuf,smb_flg2, SVAL(outbuf,smb_flg2) | FLAGS2_32_BIT_ERROR_CODES); - - ERROR(error_class,error_code); + /* + * If we're returning a 'too much in the directory changed' we need to + * set this is an NT error status flags. If we don't then the (probably + * untested) code in the NT redirector has a bug in that it doesn't re-issue + * the change notify.... Ah - I *love* it when I get so deeply into this I + * can even determine how MS failed to test stuff and why.... :-). JRA. + */ + + SSVAL(outbuf,smb_flg2, SVAL(outbuf,smb_flg2) | FLAGS2_32_BIT_ERROR_CODES); + ERROR(0,error_code); - /* - * Seems NT needs a transact command with an error code - * in it. This is a longer packet than a simple error. - */ - set_message(outbuf,18,0,False); + /* + * Seems NT needs a transact command with an error code + * in it. This is a longer packet than a simple error. + */ + set_message(outbuf,18,0,False); - send_smb(smbd_server_fd(),outbuf); + send_smb(smbd_server_fd(),outbuf); } /**************************************************************************** - Create the hash we will use to determine if the contents changed. +remove an entry from the list and free it, also closing any +directory handle if necessary +Notice the horrible stuff we have to do because this is a singly linked list. *****************************************************************************/ - -static BOOL create_directory_notify_hash( change_notify_buf *cnbp, change_hash_data *change_data) +static void change_notify_remove(struct change_notify *cnbp) { - SMB_STRUCT_STAT st; - files_struct *fsp = cnbp->fsp; - - memset((char *)change_data, '\0', sizeof(change_data)); - - /* - * Store the current timestamp on the directory we are monitoring. - */ - - if(dos_stat(fsp->fsp_name, &st) < 0) { - DEBUG(0,("create_directory_notify_hash: Unable to stat name = %s. \ -Error was %s\n", fsp->fsp_name, strerror(errno) )); - return False; - } - - change_data->modify_time = st.st_mtime; - change_data->status_time = st.st_ctime; - - /* - * If we are to watch for changes that are only stored - * in inodes of files, not in the directory inode, we must - * scan the directory and produce a unique identifier with - * which we can determine if anything changed. We use the - * modify and change times from all the files in the - * directory, added together (ignoring wrapping if it's - * larger than the max time_t value). - */ - - if(cnbp->flags & (FILE_NOTIFY_CHANGE_SIZE|FILE_NOTIFY_CHANGE_LAST_WRITE)) { - pstring full_name; - char *p; - char *fname; - size_t remaining_len; - size_t fullname_len; - void *dp = OpenDir(cnbp->conn, fsp->fsp_name, True); - - if(dp == NULL) { - DEBUG(0,("create_directory_notify_hash: Unable to open directory = %s. \ -Error was %s\n", fsp->fsp_name, strerror(errno) )); - return False; - } - - change_data->num_entries = 0; - - pstrcpy(full_name, fsp->fsp_name); - pstrcat(full_name, "/"); - - fullname_len = strlen(full_name); - remaining_len = sizeof(full_name) - fullname_len - 1; - p = &full_name[fullname_len]; - - while ((fname = ReadDirName(dp))) { - if(strequal(fname, ".") || strequal(fname, "..")) - continue; - - change_data->num_entries++; - safe_strcpy( p, fname, remaining_len); - - memset(&st, '\0', sizeof(st)); - - /* - * Do the stat - but ignore errors. - */ - - if(dos_stat(full_name, &st) < 0) { - DEBUG(5,("create_directory_notify_hash: Unable to stat content file = %s. \ -Error was %s\n", fsp->fsp_name, strerror(errno) )); - } - change_data->total_time += (st.st_mtime + st.st_ctime); - } - - CloseDir(dp); - } - - return True; + cnotify->remove_notify(cnbp->change_data); + DLIST_REMOVE(change_notify_list, cnbp); + ZERO_STRUCTP(cnbp); + free(cnbp); } + /**************************************************************************** Delete entries by fnum from the change notify pending queue. *****************************************************************************/ - void remove_pending_change_notify_requests_by_fid(files_struct *fsp) { - change_notify_buf *cnbp = (change_notify_buf *)ubi_slFirst( &change_notify_queue ); - change_notify_buf *prev = NULL; - - while(cnbp != NULL) { - if(cnbp->fsp->fnum == fsp->fnum) { - free((char *)ubi_slRemNext( &change_notify_queue, prev)); - cnbp = (change_notify_buf *)(prev ? ubi_slNext(prev) : ubi_slFirst(&change_notify_queue)); - continue; - } - - prev = cnbp; - cnbp = (change_notify_buf *)ubi_slNext(cnbp); - } + struct change_notify *cnbp, *next; + + for (cnbp=change_notify_list; cnbp; cnbp=next) { + next=cnbp->next; + if (cnbp->fsp->fnum == fsp->fnum) { + change_notify_remove(cnbp); + } + } } /**************************************************************************** Delete entries by mid from the change notify pending queue. Always send reply. *****************************************************************************/ - void remove_pending_change_notify_requests_by_mid(int mid) { - change_notify_buf *cnbp = (change_notify_buf *)ubi_slFirst( &change_notify_queue ); - change_notify_buf *prev = NULL; - - while(cnbp != NULL) { - if(SVAL(cnbp->request_buf,smb_mid) == mid) { - change_notify_reply_packet(cnbp->request_buf,0,0xC0000000 |NT_STATUS_CANCELLED); - free((char *)ubi_slRemNext( &change_notify_queue, prev)); - cnbp = (change_notify_buf *)(prev ? ubi_slNext(prev) : ubi_slFirst(&change_notify_queue)); - continue; - } - - prev = cnbp; - cnbp = (change_notify_buf *)ubi_slNext(cnbp); - } + struct change_notify *cnbp, *next; + + for (cnbp=change_notify_list; cnbp; cnbp=next) { + next=cnbp->next; + if(SVAL(cnbp->request_buf,smb_mid) == mid) { + change_notify_reply_packet(cnbp->request_buf,NT_STATUS_CANCELLED); + change_notify_remove(cnbp); + } + } } /**************************************************************************** Delete entries by filename and cnum from the change notify pending queue. Always send reply. *****************************************************************************/ - void remove_pending_change_notify_requests_by_filename(files_struct *fsp) { - change_notify_buf *cnbp = (change_notify_buf *)ubi_slFirst( &change_notify_queue ); - change_notify_buf *prev = NULL; - - while(cnbp != NULL) { - /* - * We know it refers to the same directory if the connection number and - * the filename are identical. - */ - if((cnbp->fsp->conn == fsp->conn) && strequal(cnbp->fsp->fsp_name,fsp->fsp_name)) { - change_notify_reply_packet(cnbp->request_buf,0,0xC0000000 |NT_STATUS_CANCELLED); - free((char *)ubi_slRemNext( &change_notify_queue, prev)); - cnbp = (change_notify_buf *)(prev ? ubi_slNext(prev) : ubi_slFirst(&change_notify_queue)); - continue; - } - - prev = cnbp; - cnbp = (change_notify_buf *)ubi_slNext(cnbp); - } + struct change_notify *cnbp, *next; + + for (cnbp=change_notify_list; cnbp; cnbp=next) { + next=cnbp->next; + /* + * We know it refers to the same directory if the connection number and + * the filename are identical. + */ + if((cnbp->fsp->conn == fsp->conn) && strequal(cnbp->fsp->fsp_name,fsp->fsp_name)) { + change_notify_reply_packet(cnbp->request_buf,NT_STATUS_CANCELLED); + change_notify_remove(cnbp); + } + } +} + +/**************************************************************************** + Return true if there are pending change notifies. +****************************************************************************/ +BOOL change_notifies_pending(void) +{ + return (change_notify_list != NULL); } /**************************************************************************** @@ -247,121 +154,36 @@ void remove_pending_change_notify_requests_by_filename(files_struct *fsp) Returns True if there are still outstanding change notify requests on the queue. *****************************************************************************/ - BOOL process_pending_change_notify_queue(time_t t) { - change_notify_buf *cnbp = (change_notify_buf *)ubi_slFirst( &change_notify_queue ); - change_notify_buf *prev = NULL; - - if(cnbp == NULL) - return False; - - if(cnbp->next_check_time >= t) - return True; - - /* - * It's time to check. Go through the queue and see if - * the timestamps changed. - */ - - while((cnbp != NULL) && (cnbp->next_check_time <= t)) { - change_hash_data change_data; - connection_struct *conn = cnbp->conn; - uint16 vuid = (lp_security() == SEC_SHARE) ? UID_FIELD_INVALID : - SVAL(cnbp->request_buf,smb_uid); - - ZERO_STRUCT(change_data); - - /* - * Ensure we don't have any old chain_fsp values - * sitting around.... - */ - chain_size = 0; - file_chain_reset(); - - if(!become_user(conn,vuid)) { - DEBUG(0,("process_pending_change_notify_queue: Unable to become user vuid=%d.\n", - vuid )); - /* - * Remove the entry and return an error to the client. - */ - change_notify_reply_packet(cnbp->request_buf,ERRSRV,ERRaccess); - free((char *)ubi_slRemNext( &change_notify_queue, prev)); - cnbp = (change_notify_buf *)(prev ? ubi_slNext(prev) : ubi_slFirst(&change_notify_queue)); - continue; - } - - if(!become_service(conn,True)) { - DEBUG(0,("process_pending_change_notify_queue: Unable to become service Error was %s.\n", strerror(errno) )); - /* - * Remove the entry and return an error to the client. - */ - change_notify_reply_packet(cnbp->request_buf,ERRSRV,ERRaccess); - free((char *)ubi_slRemNext( &change_notify_queue, prev)); - cnbp = (change_notify_buf *)(prev ? ubi_slNext(prev) : ubi_slFirst(&change_notify_queue)); - unbecome_user(); - continue; - } - - if(!create_directory_notify_hash( cnbp, &change_data)) { - DEBUG(0,("process_pending_change_notify_queue: Unable to create change data for \ -directory %s\n", cnbp->fsp->fsp_name )); - /* - * Remove the entry and return an error to the client. - */ - change_notify_reply_packet(cnbp->request_buf,ERRSRV,ERRaccess); - free((char *)ubi_slRemNext( &change_notify_queue, prev)); - cnbp = (change_notify_buf *)(prev ? ubi_slNext(prev) : ubi_slFirst(&change_notify_queue)); - unbecome_user(); - continue; - } - - if(memcmp( (char *)&cnbp->change_data, (char *)&change_data, sizeof(change_data))) { - /* - * Remove the entry and return a change notify to the client. - */ - DEBUG(5,("process_pending_change_notify_queue: directory name = %s changed.\n", - cnbp->fsp->fsp_name )); - change_notify_reply_packet(cnbp->request_buf,0,NT_STATUS_NOTIFY_ENUM_DIR); - free((char *)ubi_slRemNext( &change_notify_queue, prev)); - cnbp = (change_notify_buf *)(prev ? ubi_slNext(prev) : ubi_slFirst(&change_notify_queue)); - unbecome_user(); - continue; - } - - unbecome_user(); - - /* - * Move to the next in the list. - */ - prev = cnbp; - cnbp = (change_notify_buf *)ubi_slNext(cnbp); - } - - return (cnbp != NULL); -} + struct change_notify *cnbp, *next; + uint16 vuid; + + for (cnbp=change_notify_list; cnbp; cnbp=next) { + next=cnbp->next; + + vuid = (lp_security() == SEC_SHARE) ? UID_FIELD_INVALID : SVAL(cnbp->request_buf,smb_uid); + + if (cnotify->check_notify(cnbp->conn, vuid, cnbp->fsp->fsp_name, cnbp->flags, cnbp->change_data, t)) { + change_notify_reply_packet(cnbp->request_buf,STATUS_NOTIFY_ENUM_DIR); + change_notify_remove(cnbp); + } + } -/**************************************************************************** - Return true if there are pending change notifies. -****************************************************************************/ -BOOL change_notifies_pending(void) -{ - change_notify_buf *cnbp = (change_notify_buf *)ubi_slFirst( &change_notify_queue ); - return (cnbp != NULL); + return (change_notify_list != NULL); } /**************************************************************************** - * Now queue an entry on the notify change stack. We timestamp - * the entry we are adding so that we know when to scan next. + * Now queue an entry on the notify change list. * We only need to save smb_size bytes from this incoming packet * as we will always by returning a 'read the directory yourself' * error. ****************************************************************************/ BOOL change_notify_set(char *inbuf, files_struct *fsp, connection_struct *conn, uint32 flags) { - change_notify_buf *cnbp; + struct change_notify *cnbp; - if((cnbp = (change_notify_buf *)malloc(sizeof(change_notify_buf))) == NULL) { + if((cnbp = (struct change_notify *)malloc(sizeof(*cnbp))) == NULL) { DEBUG(0,("call_nt_transact_notify_change: malloc fail !\n" )); return -1; } @@ -371,23 +193,34 @@ BOOL change_notify_set(char *inbuf, files_struct *fsp, connection_struct *conn, memcpy(cnbp->request_buf, inbuf, smb_size); cnbp->fsp = fsp; cnbp->conn = conn; - cnbp->next_check_time = time(NULL) + lp_change_notify_timeout(); cnbp->flags = flags; + cnbp->change_data = cnotify->register_notify(conn, fsp->fsp_name, flags); - if (!create_directory_notify_hash(cnbp, &cnbp->change_data)) { - free((char *)cnbp); + if (!cnbp->change_data) { + free(cnbp); return False; } + + DLIST_ADD(change_notify_list, cnbp); + + return True; +} + + +/**************************************************************************** +initialise the change notify subsystem +****************************************************************************/ +BOOL init_change_notify(void) +{ + cnotify = hash_notify_init(); - /* - * Adding to the tail enables us to check only - * the head when scanning for change, as this entry - * is forced to have the first timeout expiration. - */ - - ubi_slAddTail(&change_notify_queue, cnbp); + if (!cnotify) { + DEBUG(0,("Failed to init change notify system\n")); + return False; + } return True; } + #undef OLD_NTDOMAIN diff --git a/source3/smbd/notify_hash.c b/source3/smbd/notify_hash.c new file mode 100644 index 0000000000..e01f660700 --- /dev/null +++ b/source3/smbd/notify_hash.c @@ -0,0 +1,184 @@ +#define OLD_NTDOMAIN 1 +/* + Unix SMB/Netbios implementation. + Version 3.0 + change notify handling - hash based implementation + Copyright (C) Jeremy Allison 1994-1998 + Copyright (C) Andrew Tridgell 2000 + + 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 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" + +extern int DEBUGLEVEL; + + +struct change_data { + time_t last_check_time; /* time we last checked this entry */ + time_t modify_time; /* Info from the directory we're monitoring. */ + time_t status_time; /* Info from the directory we're monitoring. */ + time_t total_time; /* Total time of all directory entries - don't care if it wraps. */ + unsigned int num_entries; /* Zero or the number of files in the directory. */ +}; + + +/**************************************************************************** + Create the hash we will use to determine if the contents changed. +*****************************************************************************/ +static BOOL notify_hash(connection_struct *conn, char *path, uint32 flags, + struct change_data *data) +{ + SMB_STRUCT_STAT st; + pstring full_name; + char *p; + char *fname; + size_t remaining_len; + size_t fullname_len; + void *dp; + + ZERO_STRUCTP(data); + + if(dos_stat(path, &st) == -1) return False; + + data->modify_time = st.st_mtime; + data->status_time = st.st_ctime; + + /* + * If we are to watch for changes that are only stored + * in inodes of files, not in the directory inode, we must + * scan the directory and produce a unique identifier with + * which we can determine if anything changed. We use the + * modify and change times from all the files in the + * directory, added together (ignoring wrapping if it's + * larger than the max time_t value). + */ + + if (!(flags & (FILE_NOTIFY_CHANGE_SIZE|FILE_NOTIFY_CHANGE_LAST_WRITE))) return True; + + dp = OpenDir(conn, path, True); + if (dp == NULL) return False; + + data->num_entries = 0; + + pstrcpy(full_name, path); + pstrcat(full_name, "/"); + + fullname_len = strlen(full_name); + remaining_len = sizeof(full_name) - fullname_len - 1; + p = &full_name[fullname_len]; + + while ((fname = ReadDirName(dp))) { + if(strequal(fname, ".") || strequal(fname, "..")) continue; + + data->num_entries++; + safe_strcpy(p, fname, remaining_len); + + ZERO_STRUCT(st); + + /* + * Do the stat - but ignore errors. + */ + dos_stat(full_name, &st); + data->total_time += (st.st_mtime + st.st_ctime); + } + + CloseDir(dp); + + return True; +} + + +/**************************************************************************** +register a change notify request +*****************************************************************************/ +static void *hash_register_notify(connection_struct *conn, char *path, uint32 flags) +{ + struct change_data data; + + if (!notify_hash(conn, path, flags, &data)) return NULL; + + data.last_check_time = time(NULL); + + return (void *)memdup(&data, sizeof(data)); +} + +/**************************************************************************** +check if a change notify should be issued +*****************************************************************************/ +static BOOL hash_check_notify(connection_struct *conn, uint16 vuid, char *path, uint32 flags, void *datap, time_t t) +{ + struct change_data *data = (struct change_data *)datap; + struct change_data data2; + + if (t < data->last_check_time + lp_change_notify_timeout()) return False; + + if (!become_user(conn,vuid)) return True; + if (!become_service(conn,True)) { + unbecome_user(); + return True; + } + + if (!notify_hash(conn, path, flags, &data2) || + data2.modify_time != data->modify_time || + data2.status_time != data->status_time || + data2.total_time != data->total_time || + data2.num_entries != data->num_entries) { + unbecome_user(); + return True; + } + + data->last_check_time = t; + unbecome_user(); + + return False; +} + +/**************************************************************************** +remove a change notify data structure +*****************************************************************************/ +static void hash_remove_notify(void *datap) +{ + free(datap); +} + + +/**************************************************************************** +setup hash based change notify +****************************************************************************/ +struct cnotify_fns *hash_notify_init(void) +{ + static struct cnotify_fns cnotify; + + cnotify.register_notify = hash_register_notify; + cnotify.check_notify = hash_check_notify; + cnotify.remove_notify = hash_remove_notify; + + return &cnotify; +} + + +/* + change_notify_reply_packet(cnbp->request_buf,ERRSRV,ERRaccess); + change_notify_reply_packet(cnbp->request_buf,0,NT_STATUS_NOTIFY_ENUM_DIR); + + chain_size = 0; + file_chain_reset(); + + uint16 vuid = (lp_security() == SEC_SHARE) ? UID_FIELD_INVALID : + SVAL(cnbp->request_buf,smb_uid); +*/ + +#undef OLD_NTDOMAIN diff --git a/source3/smbd/notify_kernel.c b/source3/smbd/notify_kernel.c new file mode 100644 index 0000000000..7732bc646f --- /dev/null +++ b/source3/smbd/notify_kernel.c @@ -0,0 +1,170 @@ +#define OLD_NTDOMAIN 1 +/* + Unix SMB/Netbios implementation. + Version 3.0 + change notify handling - linux kernel based implementation + Copyright (C) Andrew Tridgell 2000 + + 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 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" + +#if HAVE_KERNEL_CHANGE_NOTIFY + +extern int DEBUGLEVEL; +static int fd_pending; +static unsigned signals_received; +static unsigned signals_processed; + +#ifndef DN_ACCESS +#define DN_ACCESS 0x00000001 /* File accessed in directory */ +#define DN_MODIFY 0x00000002 /* File modified in directory */ +#define DN_CREATE 0x00000004 /* File created in directory */ +#define DN_DELETE 0x00000008 /* File removed from directory */ +#define DN_RENAME 0x00000010 /* File renamed in directory */ +#define DN_MULTISHOT 0x80000000 /* Don't remove notifier */ +#endif + + +#ifndef RT_SIGNAL_NOTIFY +#define RT_SIGNAL_NOTIFY 34 +#endif + +/**************************************************************************** + This is the structure to keep the information needed to + determine if a directory has changed. +*****************************************************************************/ +struct change_data { + int directory_handle; +}; + +/**************************************************************************** +the signal handler for change notify +*****************************************************************************/ +static void signal_handler(int signal, siginfo_t *info, void *unused) +{ + BlockSignals(True, signal); + fd_pending = info->si_fd; + signals_received++; + sys_select_signal(); +} + + + +/**************************************************************************** +check if a change notify should be issued +*****************************************************************************/ +static BOOL kernel_check_notify(connection_struct *conn, uint16 vuid, char *path, uint32 flags, void *datap, time_t t) +{ + struct change_data *data = (struct change_data *)datap; + + if (data->directory_handle != fd_pending) return False; + + close(fd_pending); + data->directory_handle = fd_pending = -1; + signals_processed++; + BlockSignals(False, RT_SIGNAL_NOTIFY); + return True; +} + +/**************************************************************************** +remove a change notify data structure +*****************************************************************************/ +static void kernel_remove_notify(void *datap) +{ + struct change_data *data = (struct change_data *)datap; + if (data->directory_handle != -1) { + if (data->directory_handle == fd_pending) { + data->directory_handle = fd_pending = -1; + signals_processed++; + BlockSignals(False, RT_SIGNAL_NOTIFY); + } + close(data->directory_handle); + } + free(data); +} + + +/**************************************************************************** +register a change notify request +*****************************************************************************/ +static void *kernel_register_notify(connection_struct *conn, char *path, uint32 flags) +{ + struct change_data data; + int fd; + unsigned long kernel_flags; + + fd = dos_open(fsp->fsp_name, O_RDONLY, 0); + + if (fd == -1) { + DEBUG(3,("Failed to open directory %s for change notify\n", fsp->fsp_name)); + return NULL; + } + + if (fcntl(fd, F_SETSIG, RT_SIGNAL_NOTIFY) == -1) { + DEBUG(3,("Failed to set signal handler for change notify\n")); + return NULL; + } + + kernel_flags = 0; + if (flags & FILE_NOTIFY_CHANGE_FILE_NAME) kernel_flags |= DN_RENAME; + if (flags & FILE_NOTIFY_CHANGE_DIR_NAME) kernel_flags |= DN_RENAME; + if (flags & FILE_NOTIFY_CHANGE_ATTRIBUTES) kernel_flags |= DN_MODIFY; + if (flags & FILE_NOTIFY_CHANGE_SIZE) kernel_flags |= DN_MODIFY; + if (flags & FILE_NOTIFY_CHANGE_LAST_WRITE) kernel_flags |= DN_MODIFY; + if (flags & FILE_NOTIFY_CHANGE_LAST_ACCESS) kernel_flags |= DN_ACCESS; + if (flags & FILE_NOTIFY_CHANGE_CREATION) kernel_flags |= DN_CREATE; + + if (fcntl(fd, F_NOTIFY, kernel_flags) == -1) { + DEBUG(3,("Failed to set async flag for change notify\n")); + return NULL; + } + + data.directory_handle = fd; + + return (void *)memdup(&data, sizeof(data)); +} + + +/**************************************************************************** +setup kernel based change notify +****************************************************************************/ +struct cnotify_fns *kernel_notify_init(void) +{ + static struct cnotify_fns cnotify; + struct sigaction act; + + act.sa_handler = NULL; + act.sa_sigaction = signal_handler; + act.sa_flags = SA_SIGINFO; + if (sigaction(RT_SIGNAL_NOTIFY, &act, NULL) != 0) { + DEBUG(0,("Failed to setup RT_SIGNAL_NOTIFY handler\n")); + return NULL; + } + + cnotify.register_notify = kernel_register_notify; + cnotify.check_notify = kernel_check_notify; + cnotify.remove_notify = kernel_remove_notify; + + return &cnotify; +} + + +#else + void notify_kernel_dummy(void) {} +#endif /* HAVE_KERNEL_CHANGE_NOTIFY */ + +#undef OLD_NTDOMAIN diff --git a/source3/smbd/oplock.c b/source3/smbd/oplock.c index 5e63b4d4ff..59c3c83f6f 100644 --- a/source3/smbd/oplock.c +++ b/source3/smbd/oplock.c @@ -71,44 +71,47 @@ BOOL receive_local_message(fd_set *fds, char *buffer, int buffer_len, int timeou smb_read_error = 0; if(timeout != 0) { - struct timeval to; - int selrtn; - int maxfd = oplock_sock; + struct timeval to; + int selrtn; + int maxfd = oplock_sock; - if (koplocks && koplocks->notification_fd != -1) { - FD_SET(koplocks->notification_fd, fds); - } + if (koplocks && koplocks->notification_fd != -1) { + FD_SET(koplocks->notification_fd, fds); + maxfd = MAX(maxfd, koplocks->notification_fd); + } - to.tv_sec = timeout / 1000; - to.tv_usec = (timeout % 1000) * 1000; + to.tv_sec = timeout / 1000; + to.tv_usec = (timeout % 1000) * 1000; - selrtn = sys_select(maxfd+1,fds,&to); + selrtn = sys_select(maxfd+1,fds,&to); - if (selrtn == -1 && errno == EINTR) { - /* could be a kernel oplock interrupt */ - if (koplocks && koplocks->msg_waiting(fds)) { - return koplocks->receive_message(fds, buffer, buffer_len); - } - } + if (selrtn == -1 && errno == EINTR) { + /* could be a kernel oplock interrupt */ + if (koplocks && koplocks->msg_waiting(fds)) { + return koplocks->receive_message(fds, buffer, buffer_len); + } + } - /* Check if error */ - if(selrtn == -1) { - /* something is wrong. Maybe the socket is dead? */ - smb_read_error = READ_ERROR; - return False; - } + /* Check if error */ + if(selrtn == -1) { + /* something is wrong. Maybe the socket is dead? */ + smb_read_error = READ_ERROR; + return False; + } - /* Did we timeout ? */ - if (selrtn == 0) { - smb_read_error = READ_TIMEOUT; - return False; - } + /* Did we timeout ? */ + if (selrtn == 0) { + smb_read_error = READ_TIMEOUT; + return False; + } } if (koplocks && koplocks->msg_waiting(fds)) { return koplocks->receive_message(fds, buffer, buffer_len); } + if (!FD_ISSET(oplock_sock, fds)) return False; + /* * From here down we deal with the smbd <--> smbd * oplock break protocol only. diff --git a/source3/smbd/oplock_irix.c b/source3/smbd/oplock_irix.c index 8d55a3d4a0..c4d528c835 100644 --- a/source3/smbd/oplock_irix.c +++ b/source3/smbd/oplock_irix.c @@ -218,16 +218,16 @@ static BOOL irix_kernel_oplock_parse(char *msg_start, int msg_len, SMB_INO_T *in { /* Ensure that the msg length is correct. */ if(msg_len != KERNEL_OPLOCK_BREAK_MSG_LEN) { - DEBUG(0,("process_local_message: incorrect length for KERNEL_OPLOCK_BREAK_CMD (was %d, \ -should be %d).\n", msg_len, KERNEL_OPLOCK_BREAK_MSG_LEN)); + DEBUG(0,("incorrect length for KERNEL_OPLOCK_BREAK_CMD (was %d, should be %d).\n", + msg_len, KERNEL_OPLOCK_BREAK_MSG_LEN)); return False; } memcpy((char *)inode, msg_start+KERNEL_OPLOCK_BREAK_INODE_OFFSET, sizeof(*inode)); memcpy((char *)dev, msg_start+KERNEL_OPLOCK_BREAK_DEV_OFFSET, sizeof(*dev)); - DEBUG(5,("process_local_message: kernel oplock break request for \ -file dev = %x, inode = %.0f\n", (unsigned int)*dev, (double)*inode)); + DEBUG(5,("kernel oplock break request for file dev = %x, inode = %.0f\n", + (unsigned int)*dev, (double)*inode)); return True; } diff --git a/source3/smbd/oplock_linux.c b/source3/smbd/oplock_linux.c index 46290683d2..de2a4300a7 100644 --- a/source3/smbd/oplock_linux.c +++ b/source3/smbd/oplock_linux.c @@ -29,7 +29,7 @@ extern int DEBUGLEVEL; static unsigned signals_received; static unsigned signals_processed; -static int fd_pending; /* the fd of the current pending SIGIO */ +static int fd_pending; /* the fd of the current pending signal */ #ifndef F_SETLEASE #define F_SETLEASE 1024 @@ -43,14 +43,19 @@ static int fd_pending; /* the fd of the current pending SIGIO */ #define CAP_LEASE 28 #endif +#ifndef RT_SIGNAL_LEASE +#define RT_SIGNAL_LEASE 33 +#endif + /**************************************************************************** -handle a SIGIO, incrementing the signals_received and blocking SIGIO +handle a LEASE signal, incrementing the signals_received and blocking the signal ****************************************************************************/ -static void sigio_handler(int signal, siginfo_t *info, void *unused) +static void signal_handler(int signal, siginfo_t *info, void *unused) { + BlockSignals(True, signal); fd_pending = info->si_fd; signals_received++; - BlockSignals(True, SIGIO); + sys_select_signal(); } /**************************************************************************** @@ -150,7 +155,7 @@ dev = %x, inode = %.0f\n", (unsigned int)dev, (double)inode )); /* now we can receive more signals */ fd_pending = -1; signals_processed++; - BlockSignals(False, SIGIO); + BlockSignals(False, RT_SIGNAL_LEASE); return True; } @@ -213,16 +218,16 @@ static BOOL linux_kernel_oplock_parse(char *msg_start, int msg_len, SMB_INO_T *i { /* Ensure that the msg length is correct. */ if (msg_len != KERNEL_OPLOCK_BREAK_MSG_LEN) { - DEBUG(0,("process_local_message: incorrect length for KERNEL_OPLOCK_BREAK_CMD (was %d, \ -should be %d).\n", msg_len, KERNEL_OPLOCK_BREAK_MSG_LEN)); + DEBUG(0,("incorrect length for KERNEL_OPLOCK_BREAK_CMD (was %d, should be %d).\n", + msg_len, KERNEL_OPLOCK_BREAK_MSG_LEN)); return False; } memcpy((char *)inode, msg_start+KERNEL_OPLOCK_BREAK_INODE_OFFSET, sizeof(*inode)); memcpy((char *)dev, msg_start+KERNEL_OPLOCK_BREAK_DEV_OFFSET, sizeof(*dev)); - DEBUG(5,("process_local_message: kernel oplock break request for \ -file dev = %x, inode = %.0f\n", (unsigned int)*dev, (double)*inode)); + DEBUG(5,("kernel oplock break request for file dev = %x, inode = %.0f\n", + (unsigned int)*dev, (double)*inode)); return True; } @@ -264,10 +269,10 @@ struct kernel_oplocks *linux_init_kernel_oplocks(void) } act.sa_handler = NULL; - act.sa_sigaction = sigio_handler; + act.sa_sigaction = signal_handler; act.sa_flags = SA_SIGINFO; - if (sigaction(SIGIO, &act, NULL) != 0) { - DEBUG(0,("Failed to setup SIGIO handler\n")); + if (sigaction(RT_SIGNAL_LEASE, &act, NULL) != 0) { + DEBUG(0,("Failed to setup RT_SIGNAL_LEASE handler\n")); return NULL; } diff --git a/source3/smbd/process.c b/source3/smbd/process.c index 30d03747d8..b84e55343e 100644 --- a/source3/smbd/process.c +++ b/source3/smbd/process.c @@ -107,7 +107,30 @@ static BOOL push_message(ubi_slList *list_head, char *buf, int msg_len) BOOL push_oplock_pending_smb_message(char *buf, int msg_len) { - return push_message(&smb_oplock_queue, buf, msg_len); + return push_message(&smb_oplock_queue, buf, msg_len); +} + +/**************************************************************************** +do all async processing in here. This includes UDB oplock messages, kernel +oplock messages, change notify events etc. +****************************************************************************/ +static void async_processing(fd_set *fds, char *buffer, int buffer_len) +{ + /* check for oplock messages (both UDP and kernel) */ + if (receive_local_message(fds, buffer, buffer_len, 0)) { + process_local_message(buffer, buffer_len); + } + + /* check for async change notify events */ + process_pending_change_notify_queue(0); + + /* check for sighup processing */ + if (reload_after_sighup) { + unbecome_user(); + DEBUG(1,("Reloading services after SIGHUP\n")); + reload_services(False); + reload_after_sighup = False; + } } /**************************************************************************** @@ -115,7 +138,7 @@ BOOL push_oplock_pending_smb_message(char *buf, int msg_len) If a local udp message has been pushed onto the queue (this can only happen during oplock break - processing) return this first. + processing) call async_processing() If a pending smb message has been pushed onto the queue (this can only happen during oplock break @@ -131,8 +154,7 @@ BOOL push_oplock_pending_smb_message(char *buf, int msg_len) The timeout is in milli seconds ****************************************************************************/ -static BOOL receive_message_or_smb(char *buffer, int buffer_len, - int timeout, BOOL *got_smb) +static BOOL receive_message_or_smb(char *buffer, int buffer_len, int timeout) { fd_set fds; int selrtn; @@ -141,30 +163,28 @@ static BOOL receive_message_or_smb(char *buffer, int buffer_len, smb_read_error = 0; - *got_smb = False; - /* * Check to see if we already have a message on the smb queue. * If so - copy and return it. */ - - if(ubi_slCount(&smb_oplock_queue) != 0) { + if(ubi_slCount(&smb_oplock_queue) != 0) { pending_message_list *msg = (pending_message_list *)ubi_slRemHead(&smb_oplock_queue); memcpy(buffer, msg->msg_buf, MIN(buffer_len, msg->msg_len)); /* Free the message we just copied. */ free((char *)msg->msg_buf); free((char *)msg); - *got_smb = True; DEBUG(5,("receive_message_or_smb: returning queued smb message.\n")); return True; } + /* * Setup the select read fd set. */ + again: FD_ZERO(&fds); FD_SET(smbd_server_fd(),&fds); maxfd = setup_oplock_select_set(&fds); @@ -175,16 +195,16 @@ static BOOL receive_message_or_smb(char *buffer, int buffer_len, selrtn = sys_select(MAX(maxfd,smbd_server_fd())+1,&fds,timeout>0?&to:NULL); /* if we get EINTR then maybe we have received an oplock - signal - treat this as select returning 1. This is ugly, but - is the best we can do until the oplock code knows more about - signals */ + signal - treat this as select returning 1. This is ugly, but + is the best we can do until the oplock code knows more about + signals */ if (selrtn == -1 && errno == EINTR) { - FD_ZERO(&fds); - selrtn = 1; + async_processing(&fds, buffer, buffer_len); + goto again; } /* Check if error */ - if(selrtn == -1 && errno != EINTR) { + if (selrtn == -1) { /* something is wrong. Maybe the socket is dead? */ smb_read_error = READ_ERROR; return False; @@ -195,13 +215,13 @@ static BOOL receive_message_or_smb(char *buffer, int buffer_len, smb_read_error = READ_TIMEOUT; return False; } - - if (FD_ISSET(smbd_server_fd(),&fds)) { - *got_smb = True; - return receive_smb(smbd_server_fd(), buffer, 0); - } else { - return receive_local_message(&fds, buffer, buffer_len, 0); + + if (!FD_ISSET(smbd_server_fd(),&fds) || selrtn > 1) { + async_processing(&fds, buffer, buffer_len); + if (!FD_ISSET(smbd_server_fd(),&fds)) goto again; } + + return receive_smb(smbd_server_fd(), buffer, 0); } /**************************************************************************** @@ -210,30 +230,16 @@ Get the next SMB packet, doing the local message processing automatically. BOOL receive_next_smb(char *inbuf, int bufsize, int timeout) { - BOOL got_smb = False; - BOOL ret; - - do - { - ret = receive_message_or_smb(inbuf,bufsize,timeout,&got_smb); + BOOL got_keepalive; + BOOL ret; - if(ret && !got_smb) - { - /* Deal with oplock break requests from other smbd's. */ - process_local_message(inbuf, bufsize); - continue; - } - - if(ret && (CVAL(inbuf,0) == 0x85)) - { - /* Keepalive packet. */ - got_smb = False; - } - - } - while(ret && !got_smb); + do { + ret = receive_message_or_smb(inbuf,bufsize,timeout); + + got_keepalive = (ret && (CVAL(inbuf,0) == 0x85)); + } while (ret && got_keepalive); - return ret; + return ret; } /**************************************************************************** @@ -270,13 +276,12 @@ void respond_to_all_remaining_local_messages(void) * Keep doing receive_local_message with a 1 ms timeout until * we have no more messages. */ - while(receive_local_message(&fds, buffer, sizeof(buffer), 1)) { - /* Deal with oplock break requests from other smbd's. */ - process_local_message(buffer, sizeof(buffer)); + /* Deal with oplock break requests from other smbd's. */ + process_local_message(buffer, sizeof(buffer)); - FD_ZERO(&fds); - (void)setup_oplock_select_set(&fds); + FD_ZERO(&fds); + (void)setup_oplock_select_set(&fds); } return; @@ -1008,98 +1013,80 @@ machine %s in domain %s.\n", global_myname, global_myworkgroup )); void smbd_process(void) { - extern int smb_echo_count; - time_t last_timeout_processing_time = time(NULL); - unsigned int num_smbs = 0; + extern int smb_echo_count; + time_t last_timeout_processing_time = time(NULL); + unsigned int num_smbs = 0; - InBuffer = (char *)malloc(BUFFER_SIZE + SAFETY_MARGIN); - OutBuffer = (char *)malloc(BUFFER_SIZE + SAFETY_MARGIN); - if ((InBuffer == NULL) || (OutBuffer == NULL)) - return; + InBuffer = (char *)malloc(BUFFER_SIZE + SAFETY_MARGIN); + OutBuffer = (char *)malloc(BUFFER_SIZE + SAFETY_MARGIN); + if ((InBuffer == NULL) || (OutBuffer == NULL)) + return; - InBuffer += SMB_ALIGNMENT; - OutBuffer += SMB_ALIGNMENT; + InBuffer += SMB_ALIGNMENT; + OutBuffer += SMB_ALIGNMENT; - max_recv = MIN(lp_maxxmit(),BUFFER_SIZE); + max_recv = MIN(lp_maxxmit(),BUFFER_SIZE); - /* re-initialise the timezone */ - TimeInit(); + /* re-initialise the timezone */ + TimeInit(); - while (True) - { - int deadtime = lp_deadtime()*60; - BOOL got_smb = False; - int select_timeout = setup_select_timeout(); - - if (deadtime <= 0) - deadtime = DEFAULT_SMBD_TIMEOUT; - - errno = 0; - - /* free up temporary memory */ - lp_talloc_free(); - - /* - * If reload_after_sighup == True then we got a SIGHUP - * and are being asked to reload. Fix from - */ - if (reload_after_sighup) { - /* become root */ - unbecome_user(); - DEBUG(1,("Reloading services after SIGHUP\n")); - reload_services(False); - reload_after_sighup = False; - } - - while(!receive_message_or_smb(InBuffer,BUFFER_SIZE,select_timeout,&got_smb)) - { - if(!timeout_processing( deadtime, &select_timeout, &last_timeout_processing_time)) - return; - num_smbs = 0; /* Reset smb counter. */ - } - - if(got_smb) { - /* - * Ensure we do timeout processing if the SMB we just got was - * only an echo request. This allows us to set the select - * timeout in 'receive_message_or_smb()' to any value we like - * without worrying that the client will send echo requests - * faster than the select timeout, thus starving out the - * essential processing (change notify, blocking locks) that - * the timeout code does. JRA. - */ - int num_echos = smb_echo_count; - - process_smb(InBuffer, OutBuffer); - - if(smb_echo_count != num_echos) { - if(!timeout_processing( deadtime, &select_timeout, &last_timeout_processing_time)) - return; - num_smbs = 0; /* Reset smb counter. */ - } - - num_smbs++; + while (True) { + int deadtime = lp_deadtime()*60; + int select_timeout = setup_select_timeout(); + int num_echos; - /* - * If we are getting smb requests in a constant stream - * with no echos, make sure we attempt timeout processing - * every select_timeout milliseconds - but only check for this - * every 200 smb requests. - */ + if (deadtime <= 0) + deadtime = DEFAULT_SMBD_TIMEOUT; - if((num_smbs % 200) == 0) { - time_t new_check_time = time(NULL); - if(last_timeout_processing_time - new_check_time >= (select_timeout/1000)) { - if(!timeout_processing( deadtime, &select_timeout, &last_timeout_processing_time)) - return; - num_smbs = 0; /* Reset smb counter. */ - last_timeout_processing_time = new_check_time; /* Reset time. */ - } - } - } - else - process_local_message(InBuffer, BUFFER_SIZE); - } + errno = 0; + + /* free up temporary memory */ + lp_talloc_free(); + + while (!receive_message_or_smb(InBuffer,BUFFER_SIZE,select_timeout)) { + if(!timeout_processing( deadtime, &select_timeout, &last_timeout_processing_time)) + return; + num_smbs = 0; /* Reset smb counter. */ + } + + /* + * Ensure we do timeout processing if the SMB we just got was + * only an echo request. This allows us to set the select + * timeout in 'receive_message_or_smb()' to any value we like + * without worrying that the client will send echo requests + * faster than the select timeout, thus starving out the + * essential processing (change notify, blocking locks) that + * the timeout code does. JRA. + */ + num_echos = smb_echo_count; + + process_smb(InBuffer, OutBuffer); + + if (smb_echo_count != num_echos) { + if(!timeout_processing( deadtime, &select_timeout, &last_timeout_processing_time)) + return; + num_smbs = 0; /* Reset smb counter. */ + } + + num_smbs++; + + /* + * If we are getting smb requests in a constant stream + * with no echos, make sure we attempt timeout processing + * every select_timeout milliseconds - but only check for this + * every 200 smb requests. + */ + + if ((num_smbs % 200) == 0) { + time_t new_check_time = time(NULL); + if(last_timeout_processing_time - new_check_time >= (select_timeout/1000)) { + if(!timeout_processing( deadtime, &select_timeout, &last_timeout_processing_time)) + return; + num_smbs = 0; /* Reset smb counter. */ + last_timeout_processing_time = new_check_time; /* Reset time. */ + } + } + } } #undef OLD_NTDOMAIN diff --git a/source3/smbd/server.c b/source3/smbd/server.c index a5da156250..b28ba6d4ef 100644 --- a/source3/smbd/server.c +++ b/source3/smbd/server.c @@ -350,12 +350,7 @@ static void sig_hup(int sig) BlockSignals(True,SIGHUP); DEBUG(0,("Got SIGHUP\n")); - /* - * Fix from here. - * We used to reload in the signal handler - this - * is a *BIG* no-no. - */ - + sys_select_signal(); reload_after_sighup = True; BlockSignals(False,SIGHUP); } @@ -758,6 +753,11 @@ static void usage(char *pname) exit(1); } + /* Setup change notify */ + if (!init_change_notify()) { + exit(1); + } + smbd_process(); exit_server("normal exit"); -- cgit