mirror of
https://github.com/samba-team/samba.git
synced 2025-02-21 01:59:07 +03:00
This patch introduces struct stat_ex { dev_t st_ex_dev; ino_t st_ex_ino; mode_t st_ex_mode; nlink_t st_ex_nlink; uid_t st_ex_uid; gid_t st_ex_gid; dev_t st_ex_rdev; off_t st_ex_size; struct timespec st_ex_atime; struct timespec st_ex_mtime; struct timespec st_ex_ctime; struct timespec st_ex_btime; /* birthtime */ blksize_t st_ex_blksize; blkcnt_t st_ex_blocks; }; typedef struct stat_ex SMB_STRUCT_STAT; It is really large because due to the friendly libc headers playing macro tricks with fields like st_ino, so I renamed them to st_ex_xxx. Why this change? To support birthtime, we already have quite a few #ifdef's at places where it does not really belong. With a stat struct that we control, we can consolidate the nanosecond timestamps and the birthtime deep in the VFS stat calls. At this moment it is triggered by a request to support the birthtime field for GPFS. GPFS does not extend the system level struct stat, but instead has a separate call that gets us the additional information beyond posix. Without being able to do that within the VFS stat calls, that support would have to be scattered around the main smbd code. It will very likely break all the onefs modules, but I think the changes will be reasonably easy to do.
612 lines
16 KiB
C
612 lines
16 KiB
C
/*
|
|
* Recycle bin VFS module for Samba.
|
|
*
|
|
* Copyright (C) 2001, Brandon Stone, Amherst College, <bbstone@amherst.edu>.
|
|
* Copyright (C) 2002, Jeremy Allison - modified to make a VFS module.
|
|
* Copyright (C) 2002, Alexander Bokovoy - cascaded VFS adoption,
|
|
* Copyright (C) 2002, Juergen Hasch - added some options.
|
|
* Copyright (C) 2002, Simo Sorce
|
|
* Copyright (C) 2002, Stefan (metze) Metzmacher
|
|
*
|
|
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "includes.h"
|
|
|
|
#define ALLOC_CHECK(ptr, label) do { if ((ptr) == NULL) { DEBUG(0, ("recycle.bin: out of memory!\n")); errno = ENOMEM; goto label; } } while(0)
|
|
|
|
static int vfs_recycle_debug_level = DBGC_VFS;
|
|
|
|
#undef DBGC_CLASS
|
|
#define DBGC_CLASS vfs_recycle_debug_level
|
|
|
|
static int recycle_connect(vfs_handle_struct *handle, const char *service, const char *user);
|
|
static void recycle_disconnect(vfs_handle_struct *handle);
|
|
static int recycle_unlink(vfs_handle_struct *handle, const char *name);
|
|
|
|
static vfs_op_tuple recycle_ops[] = {
|
|
|
|
/* Disk operations */
|
|
{SMB_VFS_OP(recycle_connect), SMB_VFS_OP_CONNECT, SMB_VFS_LAYER_TRANSPARENT},
|
|
{SMB_VFS_OP(recycle_disconnect), SMB_VFS_OP_DISCONNECT, SMB_VFS_LAYER_TRANSPARENT},
|
|
|
|
/* File operations */
|
|
{SMB_VFS_OP(recycle_unlink), SMB_VFS_OP_UNLINK, SMB_VFS_LAYER_TRANSPARENT},
|
|
|
|
{SMB_VFS_OP(NULL), SMB_VFS_OP_NOOP, SMB_VFS_LAYER_NOOP}
|
|
};
|
|
|
|
static int recycle_connect(vfs_handle_struct *handle, const char *service, const char *user)
|
|
{
|
|
DEBUG(10,("recycle_connect() connect to service[%s] as user[%s].\n",
|
|
service,user));
|
|
|
|
return SMB_VFS_NEXT_CONNECT(handle, service, user);
|
|
}
|
|
|
|
static void recycle_disconnect(vfs_handle_struct *handle)
|
|
{
|
|
DEBUG(10,("recycle_disconnect() connect to service[%s].\n",
|
|
lp_servicename(SNUM(handle->conn))));
|
|
|
|
SMB_VFS_NEXT_DISCONNECT(handle);
|
|
}
|
|
|
|
static const char *recycle_repository(vfs_handle_struct *handle)
|
|
{
|
|
const char *tmp_str = NULL;
|
|
|
|
|
|
tmp_str = lp_parm_const_string(SNUM(handle->conn), "recycle", "repository",".recycle");
|
|
|
|
DEBUG(10, ("recycle: repository = %s\n", tmp_str));
|
|
|
|
return tmp_str;
|
|
}
|
|
|
|
static bool recycle_keep_dir_tree(vfs_handle_struct *handle)
|
|
{
|
|
bool ret;
|
|
|
|
ret = lp_parm_bool(SNUM(handle->conn), "recycle", "keeptree", False);
|
|
|
|
DEBUG(10, ("recycle_bin: keeptree = %s\n", ret?"True":"False"));
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool recycle_versions(vfs_handle_struct *handle)
|
|
{
|
|
bool ret;
|
|
|
|
ret = lp_parm_bool(SNUM(handle->conn), "recycle", "versions", False);
|
|
|
|
DEBUG(10, ("recycle: versions = %s\n", ret?"True":"False"));
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool recycle_touch(vfs_handle_struct *handle)
|
|
{
|
|
bool ret;
|
|
|
|
ret = lp_parm_bool(SNUM(handle->conn), "recycle", "touch", False);
|
|
|
|
DEBUG(10, ("recycle: touch = %s\n", ret?"True":"False"));
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool recycle_touch_mtime(vfs_handle_struct *handle)
|
|
{
|
|
bool ret;
|
|
|
|
ret = lp_parm_bool(SNUM(handle->conn), "recycle", "touch_mtime", False);
|
|
|
|
DEBUG(10, ("recycle: touch_mtime = %s\n", ret?"True":"False"));
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const char **recycle_exclude(vfs_handle_struct *handle)
|
|
{
|
|
const char **tmp_lp;
|
|
|
|
tmp_lp = lp_parm_string_list(SNUM(handle->conn), "recycle", "exclude", NULL);
|
|
|
|
DEBUG(10, ("recycle: exclude = %s ...\n", tmp_lp?*tmp_lp:""));
|
|
|
|
return tmp_lp;
|
|
}
|
|
|
|
static const char **recycle_exclude_dir(vfs_handle_struct *handle)
|
|
{
|
|
const char **tmp_lp;
|
|
|
|
tmp_lp = lp_parm_string_list(SNUM(handle->conn), "recycle", "exclude_dir", NULL);
|
|
|
|
DEBUG(10, ("recycle: exclude_dir = %s ...\n", tmp_lp?*tmp_lp:""));
|
|
|
|
return tmp_lp;
|
|
}
|
|
|
|
static const char **recycle_noversions(vfs_handle_struct *handle)
|
|
{
|
|
const char **tmp_lp;
|
|
|
|
tmp_lp = lp_parm_string_list(SNUM(handle->conn), "recycle", "noversions", NULL);
|
|
|
|
DEBUG(10, ("recycle: noversions = %s\n", tmp_lp?*tmp_lp:""));
|
|
|
|
return tmp_lp;
|
|
}
|
|
|
|
static SMB_OFF_T recycle_maxsize(vfs_handle_struct *handle)
|
|
{
|
|
SMB_OFF_T maxsize;
|
|
|
|
maxsize = conv_str_size(lp_parm_const_string(SNUM(handle->conn),
|
|
"recycle", "maxsize", NULL));
|
|
|
|
DEBUG(10, ("recycle: maxsize = %lu\n", (long unsigned int)maxsize));
|
|
|
|
return maxsize;
|
|
}
|
|
|
|
static SMB_OFF_T recycle_minsize(vfs_handle_struct *handle)
|
|
{
|
|
SMB_OFF_T minsize;
|
|
|
|
minsize = conv_str_size(lp_parm_const_string(SNUM(handle->conn),
|
|
"recycle", "minsize", NULL));
|
|
|
|
DEBUG(10, ("recycle: minsize = %lu\n", (long unsigned int)minsize));
|
|
|
|
return minsize;
|
|
}
|
|
|
|
static mode_t recycle_directory_mode(vfs_handle_struct *handle)
|
|
{
|
|
int dirmode;
|
|
const char *buff;
|
|
|
|
buff = lp_parm_const_string(SNUM(handle->conn), "recycle", "directory_mode", NULL);
|
|
|
|
if (buff != NULL ) {
|
|
sscanf(buff, "%o", &dirmode);
|
|
} else {
|
|
dirmode=S_IRUSR | S_IWUSR | S_IXUSR;
|
|
}
|
|
|
|
DEBUG(10, ("recycle: directory_mode = %o\n", dirmode));
|
|
return (mode_t)dirmode;
|
|
}
|
|
|
|
static mode_t recycle_subdir_mode(vfs_handle_struct *handle)
|
|
{
|
|
int dirmode;
|
|
const char *buff;
|
|
|
|
buff = lp_parm_const_string(SNUM(handle->conn), "recycle", "subdir_mode", NULL);
|
|
|
|
if (buff != NULL ) {
|
|
sscanf(buff, "%o", &dirmode);
|
|
} else {
|
|
dirmode=recycle_directory_mode(handle);
|
|
}
|
|
|
|
DEBUG(10, ("recycle: subdir_mode = %o\n", dirmode));
|
|
return (mode_t)dirmode;
|
|
}
|
|
|
|
static bool recycle_directory_exist(vfs_handle_struct *handle, const char *dname)
|
|
{
|
|
SMB_STRUCT_STAT st;
|
|
|
|
if (SMB_VFS_NEXT_STAT(handle, dname, &st) == 0) {
|
|
if (S_ISDIR(st.st_ex_mode)) {
|
|
return True;
|
|
}
|
|
}
|
|
|
|
return False;
|
|
}
|
|
|
|
static bool recycle_file_exist(vfs_handle_struct *handle, const char *fname)
|
|
{
|
|
SMB_STRUCT_STAT st;
|
|
|
|
if (SMB_VFS_NEXT_STAT(handle, fname, &st) == 0) {
|
|
if (S_ISREG(st.st_ex_mode)) {
|
|
return True;
|
|
}
|
|
}
|
|
|
|
return False;
|
|
}
|
|
|
|
/**
|
|
* Return file size
|
|
* @param conn connection
|
|
* @param fname file name
|
|
* @return size in bytes
|
|
**/
|
|
static SMB_OFF_T recycle_get_file_size(vfs_handle_struct *handle, const char *fname)
|
|
{
|
|
SMB_STRUCT_STAT st;
|
|
|
|
if (SMB_VFS_NEXT_STAT(handle, fname, &st) != 0) {
|
|
DEBUG(0,("recycle: stat for %s returned %s\n", fname, strerror(errno)));
|
|
return (SMB_OFF_T)0;
|
|
}
|
|
|
|
return(st.st_ex_size);
|
|
}
|
|
|
|
/**
|
|
* Create directory tree
|
|
* @param conn connection
|
|
* @param dname Directory tree to be created
|
|
* @return Returns True for success
|
|
**/
|
|
static bool recycle_create_dir(vfs_handle_struct *handle, const char *dname)
|
|
{
|
|
size_t len;
|
|
mode_t mode;
|
|
char *new_dir = NULL;
|
|
char *tmp_str = NULL;
|
|
char *token;
|
|
char *tok_str;
|
|
bool ret = False;
|
|
char *saveptr;
|
|
|
|
mode = recycle_directory_mode(handle);
|
|
|
|
tmp_str = SMB_STRDUP(dname);
|
|
ALLOC_CHECK(tmp_str, done);
|
|
tok_str = tmp_str;
|
|
|
|
len = strlen(dname)+1;
|
|
new_dir = (char *)SMB_MALLOC(len + 1);
|
|
ALLOC_CHECK(new_dir, done);
|
|
*new_dir = '\0';
|
|
if (dname[0] == '/') {
|
|
/* Absolute path. */
|
|
safe_strcat(new_dir,"/",len);
|
|
}
|
|
|
|
/* Create directory tree if neccessary */
|
|
for(token = strtok_r(tok_str, "/", &saveptr); token;
|
|
token = strtok_r(NULL, "/", &saveptr)) {
|
|
safe_strcat(new_dir, token, len);
|
|
if (recycle_directory_exist(handle, new_dir))
|
|
DEBUG(10, ("recycle: dir %s already exists\n", new_dir));
|
|
else {
|
|
DEBUG(5, ("recycle: creating new dir %s\n", new_dir));
|
|
if (SMB_VFS_NEXT_MKDIR(handle, new_dir, mode) != 0) {
|
|
DEBUG(1,("recycle: mkdir failed for %s with error: %s\n", new_dir, strerror(errno)));
|
|
ret = False;
|
|
goto done;
|
|
}
|
|
}
|
|
safe_strcat(new_dir, "/", len);
|
|
mode = recycle_subdir_mode(handle);
|
|
}
|
|
|
|
ret = True;
|
|
done:
|
|
SAFE_FREE(tmp_str);
|
|
SAFE_FREE(new_dir);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Check if any of the components of "exclude_list" are contained in path.
|
|
* Return True if found
|
|
**/
|
|
|
|
static bool matchdirparam(const char **dir_exclude_list, char *path)
|
|
{
|
|
char *startp = NULL, *endp = NULL;
|
|
|
|
if (dir_exclude_list == NULL || dir_exclude_list[0] == NULL ||
|
|
*dir_exclude_list[0] == '\0' || path == NULL || *path == '\0') {
|
|
return False;
|
|
}
|
|
|
|
/*
|
|
* Walk the components of path, looking for matches with the
|
|
* exclude list on each component.
|
|
*/
|
|
|
|
for (startp = path; startp; startp = endp) {
|
|
int i;
|
|
|
|
while (*startp == '/') {
|
|
startp++;
|
|
}
|
|
endp = strchr(startp, '/');
|
|
if (endp) {
|
|
*endp = '\0';
|
|
}
|
|
|
|
for(i=0; dir_exclude_list[i] ; i++) {
|
|
if(unix_wild_match(dir_exclude_list[i], startp)) {
|
|
/* Repair path. */
|
|
if (endp) {
|
|
*endp = '/';
|
|
}
|
|
return True;
|
|
}
|
|
}
|
|
|
|
/* Repair path. */
|
|
if (endp) {
|
|
*endp = '/';
|
|
}
|
|
}
|
|
|
|
return False;
|
|
}
|
|
|
|
/**
|
|
* Check if needle is contained in haystack, * and ? patterns are resolved
|
|
* @param haystack list of parameters separated by delimimiter character
|
|
* @param needle string to be matched exectly to haystack including pattern matching
|
|
* @return True if found
|
|
**/
|
|
static bool matchparam(const char **haystack_list, const char *needle)
|
|
{
|
|
int i;
|
|
|
|
if (haystack_list == NULL || haystack_list[0] == NULL ||
|
|
*haystack_list[0] == '\0' || needle == NULL || *needle == '\0') {
|
|
return False;
|
|
}
|
|
|
|
for(i=0; haystack_list[i] ; i++) {
|
|
if(unix_wild_match(haystack_list[i], needle)) {
|
|
return True;
|
|
}
|
|
}
|
|
|
|
return False;
|
|
}
|
|
|
|
/**
|
|
* Touch access or modify date
|
|
**/
|
|
static void recycle_do_touch(vfs_handle_struct *handle, const char *fname,
|
|
bool touch_mtime)
|
|
{
|
|
SMB_STRUCT_STAT st;
|
|
struct smb_file_time ft;
|
|
int ret, err;
|
|
|
|
ZERO_STRUCT(ft);
|
|
|
|
if (SMB_VFS_NEXT_STAT(handle, fname, &st) != 0) {
|
|
DEBUG(0,("recycle: stat for %s returned %s\n",
|
|
fname, strerror(errno)));
|
|
return;
|
|
}
|
|
ft.atime = timespec_current(); /* atime */
|
|
ft.mtime = touch_mtime ? ft.atime : st.st_ex_mtime; /* mtime */
|
|
|
|
become_root();
|
|
ret = SMB_VFS_NEXT_NTIMES(handle, fname, &ft);
|
|
err = errno;
|
|
unbecome_root();
|
|
if (ret == -1 ) {
|
|
DEBUG(0, ("recycle: touching %s failed, reason = %s\n",
|
|
fname, strerror(err)));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if file should be recycled
|
|
**/
|
|
static int recycle_unlink(vfs_handle_struct *handle, const char *file_name)
|
|
{
|
|
connection_struct *conn = handle->conn;
|
|
char *path_name = NULL;
|
|
char *temp_name = NULL;
|
|
char *final_name = NULL;
|
|
const char *base;
|
|
char *repository = NULL;
|
|
int i = 1;
|
|
SMB_OFF_T maxsize, minsize;
|
|
SMB_OFF_T file_size; /* space_avail; */
|
|
bool exist;
|
|
int rc = -1;
|
|
|
|
repository = talloc_sub_advanced(NULL, lp_servicename(SNUM(conn)),
|
|
conn->server_info->unix_name,
|
|
conn->connectpath,
|
|
conn->server_info->utok.gid,
|
|
conn->server_info->sanitized_username,
|
|
pdb_get_domain(conn->server_info->sam_account),
|
|
recycle_repository(handle));
|
|
ALLOC_CHECK(repository, done);
|
|
/* shouldn't we allow absolute path names here? --metze */
|
|
/* Yes :-). JRA. */
|
|
trim_char(repository, '\0', '/');
|
|
|
|
if(!repository || *(repository) == '\0') {
|
|
DEBUG(3, ("recycle: repository path not set, purging %s...\n", file_name));
|
|
rc = SMB_VFS_NEXT_UNLINK(handle, file_name);
|
|
goto done;
|
|
}
|
|
|
|
/* we don't recycle the recycle bin... */
|
|
if (strncmp(file_name, repository, strlen(repository)) == 0) {
|
|
DEBUG(3, ("recycle: File is within recycling bin, unlinking ...\n"));
|
|
rc = SMB_VFS_NEXT_UNLINK(handle, file_name);
|
|
goto done;
|
|
}
|
|
|
|
file_size = recycle_get_file_size(handle, file_name);
|
|
/* it is wrong to purge filenames only because they are empty imho
|
|
* --- simo
|
|
*
|
|
if(fsize == 0) {
|
|
DEBUG(3, ("recycle: File %s is empty, purging...\n", file_name));
|
|
rc = SMB_VFS_NEXT_UNLINK(handle,file_name);
|
|
goto done;
|
|
}
|
|
*/
|
|
|
|
/* FIXME: this is wrong, we should check the whole size of the recycle bin is
|
|
* not greater then maxsize, not the size of the single file, also it is better
|
|
* to remove older files
|
|
*/
|
|
maxsize = recycle_maxsize(handle);
|
|
if(maxsize > 0 && file_size > maxsize) {
|
|
DEBUG(3, ("recycle: File %s exceeds maximum recycle size, purging... \n", file_name));
|
|
rc = SMB_VFS_NEXT_UNLINK(handle, file_name);
|
|
goto done;
|
|
}
|
|
minsize = recycle_minsize(handle);
|
|
if(minsize > 0 && file_size < minsize) {
|
|
DEBUG(3, ("recycle: File %s lowers minimum recycle size, purging... \n", file_name));
|
|
rc = SMB_VFS_NEXT_UNLINK(handle, file_name);
|
|
goto done;
|
|
}
|
|
|
|
/* FIXME: this is wrong: moving files with rename does not change the disk space
|
|
* allocation
|
|
*
|
|
space_avail = SMB_VFS_NEXT_DISK_FREE(handle, ".", True, &bsize, &dfree, &dsize) * 1024L;
|
|
DEBUG(5, ("space_avail = %Lu, file_size = %Lu\n", space_avail, file_size));
|
|
if(space_avail < file_size) {
|
|
DEBUG(3, ("recycle: Not enough diskspace, purging file %s\n", file_name));
|
|
rc = SMB_VFS_NEXT_UNLINK(handle, file_name);
|
|
goto done;
|
|
}
|
|
*/
|
|
|
|
/* extract filename and path */
|
|
base = strrchr(file_name, '/');
|
|
if (base == NULL) {
|
|
base = file_name;
|
|
path_name = SMB_STRDUP("/");
|
|
ALLOC_CHECK(path_name, done);
|
|
}
|
|
else {
|
|
path_name = SMB_STRDUP(file_name);
|
|
ALLOC_CHECK(path_name, done);
|
|
path_name[base - file_name] = '\0';
|
|
base++;
|
|
}
|
|
|
|
DEBUG(10, ("recycle: fname = %s\n", file_name)); /* original filename with path */
|
|
DEBUG(10, ("recycle: fpath = %s\n", path_name)); /* original path */
|
|
DEBUG(10, ("recycle: base = %s\n", base)); /* filename without path */
|
|
|
|
if (matchparam(recycle_exclude(handle), base)) {
|
|
DEBUG(3, ("recycle: file %s is excluded \n", base));
|
|
rc = SMB_VFS_NEXT_UNLINK(handle, file_name);
|
|
goto done;
|
|
}
|
|
|
|
if (matchdirparam(recycle_exclude_dir(handle), path_name)) {
|
|
DEBUG(3, ("recycle: directory %s is excluded \n", path_name));
|
|
rc = SMB_VFS_NEXT_UNLINK(handle, file_name);
|
|
goto done;
|
|
}
|
|
|
|
if (recycle_keep_dir_tree(handle) == True) {
|
|
if (asprintf(&temp_name, "%s/%s", repository, path_name) == -1) {
|
|
ALLOC_CHECK(temp_name, done);
|
|
}
|
|
} else {
|
|
temp_name = SMB_STRDUP(repository);
|
|
}
|
|
ALLOC_CHECK(temp_name, done);
|
|
|
|
exist = recycle_directory_exist(handle, temp_name);
|
|
if (exist) {
|
|
DEBUG(10, ("recycle: Directory already exists\n"));
|
|
} else {
|
|
DEBUG(10, ("recycle: Creating directory %s\n", temp_name));
|
|
if (recycle_create_dir(handle, temp_name) == False) {
|
|
DEBUG(3, ("recycle: Could not create directory, purging %s...\n", file_name));
|
|
rc = SMB_VFS_NEXT_UNLINK(handle, file_name);
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
if (asprintf(&final_name, "%s/%s", temp_name, base) == -1) {
|
|
ALLOC_CHECK(final_name, done);
|
|
}
|
|
DEBUG(10, ("recycle: recycled file name: %s\n", final_name)); /* new filename with path */
|
|
|
|
/* check if we should delete file from recycle bin */
|
|
if (recycle_file_exist(handle, final_name)) {
|
|
if (recycle_versions(handle) == False || matchparam(recycle_noversions(handle), base) == True) {
|
|
DEBUG(3, ("recycle: Removing old file %s from recycle bin\n", final_name));
|
|
if (SMB_VFS_NEXT_UNLINK(handle, final_name) != 0) {
|
|
DEBUG(1, ("recycle: Error deleting old file: %s\n", strerror(errno)));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* rename file we move to recycle bin */
|
|
i = 1;
|
|
while (recycle_file_exist(handle, final_name)) {
|
|
SAFE_FREE(final_name);
|
|
if (asprintf(&final_name, "%s/Copy #%d of %s", temp_name, i++, base) == -1) {
|
|
ALLOC_CHECK(final_name, done);
|
|
}
|
|
}
|
|
|
|
DEBUG(10, ("recycle: Moving %s to %s\n", file_name, final_name));
|
|
rc = SMB_VFS_NEXT_RENAME(handle, file_name, final_name);
|
|
if (rc != 0) {
|
|
DEBUG(3, ("recycle: Move error %d (%s), purging file %s (%s)\n", errno, strerror(errno), file_name, final_name));
|
|
rc = SMB_VFS_NEXT_UNLINK(handle, file_name);
|
|
goto done;
|
|
}
|
|
|
|
/* touch access date of moved file */
|
|
if (recycle_touch(handle) == True || recycle_touch_mtime(handle))
|
|
recycle_do_touch(handle, final_name, recycle_touch_mtime(handle));
|
|
|
|
done:
|
|
SAFE_FREE(path_name);
|
|
SAFE_FREE(temp_name);
|
|
SAFE_FREE(final_name);
|
|
TALLOC_FREE(repository);
|
|
return rc;
|
|
}
|
|
|
|
NTSTATUS vfs_recycle_init(void);
|
|
NTSTATUS vfs_recycle_init(void)
|
|
{
|
|
NTSTATUS ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "recycle", recycle_ops);
|
|
|
|
if (!NT_STATUS_IS_OK(ret))
|
|
return ret;
|
|
|
|
vfs_recycle_debug_level = debug_add_class("recycle");
|
|
if (vfs_recycle_debug_level == -1) {
|
|
vfs_recycle_debug_level = DBGC_VFS;
|
|
DEBUG(0, ("vfs_recycle: Couldn't register custom debugging class!\n"));
|
|
} else {
|
|
DEBUG(10, ("vfs_recycle: Debug class number of 'recycle': %d\n", vfs_recycle_debug_level));
|
|
}
|
|
|
|
return ret;
|
|
}
|