static NTSTATUS close_remove_share_mode(files_struct *fsp, enum file_close_type close_type) { connection_struct *conn = fsp->conn; bool delete_file = false; bool changed_user = false; struct share_mode_lock *lck; SMB_STRUCT_STAT sbuf; NTSTATUS status = NT_STATUS_OK; int ret; struct file_id id; /* * Lock the share entries, and determine if we should delete * on close. If so delete whilst the lock is still in effect. * This prevents race conditions with the file being created. JRA. */ lck = get_share_mode_lock(talloc_tos(), fsp->file_id, NULL, NULL, NULL); if (lck == NULL) { DEBUG(0, ("close_remove_share_mode: Could not get share mode " "lock for file %s\n", fsp->fsp_name)); return NT_STATUS_INVALID_PARAMETER; } if (fsp->write_time_forced) { set_close_write_time(fsp, lck->changed_write_time); } if (!del_share_mode(lck, fsp)) { DEBUG(0, ("close_remove_share_mode: Could not delete share " "entry for file %s\n", fsp->fsp_name)); } if (fsp->initial_delete_on_close && (lck->delete_token == NULL)) { bool became_user = False; /* Initial delete on close was set and no one else * wrote a real delete on close. */ if (current_user.vuid != fsp->vuid) { become_user(conn, fsp->vuid); became_user = True; } set_delete_on_close_lck(lck, True, ¤t_user.ut); if (became_user) { unbecome_user(); } } delete_file = lck->delete_on_close; if (delete_file) { int i; /* See if others still have the file open. If this is the * case, then don't delete. If all opens are POSIX delete now. */ for (i=0; i<lck->num_share_modes; i++) { struct share_mode_entry *e = &lck->share_modes[i]; if (is_valid_share_mode_entry(e)) { if (fsp->posix_open && (e->flags & SHARE_MODE_FLAG_POSIX_OPEN)) { continue; } delete_file = False; break; } } } /* Notify any deferred opens waiting on this close. */ notify_deferred_opens(lck); reply_to_oplock_break_requests(fsp); /* * NT can set delete_on_close of the last open * reference to a file. */ if (!(close_type == NORMAL_CLOSE || close_type == SHUTDOWN_CLOSE) || !delete_file || (lck->delete_token == NULL)) { TALLOC_FREE(lck); return NT_STATUS_OK; } /* * Ok, we have to delete the file */ DEBUG(5,("close_remove_share_mode: file %s. Delete on close was set " "- deleting file.\n", fsp->fsp_name)); /* * Don't try to update the write time when we delete the file */ fsp->update_write_time_on_close = false; if (!unix_token_equal(lck->delete_token, ¤t_user.ut)) { /* Become the user who requested the delete. */ DEBUG(5,("close_remove_share_mode: file %s. " "Change user to uid %u\n", fsp->fsp_name, (unsigned int)lck->delete_token->uid)); if (!push_sec_ctx()) { smb_panic("close_remove_share_mode: file %s. failed to push " "sec_ctx.\n"); } set_sec_ctx(lck->delete_token->uid, lck->delete_token->gid, lck->delete_token->ngroups, lck->delete_token->groups, NULL); changed_user = true; } /* We can only delete the file if the name we have is still valid and hasn't been renamed. */ if (fsp->posix_open) { ret = SMB_VFS_LSTAT(conn,fsp->fsp_name,&sbuf); } else { ret = SMB_VFS_STAT(conn,fsp->fsp_name,&sbuf); } if (ret != 0) { DEBUG(5,("close_remove_share_mode: file %s. Delete on close " "was set and stat failed with error %s\n", fsp->fsp_name, strerror(errno) )); /* * Don't save the errno here, we ignore this error */ goto done; } id = vfs_file_id_from_sbuf(conn, &sbuf); if (!file_id_equal(&fsp->file_id, &id)) { DEBUG(5,("close_remove_share_mode: file %s. Delete on close " "was set and dev and/or inode does not match\n", fsp->fsp_name )); DEBUG(5,("close_remove_share_mode: file %s. stored file_id %s, " "stat file_id %s\n", fsp->fsp_name, file_id_string_tos(&fsp->file_id), file_id_string_tos(&id))); /* * Don't save the errno here, we ignore this error */ goto done; } if ((conn->fs_capabilities & FILE_NAMED_STREAMS) && !is_ntfs_stream_name(fsp->fsp_name)) { status = delete_all_streams(conn, fsp->fsp_name); if (!NT_STATUS_IS_OK(status)) { DEBUG(5, ("delete_all_streams failed: %s\n", nt_errstr(status))); goto done; } } if (SMB_VFS_UNLINK(conn,fsp->fsp_name) != 0) { /* * This call can potentially fail as another smbd may * have had the file open with delete on close set and * deleted it when its last reference to this file * went away. Hence we log this but not at debug level * zero. */ DEBUG(5,("close_remove_share_mode: file %s. Delete on close " "was set and unlink failed with error %s\n", fsp->fsp_name, strerror(errno) )); status = map_nt_error_from_unix(errno); } notify_fname(conn, NOTIFY_ACTION_REMOVED, FILE_NOTIFY_CHANGE_FILE_NAME, fsp->fsp_name); /* As we now have POSIX opens which can unlink * with other open files we may have taken * this code path with more than one share mode * entry - ensure we only delete once by resetting * the delete on close flag. JRA. */ set_delete_on_close_lck(lck, False, NULL); done: if (changed_user) { /* unbecome user. */ pop_sec_ctx(); } TALLOC_FREE(lck); return status; }
static NTSTATUS rmdir_internals(TALLOC_CTX *ctx, files_struct *fsp) { connection_struct *conn = fsp->conn; struct smb_filename *smb_dname = fsp->fsp_name; int ret; SMB_ASSERT(!is_ntfs_stream_smb_fname(smb_dname)); /* Might be a symlink. */ if(SMB_VFS_LSTAT(conn, smb_dname) != 0) { return map_nt_error_from_unix(errno); } if (S_ISLNK(smb_dname->st.st_ex_mode)) { /* Is what it points to a directory ? */ if(SMB_VFS_STAT(conn, smb_dname) != 0) { return map_nt_error_from_unix(errno); } if (!(S_ISDIR(smb_dname->st.st_ex_mode))) { return NT_STATUS_NOT_A_DIRECTORY; } ret = SMB_VFS_UNLINK(conn, smb_dname); } else { ret = SMB_VFS_RMDIR(conn, smb_dname); } if (ret == 0) { notify_fname(conn, NOTIFY_ACTION_REMOVED, FILE_NOTIFY_CHANGE_DIR_NAME, smb_dname->base_name); return NT_STATUS_OK; } if(((errno == ENOTEMPTY)||(errno == EEXIST)) && *lp_veto_files(talloc_tos(), SNUM(conn))) { /* * Check to see if the only thing in this directory are * vetoed files/directories. If so then delete them and * retry. If we fail to delete any of them (and we *don't* * do a recursive delete) then fail the rmdir. */ SMB_STRUCT_STAT st; const char *dname = NULL; char *talloced = NULL; long dirpos = 0; struct smb_Dir *dir_hnd = OpenDir(talloc_tos(), conn, smb_dname, NULL, 0); if(dir_hnd == NULL) { errno = ENOTEMPTY; goto err; } while ((dname = ReadDirName(dir_hnd, &dirpos, &st, &talloced)) != NULL) { if((strcmp(dname, ".") == 0) || (strcmp(dname, "..")==0)) { TALLOC_FREE(talloced); continue; } if (!is_visible_file(conn, smb_dname->base_name, dname, &st, false)) { TALLOC_FREE(talloced); continue; } if(!IS_VETO_PATH(conn, dname)) { TALLOC_FREE(dir_hnd); TALLOC_FREE(talloced); errno = ENOTEMPTY; goto err; } TALLOC_FREE(talloced); } /* We only have veto files/directories. * Are we allowed to delete them ? */ if(!lp_delete_veto_files(SNUM(conn))) { TALLOC_FREE(dir_hnd); errno = ENOTEMPTY; goto err; } /* Do a recursive delete. */ RewindDir(dir_hnd,&dirpos); while ((dname = ReadDirName(dir_hnd, &dirpos, &st, &talloced)) != NULL) { struct smb_filename *smb_dname_full = NULL; char *fullname = NULL; bool do_break = true; if (ISDOT(dname) || ISDOTDOT(dname)) { TALLOC_FREE(talloced); continue; } if (!is_visible_file(conn, smb_dname->base_name, dname, &st, false)) { TALLOC_FREE(talloced); continue; } fullname = talloc_asprintf(ctx, "%s/%s", smb_dname->base_name, dname); if(!fullname) { errno = ENOMEM; goto err_break; } smb_dname_full = synthetic_smb_fname(talloc_tos(), fullname, NULL, NULL, smb_dname->flags); if (smb_dname_full == NULL) { errno = ENOMEM; goto err_break; } if(SMB_VFS_LSTAT(conn, smb_dname_full) != 0) { goto err_break; } if(smb_dname_full->st.st_ex_mode & S_IFDIR) { if(!recursive_rmdir(ctx, conn, smb_dname_full)) { goto err_break; } if(SMB_VFS_RMDIR(conn, smb_dname_full) != 0) { goto err_break; } } else if(SMB_VFS_UNLINK(conn, smb_dname_full) != 0) { goto err_break; } /* Successful iteration. */ do_break = false; err_break: TALLOC_FREE(fullname); TALLOC_FREE(smb_dname_full); TALLOC_FREE(talloced); if (do_break) break; } TALLOC_FREE(dir_hnd); /* Retry the rmdir */ ret = SMB_VFS_RMDIR(conn, smb_dname); } err: if (ret != 0) { DEBUG(3,("rmdir_internals: couldn't remove directory %s : " "%s\n", smb_fname_str_dbg(smb_dname), strerror(errno))); return map_nt_error_from_unix(errno); } notify_fname(conn, NOTIFY_ACTION_REMOVED, FILE_NOTIFY_CHANGE_DIR_NAME, smb_dname->base_name); return NT_STATUS_OK; }
NTSTATUS file_set_sparse(connection_struct *conn, files_struct *fsp, bool sparse) { uint32_t old_dosmode; uint32_t new_dosmode; NTSTATUS status; if (!CAN_WRITE(conn)) { DEBUG(9,("file_set_sparse: fname[%s] set[%u] " "on readonly share[%s]\n", smb_fname_str_dbg(fsp->fsp_name), sparse, lp_servicename(talloc_tos(), SNUM(conn)))); return NT_STATUS_MEDIA_WRITE_PROTECTED; } /* * Windows Server 2008 & 2012 permit FSCTL_SET_SPARSE if any of the * following access flags are granted. */ if ((fsp->access_mask & (FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | SEC_FILE_APPEND_DATA)) == 0) { DEBUG(9,("file_set_sparse: fname[%s] set[%u] " "access_mask[0x%08X] - access denied\n", smb_fname_str_dbg(fsp->fsp_name), sparse, fsp->access_mask)); return NT_STATUS_ACCESS_DENIED; } if (fsp->is_directory) { DEBUG(9, ("invalid attempt to %s sparse flag on dir %s\n", (sparse ? "set" : "clear"), smb_fname_str_dbg(fsp->fsp_name))); return NT_STATUS_INVALID_PARAMETER; } if (IS_IPC(conn) || IS_PRINT(conn)) { DEBUG(9, ("attempt to %s sparse flag over invalid conn\n", (sparse ? "set" : "clear"))); return NT_STATUS_INVALID_PARAMETER; } DEBUG(10,("file_set_sparse: setting sparse bit %u on file %s\n", sparse, smb_fname_str_dbg(fsp->fsp_name))); if (!lp_store_dos_attributes(SNUM(conn))) { return NT_STATUS_INVALID_DEVICE_REQUEST; } status = vfs_stat_fsp(fsp); if (!NT_STATUS_IS_OK(status)) { return status; } old_dosmode = dos_mode(conn, fsp->fsp_name); if (sparse && !(old_dosmode & FILE_ATTRIBUTE_SPARSE)) { new_dosmode = old_dosmode | FILE_ATTRIBUTE_SPARSE; } else if (!sparse && (old_dosmode & FILE_ATTRIBUTE_SPARSE)) { new_dosmode = old_dosmode & ~FILE_ATTRIBUTE_SPARSE; } else { return NT_STATUS_OK; } /* Store the DOS attributes in an EA. */ if (!set_ea_dos_attribute(conn, fsp->fsp_name, new_dosmode)) { if (errno == 0) { errno = EIO; } return map_nt_error_from_unix(errno); } notify_fname(conn, NOTIFY_ACTION_MODIFIED, FILE_NOTIFY_CHANGE_ATTRIBUTES, fsp->fsp_name->base_name); fsp->is_sparse = sparse; return NT_STATUS_OK; }
static NTSTATUS close_remove_share_mode(files_struct *fsp, enum file_close_type close_type) { connection_struct *conn = fsp->conn; struct server_id self = messaging_server_id(conn->sconn->msg_ctx); bool delete_file = false; bool changed_user = false; struct share_mode_lock *lck = NULL; NTSTATUS status = NT_STATUS_OK; NTSTATUS tmp_status; struct file_id id; const struct security_unix_token *del_token = NULL; const struct security_token *del_nt_token = NULL; bool got_tokens = false; bool normal_close; /* Ensure any pending write time updates are done. */ if (fsp->update_write_time_event) { update_write_time_handler(fsp->conn->sconn->ev_ctx, fsp->update_write_time_event, timeval_current(), (void *)fsp); } /* * Lock the share entries, and determine if we should delete * on close. If so delete whilst the lock is still in effect. * This prevents race conditions with the file being created. JRA. */ lck = get_existing_share_mode_lock(talloc_tos(), fsp->file_id); if (lck == NULL) { DEBUG(0, ("close_remove_share_mode: Could not get share mode " "lock for file %s\n", fsp_str_dbg(fsp))); return NT_STATUS_INVALID_PARAMETER; } if (fsp->write_time_forced) { DEBUG(10,("close_remove_share_mode: write time forced " "for file %s\n", fsp_str_dbg(fsp))); set_close_write_time(fsp, lck->data->changed_write_time); } else if (fsp->update_write_time_on_close) { /* Someone had a pending write. */ if (null_timespec(fsp->close_write_time)) { DEBUG(10,("close_remove_share_mode: update to current time " "for file %s\n", fsp_str_dbg(fsp))); /* Update to current time due to "normal" write. */ set_close_write_time(fsp, timespec_current()); } else { DEBUG(10,("close_remove_share_mode: write time pending " "for file %s\n", fsp_str_dbg(fsp))); /* Update to time set on close call. */ set_close_write_time(fsp, fsp->close_write_time); } } if (fsp->initial_delete_on_close && !is_delete_on_close_set(lck, fsp->name_hash)) { bool became_user = False; /* Initial delete on close was set and no one else * wrote a real delete on close. */ if (get_current_vuid(conn) != fsp->vuid) { become_user(conn, fsp->vuid); became_user = True; } fsp->delete_on_close = true; set_delete_on_close_lck(fsp, lck, get_current_nttok(conn), get_current_utok(conn)); if (became_user) { unbecome_user(); } } delete_file = is_delete_on_close_set(lck, fsp->name_hash); if (delete_file) { int i; /* See if others still have the file open via this pathname. If this is the case, then don't delete. If all opens are POSIX delete now. */ for (i=0; i<lck->data->num_share_modes; i++) { struct share_mode_entry *e = &lck->data->share_modes[i]; if (!is_valid_share_mode_entry(e)) { continue; } if (e->name_hash != fsp->name_hash) { continue; } if ((fsp->posix_flags & FSP_POSIX_FLAGS_OPEN) && (e->flags & SHARE_MODE_FLAG_POSIX_OPEN)) { continue; } if (serverid_equal(&self, &e->pid) && (e->share_file_id == fsp->fh->gen_id)) { continue; } if (share_mode_stale_pid(lck->data, i)) { continue; } delete_file = False; break; } } /* * NT can set delete_on_close of the last open * reference to a file. */ normal_close = (close_type == NORMAL_CLOSE || close_type == SHUTDOWN_CLOSE); if (!normal_close || !delete_file) { status = NT_STATUS_OK; goto done; } /* * Ok, we have to delete the file */ DEBUG(5,("close_remove_share_mode: file %s. Delete on close was set " "- deleting file.\n", fsp_str_dbg(fsp))); /* * Don't try to update the write time when we delete the file */ fsp->update_write_time_on_close = false; got_tokens = get_delete_on_close_token(lck, fsp->name_hash, &del_nt_token, &del_token); SMB_ASSERT(got_tokens); if (!unix_token_equal(del_token, get_current_utok(conn))) { /* Become the user who requested the delete. */ DEBUG(5,("close_remove_share_mode: file %s. " "Change user to uid %u\n", fsp_str_dbg(fsp), (unsigned int)del_token->uid)); if (!push_sec_ctx()) { smb_panic("close_remove_share_mode: file %s. failed to push " "sec_ctx.\n"); } set_sec_ctx(del_token->uid, del_token->gid, del_token->ngroups, del_token->groups, del_nt_token); changed_user = true; } /* We can only delete the file if the name we have is still valid and hasn't been renamed. */ tmp_status = vfs_stat_fsp(fsp); if (!NT_STATUS_IS_OK(tmp_status)) { DEBUG(5,("close_remove_share_mode: file %s. Delete on close " "was set and stat failed with error %s\n", fsp_str_dbg(fsp), nt_errstr(tmp_status))); /* * Don't save the errno here, we ignore this error */ goto done; } id = vfs_file_id_from_sbuf(conn, &fsp->fsp_name->st); if (!file_id_equal(&fsp->file_id, &id)) { DEBUG(5,("close_remove_share_mode: file %s. Delete on close " "was set and dev and/or inode does not match\n", fsp_str_dbg(fsp))); DEBUG(5,("close_remove_share_mode: file %s. stored file_id %s, " "stat file_id %s\n", fsp_str_dbg(fsp), file_id_string_tos(&fsp->file_id), file_id_string_tos(&id))); /* * Don't save the errno here, we ignore this error */ goto done; } if ((conn->fs_capabilities & FILE_NAMED_STREAMS) && !is_ntfs_stream_smb_fname(fsp->fsp_name)) { status = delete_all_streams(conn, fsp->fsp_name); if (!NT_STATUS_IS_OK(status)) { DEBUG(5, ("delete_all_streams failed: %s\n", nt_errstr(status))); goto done; } } if (SMB_VFS_UNLINK(conn, fsp->fsp_name) != 0) { /* * This call can potentially fail as another smbd may * have had the file open with delete on close set and * deleted it when its last reference to this file * went away. Hence we log this but not at debug level * zero. */ DEBUG(5,("close_remove_share_mode: file %s. Delete on close " "was set and unlink failed with error %s\n", fsp_str_dbg(fsp), strerror(errno))); status = map_nt_error_from_unix(errno); } /* As we now have POSIX opens which can unlink * with other open files we may have taken * this code path with more than one share mode * entry - ensure we only delete once by resetting * the delete on close flag. JRA. */ fsp->delete_on_close = false; reset_delete_on_close_lck(fsp, lck); done: if (changed_user) { /* unbecome user. */ pop_sec_ctx(); } if (fsp->kernel_share_modes_taken) { int ret_flock; /* remove filesystem sharemodes */ ret_flock = SMB_VFS_KERNEL_FLOCK(fsp, 0, 0); if (ret_flock == -1) { DEBUG(2, ("close_remove_share_mode: removing kernel " "flock for %s failed: %s\n", fsp_str_dbg(fsp), strerror(errno))); } } if (!del_share_mode(lck, fsp)) { DEBUG(0, ("close_remove_share_mode: Could not delete share " "entry for file %s\n", fsp_str_dbg(fsp))); } TALLOC_FREE(lck); if (delete_file) { /* * Do the notification after we released the share * mode lock. Inside notify_fname we take out another * tdb lock. With ctdb also accessing our databases, * this can lead to deadlocks. Putting this notify * after the TALLOC_FREE(lck) above we avoid locking * two records simultaneously. Notifies are async and * informational only, so calling the notify_fname * without holding the share mode lock should not do * any harm. */ notify_fname(conn, NOTIFY_ACTION_REMOVED, FILE_NOTIFY_CHANGE_FILE_NAME, fsp->fsp_name->base_name); } return status; }
int file_set_dosmode(connection_struct *conn, struct smb_filename *smb_fname, uint32_t dosmode, const char *parent_dir, bool newfile) { int mask=0; mode_t tmp; mode_t unixmode; int ret = -1, lret = -1; uint32_t old_mode; struct timespec new_create_timespec; files_struct *fsp = NULL; bool need_close = false; NTSTATUS status; if (!CAN_WRITE(conn)) { errno = EROFS; return -1; } /* We only allow READONLY|HIDDEN|SYSTEM|DIRECTORY|ARCHIVE here. */ dosmode &= (SAMBA_ATTRIBUTES_MASK | FILE_ATTRIBUTE_OFFLINE); DEBUG(10,("file_set_dosmode: setting dos mode 0x%x on file %s\n", dosmode, smb_fname_str_dbg(smb_fname))); unixmode = smb_fname->st.st_ex_mode; get_acl_group_bits(conn, smb_fname->base_name, &smb_fname->st.st_ex_mode); if (S_ISDIR(smb_fname->st.st_ex_mode)) dosmode |= FILE_ATTRIBUTE_DIRECTORY; else dosmode &= ~FILE_ATTRIBUTE_DIRECTORY; new_create_timespec = smb_fname->st.st_ex_btime; old_mode = dos_mode(conn, smb_fname); if ((dosmode & FILE_ATTRIBUTE_OFFLINE) && !(old_mode & FILE_ATTRIBUTE_OFFLINE)) { lret = SMB_VFS_SET_OFFLINE(conn, smb_fname); if (lret == -1) { if (errno == ENOTSUP) { DEBUG(10, ("Setting FILE_ATTRIBUTE_OFFLINE for " "%s/%s is not supported.\n", parent_dir, smb_fname_str_dbg(smb_fname))); } else { DEBUG(0, ("An error occurred while setting " "FILE_ATTRIBUTE_OFFLINE for " "%s/%s: %s", parent_dir, smb_fname_str_dbg(smb_fname), strerror(errno))); } } } dosmode &= ~FILE_ATTRIBUTE_OFFLINE; old_mode &= ~FILE_ATTRIBUTE_OFFLINE; smb_fname->st.st_ex_btime = new_create_timespec; /* Store the DOS attributes in an EA by preference. */ if (lp_store_dos_attributes(SNUM(conn))) { /* * Don't fall back to using UNIX modes. Finally * follow the smb.conf manpage. */ if (!set_ea_dos_attribute(conn, smb_fname, dosmode)) { return -1; } if (!newfile) { notify_fname(conn, NOTIFY_ACTION_MODIFIED, FILE_NOTIFY_CHANGE_ATTRIBUTES, smb_fname->base_name); } smb_fname->st.st_ex_mode = unixmode; return 0; } unixmode = unix_mode(conn, dosmode, smb_fname, parent_dir); /* preserve the file type bits */ mask |= S_IFMT; /* preserve the s bits */ mask |= (S_ISUID | S_ISGID); /* preserve the t bit */ #ifdef S_ISVTX mask |= S_ISVTX; #endif /* possibly preserve the x bits */ if (!MAP_ARCHIVE(conn)) mask |= S_IXUSR; if (!MAP_SYSTEM(conn)) mask |= S_IXGRP; if (!MAP_HIDDEN(conn)) mask |= S_IXOTH; unixmode |= (smb_fname->st.st_ex_mode & mask); /* if we previously had any r bits set then leave them alone */ if ((tmp = smb_fname->st.st_ex_mode & (S_IRUSR|S_IRGRP|S_IROTH))) { unixmode &= ~(S_IRUSR|S_IRGRP|S_IROTH); unixmode |= tmp; } /* if we previously had any w bits set then leave them alone whilst adding in the new w bits, if the new mode is not rdonly */ if (!IS_DOS_READONLY(dosmode)) { unixmode |= (smb_fname->st.st_ex_mode & (S_IWUSR|S_IWGRP|S_IWOTH)); } /* * From the chmod 2 man page: * * "If the calling process is not privileged, and the group of the file * does not match the effective group ID of the process or one of its * supplementary group IDs, the S_ISGID bit will be turned off, but * this will not cause an error to be returned." * * Simply refuse to do the chmod in this case. */ if (S_ISDIR(smb_fname->st.st_ex_mode) && (unixmode & S_ISGID) && geteuid() != sec_initial_uid() && !current_user_in_group(conn, smb_fname->st.st_ex_gid)) { DEBUG(3,("file_set_dosmode: setgid bit cannot be " "set for directory %s\n", smb_fname_str_dbg(smb_fname))); errno = EPERM; return -1; } ret = SMB_VFS_CHMOD(conn, smb_fname->base_name, unixmode); if (ret == 0) { if(!newfile || (lret != -1)) { notify_fname(conn, NOTIFY_ACTION_MODIFIED, FILE_NOTIFY_CHANGE_ATTRIBUTES, smb_fname->base_name); } smb_fname->st.st_ex_mode = unixmode; return 0; } if((errno != EPERM) && (errno != EACCES)) return -1; if(!lp_dos_filemode(SNUM(conn))) return -1; /* We want DOS semantics, ie allow non owner with write permission to change the bits on a file. Just like file_ntimes below. */ if (!can_write_to_file(conn, smb_fname)) { errno = EACCES; return -1; } /* * We need to get an open file handle to do the * metadata operation under root. */ status = get_file_handle_for_metadata(conn, smb_fname, &fsp, &need_close); if (!NT_STATUS_IS_OK(status)) { errno = map_errno_from_nt_status(status); return -1; } become_root(); ret = SMB_VFS_FCHMOD(fsp, unixmode); unbecome_root(); if (need_close) { close_file(NULL, fsp, NORMAL_CLOSE); } if (!newfile) { notify_fname(conn, NOTIFY_ACTION_MODIFIED, FILE_NOTIFY_CHANGE_ATTRIBUTES, smb_fname->base_name); } if (ret == 0) { smb_fname->st.st_ex_mode = unixmode; } return( ret ); }