2 * Recycle bin VFS module for Samba.
4 * Copyright (C) 2001, Brandon Stone, Amherst College, <bbstone@amherst.edu>.
5 * Copyright (C) 2002, Jeremy Allison - modified to make a VFS module.
6 * Copyright (C) 2002, Alexander Bokovoy - cascaded VFS adoption,
7 * Copyright (C) 2002, Juergen Hasch - added some options.
8 * Copyright (C) 2002, Simo Sorce
9 * Copyright (C) 2002, Stefan (metze) Metzmacher
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 3 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, see <http://www.gnu.org/licenses/>.
26 #include "smbd/smbd.h"
27 #include "system/filesys.h"
28 #include "../librpc/gen_ndr/ndr_netlogon.h"
30 #include "source3/lib/substitute.h"
32 #define ALLOC_CHECK(ptr, label) do { if ((ptr) == NULL) { DEBUG(0, ("recycle.bin: out of memory!\n")); errno = ENOMEM; goto label; } } while(0)
34 static int vfs_recycle_debug_level = DBGC_VFS;
37 #define DBGC_CLASS vfs_recycle_debug_level
39 struct recycle_config_data {
40 const char *repository;
46 const char **exclude_dir;
47 const char **noversions;
48 mode_t directory_mode;
54 static int vfs_recycle_connect(struct vfs_handle_struct *handle,
58 const struct loadparm_substitution *lp_sub =
59 loadparm_s3_global_substitution();
60 struct recycle_config_data *config = NULL;
63 const char *buff = NULL;
64 const char **tmplist = NULL;
65 char *repository = NULL;
67 ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
72 if (IS_IPC(handle->conn) || IS_PRINT(handle->conn)) {
76 config = talloc_zero(handle->conn, struct recycle_config_data);
78 DBG_ERR("talloc_zero() failed\n");
82 buff = lp_parm_const_string(SNUM(handle->conn),
86 repository = talloc_sub_full(
88 lp_servicename(talloc_tos(), lp_sub, SNUM(handle->conn)),
89 handle->conn->session_info->unix_info->unix_name,
90 handle->conn->connectpath,
91 handle->conn->session_info->unix_token->gid,
92 handle->conn->session_info->unix_info->sanitized_username,
93 handle->conn->session_info->info->domain_name,
95 if (repository == NULL) {
96 DBG_ERR("talloc_sub_full() failed\n");
101 /* shouldn't we allow absolute path names here? --metze */
103 trim_char(repository, '\0', '/');
104 config->repository = repository;
106 config->keeptree = lp_parm_bool(SNUM(handle->conn),
110 config->versions = lp_parm_bool(SNUM(handle->conn),
114 config->touch = lp_parm_bool(SNUM(handle->conn),
118 config->touch_mtime = lp_parm_bool(SNUM(handle->conn),
122 tmplist = lp_parm_string_list(SNUM(handle->conn),
126 if (tmplist != NULL) {
127 char **tmpcpy = str_list_copy(config, tmplist);
128 if (tmpcpy == NULL) {
129 DBG_ERR("str_list_copy() failed\n");
134 config->exclude = discard_const_p(const char *, tmpcpy);
136 tmplist = lp_parm_string_list(SNUM(handle->conn),
140 if (tmplist != NULL) {
141 char **tmpcpy = str_list_copy(config, tmplist);
142 if (tmpcpy == NULL) {
143 DBG_ERR("str_list_copy() failed\n");
148 config->exclude_dir = discard_const_p(const char *, tmpcpy);
150 tmplist = lp_parm_string_list(SNUM(handle->conn),
154 if (tmplist != NULL) {
155 char **tmpcpy = str_list_copy(config, tmplist);
156 if (tmpcpy == NULL) {
157 DBG_ERR("str_list_copy() failed\n");
162 config->noversions = discard_const_p(const char *, tmpcpy);
164 config->minsize = conv_str_size(lp_parm_const_string(
165 SNUM(handle->conn), "recycle", "minsize", NULL));
166 config->maxsize = conv_str_size(lp_parm_const_string(
167 SNUM(handle->conn), "recycle", "maxsize", NULL));
169 buff = lp_parm_const_string(SNUM(handle->conn),
174 sscanf(buff, "%o", &t);
176 t = S_IRUSR | S_IWUSR | S_IXUSR;
178 config->directory_mode = (mode_t)t;
180 buff = lp_parm_const_string(SNUM(handle->conn),
185 sscanf(buff, "%o", &t);
187 t = config->directory_mode;
189 config->subdir_mode = (mode_t)t;
191 SMB_VFS_HANDLE_SET_DATA(
192 handle, config, NULL, struct recycle_config_data, return -1);
196 static bool recycle_directory_exist(vfs_handle_struct *handle, const char *dname)
198 struct smb_filename smb_fname = {
199 .base_name = discard_const_p(char, dname)
202 if (SMB_VFS_STAT(handle->conn, &smb_fname) == 0) {
203 if (S_ISDIR(smb_fname.st.st_ex_mode)) {
211 static bool recycle_file_exist(vfs_handle_struct *handle,
212 const struct smb_filename *smb_fname)
214 struct smb_filename *smb_fname_tmp = NULL;
217 smb_fname_tmp = cp_smb_filename(talloc_tos(), smb_fname);
218 if (smb_fname_tmp == NULL) {
222 if (SMB_VFS_STAT(handle->conn, smb_fname_tmp) == 0) {
223 if (S_ISREG(smb_fname_tmp->st.st_ex_mode)) {
228 TALLOC_FREE(smb_fname_tmp);
234 * @param conn connection
235 * @param fname file name
236 * @return size in bytes
238 static off_t recycle_get_file_size(vfs_handle_struct *handle,
239 const struct smb_filename *smb_fname)
241 struct smb_filename *smb_fname_tmp = NULL;
244 smb_fname_tmp = cp_smb_filename(talloc_tos(), smb_fname);
245 if (smb_fname_tmp == NULL) {
250 if (SMB_VFS_STAT(handle->conn, smb_fname_tmp) != 0) {
251 DBG_DEBUG("stat for %s returned %s\n",
252 smb_fname_str_dbg(smb_fname_tmp), strerror(errno));
257 size = smb_fname_tmp->st.st_ex_size;
259 TALLOC_FREE(smb_fname_tmp);
264 * Create directory tree
265 * @param conn connection
266 * @param dname Directory tree to be created
267 * @param directory mode
268 * @param subdirectory mode
269 * @return Returns True for success
271 static bool recycle_create_dir(vfs_handle_struct *handle,
277 mode_t mode = dir_mode;
278 char *new_dir = NULL;
279 char *tmp_str = NULL;
285 tmp_str = SMB_STRDUP(dname);
286 ALLOC_CHECK(tmp_str, done);
289 len = strlen(dname)+1;
290 new_dir = (char *)SMB_MALLOC(len + 1);
291 ALLOC_CHECK(new_dir, done);
293 if (dname[0] == '/') {
295 if (strlcat(new_dir,"/",len+1) >= len+1) {
300 /* Create directory tree if necessary */
301 for(token = strtok_r(tok_str, "/", &saveptr); token;
302 token = strtok_r(NULL, "/", &saveptr)) {
303 if (strlcat(new_dir, token, len+1) >= len+1) {
306 if (recycle_directory_exist(handle, new_dir))
307 DEBUG(10, ("recycle: dir %s already exists\n", new_dir));
309 struct smb_filename *smb_fname = NULL;
312 DEBUG(5, ("recycle: creating new dir %s\n", new_dir));
314 smb_fname = synthetic_smb_fname(talloc_tos(),
320 if (smb_fname == NULL) {
324 retval = SMB_VFS_NEXT_MKDIRAT(handle,
325 handle->conn->cwd_fsp,
329 DBG_WARNING("recycle: mkdirat failed "
330 "for %s with error: %s\n",
333 TALLOC_FREE(smb_fname);
337 TALLOC_FREE(smb_fname);
339 if (strlcat(new_dir, "/", len+1) >= len+1) {
353 * Check if any of the components of "exclude_list" are contained in path.
354 * Return True if found
357 static bool matchdirparam(const char **dir_exclude_list, char *path)
359 char *startp = NULL, *endp = NULL;
361 if (dir_exclude_list == NULL || dir_exclude_list[0] == NULL ||
362 *dir_exclude_list[0] == '\0' || path == NULL || *path == '\0') {
367 * Walk the components of path, looking for matches with the
368 * exclude list on each component.
371 for (startp = path; startp; startp = endp) {
374 while (*startp == '/') {
377 endp = strchr(startp, '/');
382 for(i=0; dir_exclude_list[i] ; i++) {
383 if(unix_wild_match(dir_exclude_list[i], startp)) {
402 * Check if needle is contained in haystack, * and ? patterns are resolved
403 * @param haystack list of parameters separated by delimimiter character
404 * @param needle string to be matched exectly to haystack including pattern matching
405 * @return True if found
407 static bool matchparam(const char **haystack_list, const char *needle)
411 if (haystack_list == NULL || haystack_list[0] == NULL ||
412 *haystack_list[0] == '\0' || needle == NULL || *needle == '\0') {
416 for(i=0; haystack_list[i] ; i++) {
417 if(unix_wild_match(haystack_list[i], needle)) {
426 * Touch access or modify date
428 static void recycle_do_touch(vfs_handle_struct *handle,
429 const struct smb_filename *smb_fname,
432 struct smb_filename *smb_fname_tmp = NULL;
433 struct smb_file_time ft;
437 init_smb_file_time(&ft);
439 status = synthetic_pathref(talloc_tos(),
440 handle->conn->cwd_fsp,
441 smb_fname->base_name,
442 smb_fname->stream_name,
447 if (!NT_STATUS_IS_OK(status)) {
448 DBG_DEBUG("synthetic_pathref for '%s' failed: %s\n",
449 smb_fname_str_dbg(smb_fname), nt_errstr(status));
454 ft.atime = timespec_current();
456 ft.mtime = touch_mtime ? ft.atime : smb_fname_tmp->st.st_ex_mtime;
459 ret = SMB_VFS_NEXT_FNTIMES(handle, smb_fname_tmp->fsp, &ft);
463 DEBUG(0, ("recycle: touching %s failed, reason = %s\n",
464 smb_fname_str_dbg(smb_fname_tmp), strerror(err)));
467 TALLOC_FREE(smb_fname_tmp);
471 * Check if file should be recycled
473 static int recycle_unlink_internal(vfs_handle_struct *handle,
474 struct files_struct *dirfsp,
475 const struct smb_filename *smb_fname,
478 TALLOC_CTX *frame = NULL;
479 struct smb_filename *full_fname = NULL;
480 char *path_name = NULL;
481 const char *temp_name = NULL;
482 const char *final_name = NULL;
483 struct smb_filename *smb_fname_final = NULL;
484 const char *base = NULL;
486 off_t file_size; /* space_avail; */
489 struct recycle_config_data *config = NULL;
490 struct vfs_rename_how rhow = { .flags = 0, };
492 SMB_VFS_HANDLE_GET_DATA(handle,
494 struct recycle_config_data,
497 frame = talloc_stackframe();
499 if (config->repository[0] == '\0') {
500 DEBUG(3, ("recycle: repository path not set, purging %s...\n",
501 smb_fname_str_dbg(smb_fname)));
502 rc = SMB_VFS_NEXT_UNLINKAT(handle,
509 full_fname = full_path_from_dirfsp_atname(frame,
512 if (full_fname == NULL) {
518 /* we don't recycle the recycle bin... */
519 if (strncmp(full_fname->base_name, config->repository,
520 strlen(config->repository)) == 0) {
521 DEBUG(3, ("recycle: File is within recycling bin, unlinking ...\n"));
522 rc = SMB_VFS_NEXT_UNLINKAT(handle,
529 file_size = recycle_get_file_size(handle, full_fname);
530 /* it is wrong to purge filenames only because they are empty imho
534 DEBUG(3, ("recycle: File %s is empty, purging...\n", file_name));
535 rc = SMB_VFS_NEXT_UNLINKAT(handle,
543 /* FIXME: this is wrong, we should check the whole size of the recycle bin is
544 * not greater then maxsize, not the size of the single file, also it is better
545 * to remove older files
547 if (config->maxsize > 0 && file_size > config->maxsize) {
548 DBG_NOTICE("File %s exceeds maximum recycle size, "
550 smb_fname_str_dbg(full_fname));
551 rc = SMB_VFS_NEXT_UNLINKAT(handle,
557 if (config->minsize > 0 && file_size < config->minsize) {
558 DBG_NOTICE("File %s lowers minimum recycle size, "
560 smb_fname_str_dbg(full_fname));
561 rc = SMB_VFS_NEXT_UNLINKAT(handle,
568 /* FIXME: this is wrong: moving files with rename does not change the disk space
571 space_avail = SMB_VFS_NEXT_DISK_FREE(handle, ".", True, &bsize, &dfree, &dsize) * 1024L;
572 DEBUG(5, ("space_avail = %Lu, file_size = %Lu\n", space_avail, file_size));
573 if(space_avail < file_size) {
574 DEBUG(3, ("recycle: Not enough diskspace, purging file %s\n", file_name));
575 rc = SMB_VFS_NEXT_UNLINKAT(handle,
583 /* extract filename and path */
584 if (!parent_dirname(frame, full_fname->base_name, &path_name, &base)) {
590 /* original filename with path */
591 DEBUG(10, ("recycle: fname = %s\n", smb_fname_str_dbg(full_fname)));
593 DEBUG(10, ("recycle: fpath = %s\n", path_name));
594 /* filename without path */
595 DEBUG(10, ("recycle: base = %s\n", base));
597 if (matchparam(config->exclude, base)) {
598 DEBUG(3, ("recycle: file %s is excluded \n", base));
599 rc = SMB_VFS_NEXT_UNLINKAT(handle,
606 if (matchdirparam(config->exclude_dir, path_name)) {
607 DEBUG(3, ("recycle: directory %s is excluded \n", path_name));
608 rc = SMB_VFS_NEXT_UNLINKAT(handle,
615 if (config->keeptree) {
616 temp_name = talloc_asprintf(frame, "%s/%s",
619 if (temp_name == NULL) {
624 temp_name = config->repository;
627 exist = recycle_directory_exist(handle, temp_name);
629 DEBUG(10, ("recycle: Directory already exists\n"));
631 DEBUG(10, ("recycle: Creating directory %s\n", temp_name));
632 if (recycle_create_dir(handle,
634 config->directory_mode,
635 config->subdir_mode) == False)
637 DEBUG(3, ("recycle: Could not create directory, "
639 smb_fname_str_dbg(full_fname)));
640 rc = SMB_VFS_NEXT_UNLINKAT(handle,
648 final_name = talloc_asprintf(frame, "%s/%s",
650 if (final_name == NULL) {
655 /* Create smb_fname with final base name and orig stream name. */
656 smb_fname_final = synthetic_smb_fname(frame,
658 full_fname->stream_name,
662 if (smb_fname_final == NULL) {
663 rc = SMB_VFS_NEXT_UNLINKAT(handle,
670 /* new filename with path */
671 DEBUG(10, ("recycle: recycled file name: %s\n",
672 smb_fname_str_dbg(smb_fname_final)));
674 /* check if we should delete file from recycle bin */
675 if (recycle_file_exist(handle, smb_fname_final)) {
676 if (config->versions == False ||
677 matchparam(config->noversions, base) == True) {
678 DEBUG(3, ("recycle: Removing old file %s from recycle "
679 "bin\n", smb_fname_str_dbg(smb_fname_final)));
680 if (SMB_VFS_NEXT_UNLINKAT(handle,
681 dirfsp->conn->cwd_fsp,
684 DEBUG(1, ("recycle: Error deleting old file: %s\n", strerror(errno)));
689 /* rename file we move to recycle bin */
691 while (recycle_file_exist(handle, smb_fname_final)) {
694 TALLOC_FREE(smb_fname_final->base_name);
695 copy = talloc_asprintf(smb_fname_final, "%s/Copy #%d of %s",
696 temp_name, i++, base);
701 smb_fname_final->base_name = copy;
704 DEBUG(10, ("recycle: Moving %s to %s\n", smb_fname_str_dbg(full_fname),
705 smb_fname_str_dbg(smb_fname_final)));
706 rc = SMB_VFS_NEXT_RENAMEAT(handle,
709 handle->conn->cwd_fsp,
713 DEBUG(3, ("recycle: Move error %d (%s), purging file %s "
714 "(%s)\n", errno, strerror(errno),
715 smb_fname_str_dbg(full_fname),
716 smb_fname_str_dbg(smb_fname_final)));
717 rc = SMB_VFS_NEXT_UNLINKAT(handle,
724 /* touch access date of moved file */
725 if (config->touch || config->touch_mtime)
726 recycle_do_touch(handle, smb_fname_final, config->touch_mtime);
733 static int recycle_unlinkat(vfs_handle_struct *handle,
734 struct files_struct *dirfsp,
735 const struct smb_filename *smb_fname,
740 if (flags & AT_REMOVEDIR) {
741 ret = SMB_VFS_NEXT_UNLINKAT(handle,
746 ret = recycle_unlink_internal(handle,
754 static struct vfs_fn_pointers vfs_recycle_fns = {
755 .connect_fn = vfs_recycle_connect,
756 .unlinkat_fn = recycle_unlinkat,
760 NTSTATUS vfs_recycle_init(TALLOC_CTX *ctx)
762 NTSTATUS ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "recycle",
765 if (!NT_STATUS_IS_OK(ret))
768 vfs_recycle_debug_level = debug_add_class("recycle");
769 if (vfs_recycle_debug_level == -1) {
770 vfs_recycle_debug_level = DBGC_VFS;
771 DEBUG(0, ("vfs_recycle: Couldn't register custom debugging class!\n"));
773 DEBUG(10, ("vfs_recycle: Debug class number of 'recycle': %d\n", vfs_recycle_debug_level));