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 +++++++++++++++----------------------------------- 1 file changed, 117 insertions(+), 284 deletions(-) (limited to 'source3/smbd/notify.c') 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 -- cgit