9c0020a8dbe02b1a4da55d47cc4f45adc76e1487
[samba.git] / source3 / modules / vfs_recycle.c
1 /*
2  * Recycle bin VFS module for Samba.
3  *
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
10  *
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.
15  *
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.
20  *
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/>.
23  */
24
25 #include "includes.h"
26 #include "smbd/smbd.h"
27 #include "system/filesys.h"
28 #include "../librpc/gen_ndr/ndr_netlogon.h"
29 #include "auth.h"
30 #include "source3/lib/substitute.h"
31
32 #define ALLOC_CHECK(ptr, label) do { if ((ptr) == NULL) { DEBUG(0, ("recycle.bin: out of memory!\n")); errno = ENOMEM; goto label; } } while(0)
33
34 static int vfs_recycle_debug_level = DBGC_VFS;
35
36 #undef DBGC_CLASS
37 #define DBGC_CLASS vfs_recycle_debug_level
38
39 struct recycle_config_data {
40         const char *repository;
41         bool keeptree;
42         bool versions;
43         bool touch;
44         bool touch_mtime;
45         const char **exclude;
46         const char **exclude_dir;
47         const char **noversions;
48         mode_t directory_mode;
49         mode_t subdir_mode;
50         off_t minsize;
51         off_t maxsize;
52 };
53
54 static int vfs_recycle_connect(struct vfs_handle_struct *handle,
55                                const char *service,
56                                const char *user)
57 {
58         const struct loadparm_substitution *lp_sub =
59                 loadparm_s3_global_substitution();
60         struct recycle_config_data *config = NULL;
61         int ret;
62         int t;
63         const char *buff = NULL;
64         const char **tmplist = NULL;
65         char *repository = NULL;
66
67         ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
68         if (ret < 0) {
69                 return ret;
70         }
71
72         if (IS_IPC(handle->conn) || IS_PRINT(handle->conn)) {
73                 return 0;
74         }
75
76         config = talloc_zero(handle->conn, struct recycle_config_data);
77         if (config == NULL) {
78                 DBG_ERR("talloc_zero() failed\n");
79                 errno = ENOMEM;
80                 return -1;
81         }
82         buff = lp_parm_const_string(SNUM(handle->conn),
83                                     "recycle",
84                                     "repository",
85                                     ".recycle");
86         repository = talloc_sub_full(
87                 config,
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,
94                 buff);
95         if (repository == NULL) {
96                 DBG_ERR("talloc_sub_full() failed\n");
97                 TALLOC_FREE(config);
98                 errno = ENOMEM;
99                 return -1;
100         }
101         /* shouldn't we allow absolute path names here? --metze */
102         /* Yes :-). JRA. */
103         trim_char(repository, '\0', '/');
104         config->repository = repository;
105
106         config->keeptree = lp_parm_bool(SNUM(handle->conn),
107                                         "recycle",
108                                         "keeptree",
109                                         False);
110         config->versions = lp_parm_bool(SNUM(handle->conn),
111                                         "recycle",
112                                         "versions",
113                                         False);
114         config->touch = lp_parm_bool(SNUM(handle->conn),
115                                      "recycle",
116                                      "touch",
117                                      False);
118         config->touch_mtime = lp_parm_bool(SNUM(handle->conn),
119                                            "recycle",
120                                            "touch_mtime",
121                                            False);
122         tmplist = lp_parm_string_list(SNUM(handle->conn),
123                                       "recycle",
124                                       "exclude",
125                                       NULL);
126         if (tmplist != NULL) {
127                 char **tmpcpy = str_list_copy(config, tmplist);
128                 if (tmpcpy == NULL) {
129                         DBG_ERR("str_list_copy() failed\n");
130                         TALLOC_FREE(config);
131                         errno = ENOMEM;
132                         return -1;
133                 }
134                 config->exclude = discard_const_p(const char *, tmpcpy);
135         }
136         tmplist = lp_parm_string_list(SNUM(handle->conn),
137                                       "recycle",
138                                       "exclude_dir",
139                                       NULL);
140         if (tmplist != NULL) {
141                 char **tmpcpy = str_list_copy(config, tmplist);
142                 if (tmpcpy == NULL) {
143                         DBG_ERR("str_list_copy() failed\n");
144                         TALLOC_FREE(config);
145                         errno = ENOMEM;
146                         return -1;
147                 }
148                 config->exclude_dir = discard_const_p(const char *, tmpcpy);
149         }
150         tmplist = lp_parm_string_list(SNUM(handle->conn),
151                                       "recycle",
152                                       "noversions",
153                                       NULL);
154         if (tmplist != NULL) {
155                 char **tmpcpy = str_list_copy(config, tmplist);
156                 if (tmpcpy == NULL) {
157                         DBG_ERR("str_list_copy() failed\n");
158                         TALLOC_FREE(config);
159                         errno = ENOMEM;
160                         return -1;
161                 }
162                 config->noversions = discard_const_p(const char *, tmpcpy);
163         }
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));
168
169         buff = lp_parm_const_string(SNUM(handle->conn),
170                                     "recycle",
171                                     "directory_mode",
172                                     NULL);
173         if (buff != NULL ) {
174                 sscanf(buff, "%o", &t);
175         } else {
176                 t = S_IRUSR | S_IWUSR | S_IXUSR;
177         }
178         config->directory_mode = (mode_t)t;
179
180         buff = lp_parm_const_string(SNUM(handle->conn),
181                                     "recycle",
182                                     "subdir_mode",
183                                     NULL);
184         if (buff != NULL ) {
185                 sscanf(buff, "%o", &t);
186         } else {
187                 t = config->directory_mode;
188         }
189         config->subdir_mode = (mode_t)t;
190
191         SMB_VFS_HANDLE_SET_DATA(
192                 handle, config, NULL, struct recycle_config_data, return -1);
193         return 0;
194 }
195
196 static bool recycle_directory_exist(vfs_handle_struct *handle, const char *dname)
197 {
198         struct smb_filename smb_fname = {
199                         .base_name = discard_const_p(char, dname)
200         };
201
202         if (SMB_VFS_STAT(handle->conn, &smb_fname) == 0) {
203                 if (S_ISDIR(smb_fname.st.st_ex_mode)) {
204                         return True;
205                 }
206         }
207
208         return False;
209 }
210
211 static bool recycle_file_exist(vfs_handle_struct *handle,
212                                const struct smb_filename *smb_fname)
213 {
214         struct smb_filename *smb_fname_tmp = NULL;
215         bool ret = false;
216
217         smb_fname_tmp = cp_smb_filename(talloc_tos(), smb_fname);
218         if (smb_fname_tmp == NULL) {
219                 return false;
220         }
221
222         if (SMB_VFS_STAT(handle->conn, smb_fname_tmp) == 0) {
223                 if (S_ISREG(smb_fname_tmp->st.st_ex_mode)) {
224                         ret = true;
225                 }
226         }
227
228         TALLOC_FREE(smb_fname_tmp);
229         return ret;
230 }
231
232 /**
233  * Return file size
234  * @param conn connection
235  * @param fname file name
236  * @return size in bytes
237  **/
238 static off_t recycle_get_file_size(vfs_handle_struct *handle,
239                                        const struct smb_filename *smb_fname)
240 {
241         struct smb_filename *smb_fname_tmp = NULL;
242         off_t size;
243
244         smb_fname_tmp = cp_smb_filename(talloc_tos(), smb_fname);
245         if (smb_fname_tmp == NULL) {
246                 size = (off_t)0;
247                 goto out;
248         }
249
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));
253                 size = (off_t)0;
254                 goto out;
255         }
256
257         size = smb_fname_tmp->st.st_ex_size;
258  out:
259         TALLOC_FREE(smb_fname_tmp);
260         return size;
261 }
262
263 /**
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
270  **/
271 static bool recycle_create_dir(vfs_handle_struct *handle,
272                                const char *dname,
273                                mode_t dir_mode,
274                                mode_t subdir_mode)
275 {
276         size_t len;
277         mode_t mode = dir_mode;
278         char *new_dir = NULL;
279         char *tmp_str = NULL;
280         char *token;
281         char *tok_str;
282         bool ret = False;
283         char *saveptr;
284
285         tmp_str = SMB_STRDUP(dname);
286         ALLOC_CHECK(tmp_str, done);
287         tok_str = tmp_str;
288
289         len = strlen(dname)+1;
290         new_dir = (char *)SMB_MALLOC(len + 1);
291         ALLOC_CHECK(new_dir, done);
292         *new_dir = '\0';
293         if (dname[0] == '/') {
294                 /* Absolute path. */
295                 if (strlcat(new_dir,"/",len+1) >= len+1) {
296                         goto done;
297                 }
298         }
299
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) {
304                         goto done;
305                 }
306                 if (recycle_directory_exist(handle, new_dir))
307                         DEBUG(10, ("recycle: dir %s already exists\n", new_dir));
308                 else {
309                         struct smb_filename *smb_fname = NULL;
310                         int retval;
311
312                         DEBUG(5, ("recycle: creating new dir %s\n", new_dir));
313
314                         smb_fname = synthetic_smb_fname(talloc_tos(),
315                                                 new_dir,
316                                                 NULL,
317                                                 NULL,
318                                                 0,
319                                                 0);
320                         if (smb_fname == NULL) {
321                                 goto done;
322                         }
323
324                         retval = SMB_VFS_NEXT_MKDIRAT(handle,
325                                         handle->conn->cwd_fsp,
326                                         smb_fname,
327                                         mode);
328                         if (retval != 0) {
329                                 DBG_WARNING("recycle: mkdirat failed "
330                                         "for %s with error: %s\n",
331                                         new_dir,
332                                         strerror(errno));
333                                 TALLOC_FREE(smb_fname);
334                                 ret = False;
335                                 goto done;
336                         }
337                         TALLOC_FREE(smb_fname);
338                 }
339                 if (strlcat(new_dir, "/", len+1) >= len+1) {
340                         goto done;
341                 }
342                 mode = subdir_mode;
343         }
344
345         ret = True;
346 done:
347         SAFE_FREE(tmp_str);
348         SAFE_FREE(new_dir);
349         return ret;
350 }
351
352 /**
353  * Check if any of the components of "exclude_list" are contained in path.
354  * Return True if found
355  **/
356
357 static bool matchdirparam(const char **dir_exclude_list, char *path)
358 {
359         char *startp = NULL, *endp = NULL;
360
361         if (dir_exclude_list == NULL || dir_exclude_list[0] == NULL ||
362                 *dir_exclude_list[0] == '\0' || path == NULL || *path == '\0') {
363                 return False;
364         }
365
366         /* 
367          * Walk the components of path, looking for matches with the
368          * exclude list on each component. 
369          */
370
371         for (startp = path; startp; startp = endp) {
372                 int i;
373
374                 while (*startp == '/') {
375                         startp++;
376                 }
377                 endp = strchr(startp, '/');
378                 if (endp) {
379                         *endp = '\0';
380                 }
381
382                 for(i=0; dir_exclude_list[i] ; i++) {
383                         if(unix_wild_match(dir_exclude_list[i], startp)) {
384                                 /* Repair path. */
385                                 if (endp) {
386                                         *endp = '/';
387                                 }
388                                 return True;
389                         }
390                 }
391
392                 /* Repair path. */
393                 if (endp) {
394                         *endp = '/';
395                 }
396         }
397
398         return False;
399 }
400
401 /**
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
406  **/
407 static bool matchparam(const char **haystack_list, const char *needle)
408 {
409         int i;
410
411         if (haystack_list == NULL || haystack_list[0] == NULL ||
412                 *haystack_list[0] == '\0' || needle == NULL || *needle == '\0') {
413                 return False;
414         }
415
416         for(i=0; haystack_list[i] ; i++) {
417                 if(unix_wild_match(haystack_list[i], needle)) {
418                         return True;
419                 }
420         }
421
422         return False;
423 }
424
425 /**
426  * Touch access or modify date
427  **/
428 static void recycle_do_touch(vfs_handle_struct *handle,
429                              const struct smb_filename *smb_fname,
430                              bool touch_mtime)
431 {
432         struct smb_filename *smb_fname_tmp = NULL;
433         struct smb_file_time ft;
434         int ret, err;
435         NTSTATUS status;
436
437         init_smb_file_time(&ft);
438
439         status = synthetic_pathref(talloc_tos(),
440                                    handle->conn->cwd_fsp,
441                                    smb_fname->base_name,
442                                    smb_fname->stream_name,
443                                    NULL,
444                                    smb_fname->twrp,
445                                    smb_fname->flags,
446                                    &smb_fname_tmp);
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));
450                 return;
451         }
452
453         /* atime */
454         ft.atime = timespec_current();
455         /* mtime */
456         ft.mtime = touch_mtime ? ft.atime : smb_fname_tmp->st.st_ex_mtime;
457
458         become_root();
459         ret = SMB_VFS_NEXT_FNTIMES(handle, smb_fname_tmp->fsp, &ft);
460         err = errno;
461         unbecome_root();
462         if (ret == -1 ) {
463                 DEBUG(0, ("recycle: touching %s failed, reason = %s\n",
464                           smb_fname_str_dbg(smb_fname_tmp), strerror(err)));
465         }
466
467         TALLOC_FREE(smb_fname_tmp);
468 }
469
470 /**
471  * Check if file should be recycled
472  **/
473 static int recycle_unlink_internal(vfs_handle_struct *handle,
474                                 struct files_struct *dirfsp,
475                                 const struct smb_filename *smb_fname,
476                                 int flags)
477 {
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;
485         int i = 1;
486         off_t file_size; /* space_avail;        */
487         bool exist;
488         int rc = -1;
489         struct recycle_config_data *config = NULL;
490         struct vfs_rename_how rhow = { .flags = 0, };
491
492         SMB_VFS_HANDLE_GET_DATA(handle,
493                                 config,
494                                 struct recycle_config_data,
495                                 return -1);
496
497         frame = talloc_stackframe();
498
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,
503                                         dirfsp,
504                                         smb_fname,
505                                         flags);
506                 goto done;
507         }
508
509         full_fname = full_path_from_dirfsp_atname(frame,
510                                                   dirfsp,
511                                                   smb_fname);
512         if (full_fname == NULL) {
513                 rc = -1;
514                 errno = ENOMEM;
515                 goto done;
516         }
517
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,
523                                         dirfsp,
524                                         smb_fname,
525                                         flags);
526                 goto done;
527         }
528
529         file_size = recycle_get_file_size(handle, full_fname);
530         /* it is wrong to purge filenames only because they are empty imho
531          *   --- simo
532          *
533         if(fsize == 0) {
534                 DEBUG(3, ("recycle: File %s is empty, purging...\n", file_name));
535                 rc = SMB_VFS_NEXT_UNLINKAT(handle,
536                                         dirfsp,
537                                         file_name,
538                                         flags);
539                 goto done;
540         }
541          */
542
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
546          */
547         if (config->maxsize > 0 && file_size > config->maxsize) {
548                 DBG_NOTICE("File %s exceeds maximum recycle size, "
549                            "purging... \n",
550                            smb_fname_str_dbg(full_fname));
551                 rc = SMB_VFS_NEXT_UNLINKAT(handle,
552                                         dirfsp,
553                                         smb_fname,
554                                         flags);
555                 goto done;
556         }
557         if (config->minsize > 0 && file_size < config->minsize) {
558                 DBG_NOTICE("File %s lowers minimum recycle size, "
559                            "purging... \n",
560                            smb_fname_str_dbg(full_fname));
561                 rc = SMB_VFS_NEXT_UNLINKAT(handle,
562                                         dirfsp,
563                                         smb_fname,
564                                         flags);
565                 goto done;
566         }
567
568         /* FIXME: this is wrong: moving files with rename does not change the disk space
569          * allocation
570          *
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,
576                                         dirfsp,
577                                         file_name,
578                                         flags);
579                 goto done;
580         }
581          */
582
583         /* extract filename and path */
584         if (!parent_dirname(frame, full_fname->base_name, &path_name, &base)) {
585                 rc = -1;
586                 errno = ENOMEM;
587                 goto done;
588         }
589
590         /* original filename with path */
591         DEBUG(10, ("recycle: fname = %s\n", smb_fname_str_dbg(full_fname)));
592         /* original path */
593         DEBUG(10, ("recycle: fpath = %s\n", path_name));
594         /* filename without path */
595         DEBUG(10, ("recycle: base = %s\n", base));
596
597         if (matchparam(config->exclude, base)) {
598                 DEBUG(3, ("recycle: file %s is excluded \n", base));
599                 rc = SMB_VFS_NEXT_UNLINKAT(handle,
600                                         dirfsp,
601                                         smb_fname,
602                                         flags);
603                 goto done;
604         }
605
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,
609                                         dirfsp,
610                                         smb_fname,
611                                         flags);
612                 goto done;
613         }
614
615         if (config->keeptree) {
616                 temp_name = talloc_asprintf(frame, "%s/%s",
617                                             config->repository,
618                                             path_name);
619                 if (temp_name == NULL) {
620                         rc = -1;
621                         goto done;
622                 }
623         } else {
624                 temp_name = config->repository;
625         }
626
627         exist = recycle_directory_exist(handle, temp_name);
628         if (exist) {
629                 DEBUG(10, ("recycle: Directory already exists\n"));
630         } else {
631                 DEBUG(10, ("recycle: Creating directory %s\n", temp_name));
632                 if (recycle_create_dir(handle,
633                                        temp_name,
634                                        config->directory_mode,
635                                        config->subdir_mode) == False)
636                 {
637                         DEBUG(3, ("recycle: Could not create directory, "
638                                   "purging %s...\n",
639                                   smb_fname_str_dbg(full_fname)));
640                         rc = SMB_VFS_NEXT_UNLINKAT(handle,
641                                         dirfsp,
642                                         smb_fname,
643                                         flags);
644                         goto done;
645                 }
646         }
647
648         final_name = talloc_asprintf(frame, "%s/%s",
649                                      temp_name, base);
650         if (final_name == NULL) {
651                 rc = -1;
652                 goto done;
653         }
654
655         /* Create smb_fname with final base name and orig stream name. */
656         smb_fname_final = synthetic_smb_fname(frame,
657                                         final_name,
658                                         full_fname->stream_name,
659                                         NULL,
660                                         full_fname->twrp,
661                                         full_fname->flags);
662         if (smb_fname_final == NULL) {
663                 rc = SMB_VFS_NEXT_UNLINKAT(handle,
664                                         dirfsp,
665                                         smb_fname,
666                                         flags);
667                 goto done;
668         }
669
670         /* new filename with path */
671         DEBUG(10, ("recycle: recycled file name: %s\n",
672                    smb_fname_str_dbg(smb_fname_final)));
673
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,
682                                                 smb_fname_final,
683                                                 flags) != 0) {
684                                 DEBUG(1, ("recycle: Error deleting old file: %s\n", strerror(errno)));
685                         }
686                 }
687         }
688
689         /* rename file we move to recycle bin */
690         i = 1;
691         while (recycle_file_exist(handle, smb_fname_final)) {
692                 char *copy = NULL;
693
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);
697                 if (copy == NULL) {
698                         rc = -1;
699                         goto done;
700                 }
701                 smb_fname_final->base_name = copy;
702         }
703
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,
707                         dirfsp,
708                         smb_fname,
709                         handle->conn->cwd_fsp,
710                         smb_fname_final,
711                         &rhow);
712         if (rc != 0) {
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,
718                                 dirfsp,
719                                 smb_fname,
720                                 flags);
721                 goto done;
722         }
723
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);
727
728 done:
729         TALLOC_FREE(frame);
730         return rc;
731 }
732
733 static int recycle_unlinkat(vfs_handle_struct *handle,
734                 struct files_struct *dirfsp,
735                 const struct smb_filename *smb_fname,
736                 int flags)
737 {
738         int ret;
739
740         if (flags & AT_REMOVEDIR) {
741                 ret = SMB_VFS_NEXT_UNLINKAT(handle,
742                                         dirfsp,
743                                         smb_fname,
744                                         flags);
745         } else {
746                 ret = recycle_unlink_internal(handle,
747                                         dirfsp,
748                                         smb_fname,
749                                         flags);
750         }
751         return ret;
752 }
753
754 static struct vfs_fn_pointers vfs_recycle_fns = {
755         .connect_fn = vfs_recycle_connect,
756         .unlinkat_fn = recycle_unlinkat,
757 };
758
759 static_decl_vfs;
760 NTSTATUS vfs_recycle_init(TALLOC_CTX *ctx)
761 {
762         NTSTATUS ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "recycle",
763                                         &vfs_recycle_fns);
764
765         if (!NT_STATUS_IS_OK(ret))
766                 return ret;
767
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"));
772         } else {
773                 DEBUG(10, ("vfs_recycle: Debug class number of 'recycle': %d\n", vfs_recycle_debug_level));
774         }
775
776         return ret;
777 }