diff options
Diffstat (limited to 'source3/smbd/oplock.c')
-rw-r--r-- | source3/smbd/oplock.c | 639 |
1 files changed, 639 insertions, 0 deletions
diff --git a/source3/smbd/oplock.c b/source3/smbd/oplock.c new file mode 100644 index 0000000000..1b49e2cfaf --- /dev/null +++ b/source3/smbd/oplock.c @@ -0,0 +1,639 @@ +/* + Unix SMB/Netbios implementation. + Version 1.9. + oplock processing + Copyright (C) Andrew Tridgell 1992-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 + 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; + +extern int oplock_sock; +extern uint16 oplock_port; +extern int32 global_oplocks_open; +extern int32 global_oplocks_open; +extern int global_oplock_break; +extern int smb_read_error; + + +/**************************************************************************** + open the oplock IPC socket communication +****************************************************************************/ +BOOL open_oplock_ipc(void) +{ + struct sockaddr_in sock_name; + int len = sizeof(sock_name); + + DEBUG(3,("open_oplock_ipc: opening loopback UDP socket.\n")); + + /* Open a lookback UDP socket on a random port. */ + oplock_sock = open_socket_in(SOCK_DGRAM, 0, 0, htonl(INADDR_LOOPBACK)); + if (oplock_sock == -1) + { + DEBUG(0,("open_oplock_ipc: Failed to get local UDP socket for \ +address %x. Error was %s\n", htonl(INADDR_LOOPBACK), strerror(errno))); + oplock_port = 0; + return(False); + } + + /* Find out the transient UDP port we have been allocated. */ + if(getsockname(oplock_sock, (struct sockaddr *)&sock_name, &len)<0) + { + DEBUG(0,("open_oplock_ipc: Failed to get local UDP port. Error was %s\n", + strerror(errno))); + close(oplock_sock); + oplock_sock = -1; + oplock_port = 0; + return False; + } + oplock_port = ntohs(sock_name.sin_port); + + DEBUG(3,("open_oplock ipc: pid = %d, oplock_port = %u\n", + (int)getpid(), oplock_port)); + + return True; +} + +/**************************************************************************** + process an oplock break message. +****************************************************************************/ +BOOL process_local_message(int sock, char *buffer, int buf_size) +{ + int32 msg_len; + uint16 from_port; + char *msg_start; + + msg_len = IVAL(buffer,UDP_CMD_LEN_OFFSET); + from_port = SVAL(buffer,UDP_CMD_PORT_OFFSET); + + msg_start = &buffer[UDP_CMD_HEADER_LEN]; + + DEBUG(5,("process_local_message: Got a message of length %d from port (%d)\n", + msg_len, from_port)); + + /* Switch on message command - currently OPLOCK_BREAK_CMD is the + only valid request. */ + + switch(SVAL(msg_start,UDP_MESSAGE_CMD_OFFSET)) + { + case OPLOCK_BREAK_CMD: + /* Ensure that the msg length is correct. */ + if(msg_len != OPLOCK_BREAK_MSG_LEN) + { + DEBUG(0,("process_local_message: incorrect length for OPLOCK_BREAK_CMD (was %d, \ +should be %d).\n", msg_len, OPLOCK_BREAK_MSG_LEN)); + return False; + } + { + uint32 remotepid = IVAL(msg_start,OPLOCK_BREAK_PID_OFFSET); + uint32 dev = IVAL(msg_start,OPLOCK_BREAK_DEV_OFFSET); + uint32 inode = IVAL(msg_start, OPLOCK_BREAK_INODE_OFFSET); + struct timeval tval; + struct sockaddr_in toaddr; + + tval.tv_sec = IVAL(msg_start, OPLOCK_BREAK_SEC_OFFSET); + tval.tv_usec = IVAL(msg_start, OPLOCK_BREAK_USEC_OFFSET); + + DEBUG(5,("process_local_message: oplock break request from \ +pid %d, port %d, dev = %x, inode = %x\n", remotepid, from_port, dev, inode)); + + /* + * If we have no record of any currently open oplocks, + * it's not an error, as a close command may have + * just been issued on the file that was oplocked. + * Just return success in this case. + */ + + if(global_oplocks_open != 0) + { + if(oplock_break(dev, inode, &tval) == False) + { + DEBUG(0,("process_local_message: oplock break failed - \ +not returning udp message.\n")); + return False; + } + } + else + { + DEBUG(3,("process_local_message: oplock break requested with no outstanding \ +oplocks. Returning success.\n")); + } + + /* Send the message back after OR'ing in the 'REPLY' bit. */ + SSVAL(msg_start,UDP_MESSAGE_CMD_OFFSET,OPLOCK_BREAK_CMD | CMD_REPLY); + + bzero((char *)&toaddr,sizeof(toaddr)); + toaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + toaddr.sin_port = htons(from_port); + toaddr.sin_family = AF_INET; + + if(sendto( sock, msg_start, OPLOCK_BREAK_MSG_LEN, 0, + (struct sockaddr *)&toaddr, sizeof(toaddr)) < 0) + { + DEBUG(0,("process_local_message: sendto process %d failed. Errno was %s\n", + remotepid, strerror(errno))); + return False; + } + + DEBUG(5,("process_local_message: oplock break reply sent to \ +pid %d, port %d, for file dev = %x, inode = %x\n", remotepid, + from_port, dev, inode)); + + } + break; + /* + * Keep this as a debug case - eventually we can remove it. + */ + case 0x8001: + DEBUG(0,("process_local_message: Received unsolicited break \ +reply - dumping info.\n")); + + if(msg_len != OPLOCK_BREAK_MSG_LEN) + { + DEBUG(0,("process_local_message: ubr: incorrect length for reply \ +(was %d, should be %d).\n", msg_len, OPLOCK_BREAK_MSG_LEN)); + return False; + } + + { + uint32 remotepid = IVAL(msg_start,OPLOCK_BREAK_PID_OFFSET); + uint32 dev = IVAL(msg_start,OPLOCK_BREAK_DEV_OFFSET); + uint32 inode = IVAL(msg_start, OPLOCK_BREAK_INODE_OFFSET); + + DEBUG(0,("process_local_message: unsolicited oplock break reply from \ +pid %d, port %d, dev = %x, inode = %x\n", remotepid, from_port, dev, inode)); + + } + return False; + + default: + DEBUG(0,("process_local_message: unknown UDP message command code (%x) - ignoring.\n", + (unsigned int)SVAL(msg_start,0))); + return False; + } + return True; +} + +/**************************************************************************** + Process an oplock break directly. +****************************************************************************/ +BOOL oplock_break(uint32 dev, uint32 inode, struct timeval *tval) +{ + extern struct current_user current_user; + extern int Client; + char *inbuf = NULL; + char *outbuf = NULL; + files_struct *fsp = NULL; + time_t start_time; + BOOL shutdown_server = False; + connection_struct *saved_conn; + int saved_vuid; + pstring saved_dir; + + if( DEBUGLVL( 3 ) ) + { + dbgtext( "oplock_break: called for dev = %x, inode = %x.\n", dev, inode ); + dbgtext( "Current global_oplocks_open = %d\n", global_oplocks_open ); + } + + /* We need to search the file open table for the + entry containing this dev and inode, and ensure + we have an oplock on it. */ + fsp = file_find_dit(dev, inode, tval); + + if(fsp == NULL) + { + /* The file could have been closed in the meantime - return success. */ + if( DEBUGLVL( 0 ) ) + { + dbgtext( "oplock_break: cannot find open file with " ); + dbgtext( "dev = %x, inode = %x ", dev, inode); + dbgtext( "allowing break to succeed.\n" ); + } + return True; + } + + /* Ensure we have an oplock on the file */ + + /* There is a potential race condition in that an oplock could + have been broken due to another udp request, and yet there are + still oplock break messages being sent in the udp message + queue for this file. So return true if we don't have an oplock, + as we may have just freed it. + */ + + if(!fsp->granted_oplock) + { + if( DEBUGLVL( 0 ) ) + { + dbgtext( "oplock_break: file %s ", fsp->fsp_name ); + dbgtext( "(dev = %x, inode = %x) has no oplock.\n", dev, inode ); + dbgtext( "Allowing break to succeed regardless.\n" ); + } + return True; + } + + /* mark the oplock break as sent - we don't want to send twice! */ + if (fsp->sent_oplock_break) + { + if( DEBUGLVL( 0 ) ) + { + dbgtext( "oplock_break: ERROR: oplock_break already sent for " ); + dbgtext( "file %s ", fsp->fsp_name); + dbgtext( "(dev = %x, inode = %x)\n", dev, inode ); + } + + /* We have to fail the open here as we cannot send another oplock break on + this file whilst we are awaiting a response from the client - neither + can we allow another open to succeed while we are waiting for the + client. + */ + return False; + } + + /* Now comes the horrid part. We must send an oplock break to the client, + and then process incoming messages until we get a close or oplock release. + At this point we know we need a new inbuf/outbuf buffer pair. + We cannot use these staticaly as we may recurse into here due to + messages crossing on the wire. + */ + + if((inbuf = (char *)malloc(BUFFER_SIZE + SAFETY_MARGIN))==NULL) + { + DEBUG(0,("oplock_break: malloc fail for input buffer.\n")); + return False; + } + + if((outbuf = (char *)malloc(BUFFER_SIZE + SAFETY_MARGIN))==NULL) + { + DEBUG(0,("oplock_break: malloc fail for output buffer.\n")); + free(inbuf); + inbuf = NULL; + return False; + } + + /* Prepare the SMBlockingX message. */ + bzero(outbuf,smb_size); + set_message(outbuf,8,0,True); + + SCVAL(outbuf,smb_com,SMBlockingX); + SSVAL(outbuf,smb_tid,fsp->conn->cnum); + SSVAL(outbuf,smb_pid,0xFFFF); + SSVAL(outbuf,smb_uid,0); + SSVAL(outbuf,smb_mid,0xFFFF); + SCVAL(outbuf,smb_vwv0,0xFF); + SSVAL(outbuf,smb_vwv2,fsp->fnum); + SCVAL(outbuf,smb_vwv3,LOCKING_ANDX_OPLOCK_RELEASE); + /* Change this when we have level II oplocks. */ + SCVAL(outbuf,smb_vwv3+1,OPLOCKLEVEL_NONE); + + send_smb(Client, outbuf); + + /* Remember we just sent an oplock break on this file. */ + fsp->sent_oplock_break = True; + + /* We need this in case a readraw crosses on the wire. */ + global_oplock_break = True; + + /* Process incoming messages. */ + + /* JRA - If we don't get a break from the client in OPLOCK_BREAK_TIMEOUT + seconds we should just die.... */ + + start_time = time(NULL); + + /* + * Save the information we need to re-become the + * user, then unbecome the user whilst we're doing this. + */ + saved_conn = fsp->conn; + saved_vuid = current_user.vuid; + GetWd(saved_dir); + unbecome_user(); + + while(OPEN_FSP(fsp) && fsp->granted_oplock) + { + if(receive_smb(Client,inbuf,OPLOCK_BREAK_TIMEOUT * 1000) == False) + { + /* + * Die if we got an error. + */ + + if (smb_read_error == READ_EOF) + DEBUG( 0, ( "oplock_break: end of file from client\n" ) ); + + if (smb_read_error == READ_ERROR) + DEBUG( 0, ("oplock_break: receive_smb error (%s)\n", strerror(errno)) ); + + if (smb_read_error == READ_TIMEOUT) + DEBUG( 0, ( "oplock_break: receive_smb timed out after %d seconds.\n", + OPLOCK_BREAK_TIMEOUT ) ); + + DEBUGADD( 0, ( "oplock_break failed for file %s ", fsp->fsp_name ) ); + DEBUGADD( 0, ( "(dev = %x, inode = %x).\n", dev, inode)); + shutdown_server = True; + break; + } + + /* + * There are certain SMB requests that we shouldn't allow + * to recurse. opens, renames and deletes are the obvious + * ones. This is handled in the switch_message() function. + * If global_oplock_break is set they will push the packet onto + * the pending smb queue and return -1 (no reply). + * JRA. + */ + + process_smb(inbuf, outbuf); + + /* + * Die if we go over the time limit. + */ + + if((time(NULL) - start_time) > OPLOCK_BREAK_TIMEOUT) + { + if( DEBUGLVL( 0 ) ) + { + dbgtext( "oplock_break: no break received from client " ); + dbgtext( "within %d seconds.\n", OPLOCK_BREAK_TIMEOUT ); + dbgtext( "oplock_break failed for file %s ", fsp->fsp_name ); + dbgtext( "(dev = %x, inode = %x).\n", dev, inode ); + } + shutdown_server = True; + break; + } + } + + /* + * Go back to being the user who requested the oplock + * break. + */ + if(!become_user(saved_conn, saved_vuid)) + { + DEBUG( 0, ( "oplock_break: unable to re-become user!" ) ); + DEBUGADD( 0, ( "Shutting down server\n" ) ); + close_sockets(); + close(oplock_sock); + exit_server("unable to re-become user"); + } + /* Including the directory. */ + ChDir(saved_dir); + + /* Free the buffers we've been using to recurse. */ + free(inbuf); + free(outbuf); + + /* We need this in case a readraw crossed on the wire. */ + if(global_oplock_break) + global_oplock_break = False; + + /* + * If the client did not respond we must die. + */ + + if(shutdown_server) + { + DEBUG( 0, ( "oplock_break: client failure in break - " ) ); + DEBUGADD( 0, ( "shutting down this smbd.\n" ) ); + close_sockets(); + close(oplock_sock); + exit_server("oplock break failure"); + } + + if(OPEN_FSP(fsp)) + { + /* The lockingX reply will have removed the oplock flag + from the sharemode. */ + /* Paranoia.... */ + fsp->granted_oplock = False; + fsp->sent_oplock_break = False; + global_oplocks_open--; + } + + /* Santity check - remove this later. JRA */ + if(global_oplocks_open < 0) + { + DEBUG(0,("oplock_break: global_oplocks_open < 0 (%d). PANIC ERROR\n", + global_oplocks_open)); + exit_server("oplock_break: global_oplocks_open < 0"); + } + + if( DEBUGLVL( 3 ) ) + { + dbgtext( "oplock_break: returning success for " ); + dbgtext( "dev = %x, inode = %x.\n", dev, inode ); + dbgtext( "Current global_oplocks_open = %d\n", global_oplocks_open ); + } + + return True; +} + +/**************************************************************************** +Send an oplock break message to another smbd process. If the oplock is held +by the local smbd then call the oplock break function directly. +****************************************************************************/ + +BOOL request_oplock_break(share_mode_entry *share_entry, + uint32 dev, uint32 inode) +{ + char op_break_msg[OPLOCK_BREAK_MSG_LEN]; + struct sockaddr_in addr_out; + int pid = getpid(); + time_t start_time; + int time_left; + + if(pid == share_entry->pid) + { + /* We are breaking our own oplock, make sure it's us. */ + if(share_entry->op_port != oplock_port) + { + DEBUG(0,("request_oplock_break: corrupt share mode entry - pid = %d, port = %d \ +should be %d\n", pid, share_entry->op_port, oplock_port)); + return False; + } + + DEBUG(5,("request_oplock_break: breaking our own oplock\n")); + + /* Call oplock break direct. */ + return oplock_break(dev, inode, &share_entry->time); + } + + /* We need to send a OPLOCK_BREAK_CMD message to the + port in the share mode entry. */ + + SSVAL(op_break_msg,UDP_MESSAGE_CMD_OFFSET,OPLOCK_BREAK_CMD); + SIVAL(op_break_msg,OPLOCK_BREAK_PID_OFFSET,pid); + SIVAL(op_break_msg,OPLOCK_BREAK_DEV_OFFSET,dev); + SIVAL(op_break_msg,OPLOCK_BREAK_INODE_OFFSET,inode); + SIVAL(op_break_msg,OPLOCK_BREAK_SEC_OFFSET,(uint32)share_entry->time.tv_sec); + SIVAL(op_break_msg,OPLOCK_BREAK_USEC_OFFSET,(uint32)share_entry->time.tv_usec); + + /* set the address and port */ + bzero((char *)&addr_out,sizeof(addr_out)); + addr_out.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr_out.sin_port = htons( share_entry->op_port ); + addr_out.sin_family = AF_INET; + + if( DEBUGLVL( 3 ) ) + { + dbgtext( "request_oplock_break: sending a oplock break message to " ); + dbgtext( "pid %d on port %d ", share_entry->pid, share_entry->op_port ); + dbgtext( "for dev = %x, inode = %x\n", dev, inode ); + } + + if(sendto(oplock_sock,op_break_msg,OPLOCK_BREAK_MSG_LEN,0, + (struct sockaddr *)&addr_out,sizeof(addr_out)) < 0) + { + if( DEBUGLVL( 0 ) ) + { + dbgtext( "request_oplock_break: failed when sending a oplock " ); + dbgtext( "break message to pid %d ", share_entry->pid ); + dbgtext( "on port %d ", share_entry->op_port ); + dbgtext( "for dev = %x, inode = %x.\n", dev, inode ); + dbgtext( "Error was %s\n", strerror(errno) ); + } + return False; + } + + /* + * Now we must await the oplock broken message coming back + * from the target smbd process. Timeout if it fails to + * return in (OPLOCK_BREAK_TIMEOUT + OPLOCK_BREAK_TIMEOUT_FUDGEFACTOR) seconds. + * While we get messages that aren't ours, loop. + */ + + start_time = time(NULL); + time_left = OPLOCK_BREAK_TIMEOUT+OPLOCK_BREAK_TIMEOUT_FUDGEFACTOR; + + while(time_left >= 0) + { + char op_break_reply[UDP_CMD_HEADER_LEN+OPLOCK_BREAK_MSG_LEN]; + int32 reply_msg_len; + uint16 reply_from_port; + char *reply_msg_start; + + if(receive_local_message(oplock_sock, op_break_reply, sizeof(op_break_reply), + time_left ? time_left * 1000 : 1) == False) + { + if(smb_read_error == READ_TIMEOUT) + { + if( DEBUGLVL( 0 ) ) + { + dbgtext( "request_oplock_break: no response received to oplock " ); + dbgtext( "break request to pid %d ", share_entry->pid ); + dbgtext( "on port %d ", share_entry->op_port ); + dbgtext( "for dev = %x, inode = %x\n", dev, inode ); + } + /* + * This is a hack to make handling of failing clients more robust. + * If a oplock break response message is not received in the timeout + * period we may assume that the smbd servicing that client holding + * the oplock has died and the client changes were lost anyway, so + * we should continue to try and open the file. + */ + break; + } + else + if( DEBUGLVL( 0 ) ) + { + dbgtext( "request_oplock_break: error in response received " ); + dbgtext( "to oplock break request to pid %d ", share_entry->pid ); + dbgtext( "on port %d ", share_entry->op_port ); + dbgtext( "for dev = %x, inode = %x.\n", dev, inode ); + dbgtext( "Error was (%s).\n", strerror(errno) ); + } + return False; + } + + reply_msg_len = IVAL(op_break_reply,UDP_CMD_LEN_OFFSET); + reply_from_port = SVAL(op_break_reply,UDP_CMD_PORT_OFFSET); + + reply_msg_start = &op_break_reply[UDP_CMD_HEADER_LEN]; + + if(reply_msg_len != OPLOCK_BREAK_MSG_LEN) + { + /* Ignore it. */ + DEBUG( 0, ( "request_oplock_break: invalid message length received." ) ); + DEBUGADD( 0, ( " Ignoring.\n" ) ); + continue; + } + + /* + * Test to see if this is the reply we are awaiting. + */ + + if((SVAL(reply_msg_start,UDP_MESSAGE_CMD_OFFSET) & CMD_REPLY) && + (reply_from_port == share_entry->op_port) && + (memcmp(&reply_msg_start[OPLOCK_BREAK_PID_OFFSET], + &op_break_msg[OPLOCK_BREAK_PID_OFFSET], + OPLOCK_BREAK_MSG_LEN - OPLOCK_BREAK_PID_OFFSET) == 0)) + { + /* + * This is the reply we've been waiting for. + */ + break; + } + else + { + /* + * This is another message - probably a break request. + * Process it to prevent potential deadlock. + * Note that the code in switch_message() prevents + * us from recursing into here as any SMB requests + * we might process that would cause another oplock + * break request to be made will be queued. + * JRA. + */ + + process_local_message(oplock_sock, op_break_reply, sizeof(op_break_reply)); + } + + time_left -= (time(NULL) - start_time); + } + + DEBUG(3,("request_oplock_break: broke oplock.\n")); + + return True; +} + + +/**************************************************************************** + Attempt to break an oplock on a file (if oplocked). + Returns True if the file was closed as a result of + the oplock break, False otherwise. + Used as a last ditch attempt to free a space in the + file table when we have run out. +****************************************************************************/ +BOOL attempt_close_oplocked_file(files_struct *fsp) +{ + + DEBUG(5,("attempt_close_oplocked_file: checking file %s.\n", fsp->fsp_name)); + + if (fsp->open && fsp->granted_oplock && !fsp->sent_oplock_break) { + + /* Try and break the oplock. */ + file_fd_struct *fd_ptr = fsp->fd_ptr; + if(oplock_break( fd_ptr->dev, fd_ptr->inode, &fsp->open_time)) { + if(!fsp->open) /* Did the oplock break close the file ? */ + return True; + } + } + + return False; +} + |