110acad75af25ce6f802081b33e5ed35f48f43e6
[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
30 #define ALLOC_CHECK(ptr, label) do { if ((ptr) == NULL) { DEBUG(0, ("recycle.bin: out of memory!\n")); errno = ENOMEM; goto label; } } while(0)
31
32 static int vfs_recycle_debug_level = DBGC_VFS;
33
34 #undef DBGC_CLASS
35 #define DBGC_CLASS vfs_recycle_debug_level
36  
37 static int recycle_connect(vfs_handle_struct *handle, const char *service, const char *user);
38 static void recycle_disconnect(vfs_handle_struct *handle);
39 static int recycle_unlink(vfs_handle_struct *handle,
40                           const struct smb_filename *smb_fname);
41
42 static int recycle_connect(vfs_handle_struct *handle, const char *service, const char *user)
43 {
44         int ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
45
46         if (ret < 0) {
47                 return ret;
48         }
49
50         DEBUG(10,("recycle_connect() connect to service[%s] as user[%s].\n",
51                 service,user));
52
53         return 0;
54 }
55
56 static void recycle_disconnect(vfs_handle_struct *handle)
57 {
58         DEBUG(10,("recycle_disconnect() connect to service[%s].\n",
59                 lp_servicename(SNUM(handle->conn))));
60
61         SMB_VFS_NEXT_DISCONNECT(handle);
62 }
63
64 static const char *recycle_repository(vfs_handle_struct *handle)
65 {
66         const char *tmp_str = NULL;
67
68         tmp_str = lp_parm_const_string(SNUM(handle->conn), "recycle", "repository",".recycle");
69
70         DEBUG(10, ("recycle: repository = %s\n", tmp_str));
71
72         return tmp_str;
73 }
74
75 static bool recycle_keep_dir_tree(vfs_handle_struct *handle)
76 {
77         bool ret;
78
79         ret = lp_parm_bool(SNUM(handle->conn), "recycle", "keeptree", False);
80
81         DEBUG(10, ("recycle_bin: keeptree = %s\n", ret?"True":"False"));
82
83         return ret;
84 }
85
86 static bool recycle_versions(vfs_handle_struct *handle)
87 {
88         bool ret;
89
90         ret = lp_parm_bool(SNUM(handle->conn), "recycle", "versions", False);
91
92         DEBUG(10, ("recycle: versions = %s\n", ret?"True":"False"));
93
94         return ret;
95 }
96
97 static bool recycle_touch(vfs_handle_struct *handle)
98 {
99         bool ret;
100
101         ret = lp_parm_bool(SNUM(handle->conn), "recycle", "touch", False);
102
103         DEBUG(10, ("recycle: touch = %s\n", ret?"True":"False"));
104
105         return ret;
106 }
107
108 static bool recycle_touch_mtime(vfs_handle_struct *handle)
109 {
110         bool ret;
111
112         ret = lp_parm_bool(SNUM(handle->conn), "recycle", "touch_mtime", False);
113
114         DEBUG(10, ("recycle: touch_mtime = %s\n", ret?"True":"False"));
115
116         return ret;
117 }
118
119 static const char **recycle_exclude(vfs_handle_struct *handle)
120 {
121         const char **tmp_lp;
122
123         tmp_lp = lp_parm_string_list(SNUM(handle->conn), "recycle", "exclude", NULL);
124
125         DEBUG(10, ("recycle: exclude = %s ...\n", tmp_lp?*tmp_lp:""));
126
127         return tmp_lp;
128 }
129
130 static const char **recycle_exclude_dir(vfs_handle_struct *handle)
131 {
132         const char **tmp_lp;
133
134         tmp_lp = lp_parm_string_list(SNUM(handle->conn), "recycle", "exclude_dir", NULL);
135
136         DEBUG(10, ("recycle: exclude_dir = %s ...\n", tmp_lp?*tmp_lp:""));
137
138         return tmp_lp;
139 }
140
141 static const char **recycle_noversions(vfs_handle_struct *handle)
142 {
143         const char **tmp_lp;
144
145         tmp_lp = lp_parm_string_list(SNUM(handle->conn), "recycle", "noversions", NULL);
146
147         DEBUG(10, ("recycle: noversions = %s\n", tmp_lp?*tmp_lp:""));
148
149         return tmp_lp;
150 }
151
152 static SMB_OFF_T recycle_maxsize(vfs_handle_struct *handle)
153 {
154         SMB_OFF_T maxsize;
155
156         maxsize = conv_str_size(lp_parm_const_string(SNUM(handle->conn),
157                                             "recycle", "maxsize", NULL));
158
159         DEBUG(10, ("recycle: maxsize = %lu\n", (long unsigned int)maxsize));
160
161         return maxsize;
162 }
163
164 static SMB_OFF_T recycle_minsize(vfs_handle_struct *handle)
165 {
166         SMB_OFF_T minsize;
167
168         minsize = conv_str_size(lp_parm_const_string(SNUM(handle->conn),
169                                             "recycle", "minsize", NULL));
170
171         DEBUG(10, ("recycle: minsize = %lu\n", (long unsigned int)minsize));
172
173         return minsize;
174 }
175
176 static mode_t recycle_directory_mode(vfs_handle_struct *handle)
177 {
178         int dirmode;
179         const char *buff;
180
181         buff = lp_parm_const_string(SNUM(handle->conn), "recycle", "directory_mode", NULL);
182
183         if (buff != NULL ) {
184                 sscanf(buff, "%o", &dirmode);
185         } else {
186                 dirmode=S_IRUSR | S_IWUSR | S_IXUSR;
187         }
188
189         DEBUG(10, ("recycle: directory_mode = %o\n", dirmode));
190         return (mode_t)dirmode;
191 }
192
193 static mode_t recycle_subdir_mode(vfs_handle_struct *handle)
194 {
195         int dirmode;
196         const char *buff;
197
198         buff = lp_parm_const_string(SNUM(handle->conn), "recycle", "subdir_mode", NULL);
199
200         if (buff != NULL ) {
201                 sscanf(buff, "%o", &dirmode);
202         } else {
203                 dirmode=recycle_directory_mode(handle);
204         }
205
206         DEBUG(10, ("recycle: subdir_mode = %o\n", dirmode));
207         return (mode_t)dirmode;
208 }
209
210 static bool recycle_directory_exist(vfs_handle_struct *handle, const char *dname)
211 {
212         SMB_STRUCT_STAT st;
213
214         if (vfs_stat_smb_fname(handle->conn, dname, &st) == 0) {
215                 if (S_ISDIR(st.st_ex_mode)) {
216                         return True;
217                 }
218         }
219
220         return False;
221 }
222
223 static bool recycle_file_exist(vfs_handle_struct *handle,
224                                const struct smb_filename *smb_fname)
225 {
226         struct smb_filename *smb_fname_tmp = NULL;
227         NTSTATUS status;
228         bool ret = false;
229
230         status = copy_smb_filename(talloc_tos(), smb_fname, &smb_fname_tmp);
231         if (!NT_STATUS_IS_OK(status)) {
232                 return false;
233         }
234
235         if (SMB_VFS_STAT(handle->conn, smb_fname_tmp) == 0) {
236                 if (S_ISREG(smb_fname_tmp->st.st_ex_mode)) {
237                         ret = true;
238                 }
239         }
240
241         TALLOC_FREE(smb_fname_tmp);
242         return ret;
243 }
244
245 /**
246  * Return file size
247  * @param conn connection
248  * @param fname file name
249  * @return size in bytes
250  **/
251 static SMB_OFF_T recycle_get_file_size(vfs_handle_struct *handle,
252                                        const struct smb_filename *smb_fname)
253 {
254         struct smb_filename *smb_fname_tmp = NULL;
255         NTSTATUS status;
256         SMB_OFF_T size;
257
258         status = copy_smb_filename(talloc_tos(), smb_fname, &smb_fname_tmp);
259         if (!NT_STATUS_IS_OK(status)) {
260                 size = (SMB_OFF_T)0;
261                 goto out;
262         }
263
264         if (SMB_VFS_STAT(handle->conn, smb_fname_tmp) != 0) {
265                 DEBUG(0,("recycle: stat for %s returned %s\n",
266                          smb_fname_str_dbg(smb_fname_tmp), strerror(errno)));
267                 size = (SMB_OFF_T)0;
268                 goto out;
269         }
270
271         size = smb_fname_tmp->st.st_ex_size;
272  out:
273         TALLOC_FREE(smb_fname_tmp);
274         return size;
275 }
276
277 /**
278  * Create directory tree
279  * @param conn connection
280  * @param dname Directory tree to be created
281  * @return Returns True for success
282  **/
283 static bool recycle_create_dir(vfs_handle_struct *handle, const char *dname)
284 {
285         size_t len;
286         mode_t mode;
287         char *new_dir = NULL;
288         char *tmp_str = NULL;
289         char *token;
290         char *tok_str;
291         bool ret = False;
292         char *saveptr;
293
294         mode = recycle_directory_mode(handle);
295
296         tmp_str = SMB_STRDUP(dname);
297         ALLOC_CHECK(tmp_str, done);
298         tok_str = tmp_str;
299
300         len = strlen(dname)+1;
301         new_dir = (char *)SMB_MALLOC(len + 1);
302         ALLOC_CHECK(new_dir, done);
303         *new_dir = '\0';
304         if (dname[0] == '/') {
305                 /* Absolute path. */
306                 safe_strcat(new_dir,"/",len);
307         }
308
309         /* Create directory tree if neccessary */
310         for(token = strtok_r(tok_str, "/", &saveptr); token;
311             token = strtok_r(NULL, "/", &saveptr)) {
312                 safe_strcat(new_dir, token, len);
313                 if (recycle_directory_exist(handle, new_dir))
314                         DEBUG(10, ("recycle: dir %s already exists\n", new_dir));
315                 else {
316                         DEBUG(5, ("recycle: creating new dir %s\n", new_dir));
317                         if (SMB_VFS_NEXT_MKDIR(handle, new_dir, mode) != 0) {
318                                 DEBUG(1,("recycle: mkdir failed for %s with error: %s\n", new_dir, strerror(errno)));
319                                 ret = False;
320                                 goto done;
321                         }
322                 }
323                 safe_strcat(new_dir, "/", len);
324                 mode = recycle_subdir_mode(handle);
325         }
326
327         ret = True;
328 done:
329         SAFE_FREE(tmp_str);
330         SAFE_FREE(new_dir);
331         return ret;
332 }
333
334 /**
335  * Check if any of the components of "exclude_list" are contained in path.
336  * Return True if found
337  **/
338
339 static bool matchdirparam(const char **dir_exclude_list, char *path)
340 {
341         char *startp = NULL, *endp = NULL;
342
343         if (dir_exclude_list == NULL || dir_exclude_list[0] == NULL ||
344                 *dir_exclude_list[0] == '\0' || path == NULL || *path == '\0') {
345                 return False;
346         }
347
348         /* 
349          * Walk the components of path, looking for matches with the
350          * exclude list on each component. 
351          */
352
353         for (startp = path; startp; startp = endp) {
354                 int i;
355
356                 while (*startp == '/') {
357                         startp++;
358                 }
359                 endp = strchr(startp, '/');
360                 if (endp) {
361                         *endp = '\0';
362                 }
363
364                 for(i=0; dir_exclude_list[i] ; i++) {
365                         if(unix_wild_match(dir_exclude_list[i], startp)) {
366                                 /* Repair path. */
367                                 if (endp) {
368                                         *endp = '/';
369                                 }
370                                 return True;
371                         }
372                 }
373
374                 /* Repair path. */
375                 if (endp) {
376                         *endp = '/';
377                 }
378         }
379
380         return False;
381 }
382
383 /**
384  * Check if needle is contained in haystack, * and ? patterns are resolved
385  * @param haystack list of parameters separated by delimimiter character
386  * @param needle string to be matched exectly to haystack including pattern matching
387  * @return True if found
388  **/
389 static bool matchparam(const char **haystack_list, const char *needle)
390 {
391         int i;
392
393         if (haystack_list == NULL || haystack_list[0] == NULL ||
394                 *haystack_list[0] == '\0' || needle == NULL || *needle == '\0') {
395                 return False;
396         }
397
398         for(i=0; haystack_list[i] ; i++) {
399                 if(unix_wild_match(haystack_list[i], needle)) {
400                         return True;
401                 }
402         }
403
404         return False;
405 }
406
407 /**
408  * Touch access or modify date
409  **/
410 static void recycle_do_touch(vfs_handle_struct *handle,
411                              const struct smb_filename *smb_fname,
412                              bool touch_mtime)
413 {
414         struct smb_filename *smb_fname_tmp = NULL;
415         struct smb_file_time ft;
416         NTSTATUS status;
417         int ret, err;
418
419         ZERO_STRUCT(ft);
420
421         status = copy_smb_filename(talloc_tos(), smb_fname, &smb_fname_tmp);
422         if (!NT_STATUS_IS_OK(status)) {
423                 return;
424         }
425
426         if (SMB_VFS_STAT(handle->conn, smb_fname_tmp) != 0) {
427                 DEBUG(0,("recycle: stat for %s returned %s\n",
428                          smb_fname_str_dbg(smb_fname_tmp), strerror(errno)));
429                 goto out;
430         }
431         /* atime */
432         ft.atime = timespec_current();
433         /* mtime */
434         ft.mtime = touch_mtime ? ft.atime : smb_fname_tmp->st.st_ex_mtime;
435
436         become_root();
437         ret = SMB_VFS_NEXT_NTIMES(handle, smb_fname_tmp, &ft);
438         err = errno;
439         unbecome_root();
440         if (ret == -1 ) {
441                 DEBUG(0, ("recycle: touching %s failed, reason = %s\n",
442                           smb_fname_str_dbg(smb_fname_tmp), strerror(err)));
443         }
444  out:
445         TALLOC_FREE(smb_fname_tmp);
446 }
447
448 /**
449  * Check if file should be recycled
450  **/
451 static int recycle_unlink(vfs_handle_struct *handle,
452     const struct smb_filename *smb_fname)
453 {
454         connection_struct *conn = handle->conn;
455         char *path_name = NULL;
456         char *temp_name = NULL;
457         char *final_name = NULL;
458         struct smb_filename *smb_fname_final = NULL;
459         const char *base;
460         char *repository = NULL;
461         int i = 1;
462         SMB_OFF_T maxsize, minsize;
463         SMB_OFF_T file_size; /* space_avail;    */
464         bool exist;
465         NTSTATUS status;
466         int rc = -1;
467
468         repository = talloc_sub_advanced(NULL, lp_servicename(SNUM(conn)),
469                                         conn->session_info->unix_name,
470                                         conn->connectpath,
471                                         conn->session_info->utok.gid,
472                                         conn->session_info->sanitized_username,
473                                         conn->session_info->info3->base.domain.string,
474                                         recycle_repository(handle));
475         ALLOC_CHECK(repository, done);
476         /* shouldn't we allow absolute path names here? --metze */
477         /* Yes :-). JRA. */
478         trim_char(repository, '\0', '/');
479
480         if(!repository || *(repository) == '\0') {
481                 DEBUG(3, ("recycle: repository path not set, purging %s...\n",
482                           smb_fname_str_dbg(smb_fname)));
483                 rc = SMB_VFS_NEXT_UNLINK(handle, smb_fname);
484                 goto done;
485         }
486
487         /* we don't recycle the recycle bin... */
488         if (strncmp(smb_fname->base_name, repository,
489                     strlen(repository)) == 0) {
490                 DEBUG(3, ("recycle: File is within recycling bin, unlinking ...\n"));
491                 rc = SMB_VFS_NEXT_UNLINK(handle, smb_fname);
492                 goto done;
493         }
494
495         file_size = recycle_get_file_size(handle, smb_fname);
496         /* it is wrong to purge filenames only because they are empty imho
497          *   --- simo
498          *
499         if(fsize == 0) {
500                 DEBUG(3, ("recycle: File %s is empty, purging...\n", file_name));
501                 rc = SMB_VFS_NEXT_UNLINK(handle,file_name);
502                 goto done;
503         }
504          */
505
506         /* FIXME: this is wrong, we should check the whole size of the recycle bin is
507          * not greater then maxsize, not the size of the single file, also it is better
508          * to remove older files
509          */
510         maxsize = recycle_maxsize(handle);
511         if(maxsize > 0 && file_size > maxsize) {
512                 DEBUG(3, ("recycle: File %s exceeds maximum recycle size, "
513                           "purging... \n", smb_fname_str_dbg(smb_fname)));
514                 rc = SMB_VFS_NEXT_UNLINK(handle, smb_fname);
515                 goto done;
516         }
517         minsize = recycle_minsize(handle);
518         if(minsize > 0 && file_size < minsize) {
519                 DEBUG(3, ("recycle: File %s lowers minimum recycle size, "
520                           "purging... \n", smb_fname_str_dbg(smb_fname)));
521                 rc = SMB_VFS_NEXT_UNLINK(handle, smb_fname);
522                 goto done;
523         }
524
525         /* FIXME: this is wrong: moving files with rename does not change the disk space
526          * allocation
527          *
528         space_avail = SMB_VFS_NEXT_DISK_FREE(handle, ".", True, &bsize, &dfree, &dsize) * 1024L;
529         DEBUG(5, ("space_avail = %Lu, file_size = %Lu\n", space_avail, file_size));
530         if(space_avail < file_size) {
531                 DEBUG(3, ("recycle: Not enough diskspace, purging file %s\n", file_name));
532                 rc = SMB_VFS_NEXT_UNLINK(handle, file_name);
533                 goto done;
534         }
535          */
536
537         /* extract filename and path */
538         base = strrchr(smb_fname->base_name, '/');
539         if (base == NULL) {
540                 base = smb_fname->base_name;
541                 path_name = SMB_STRDUP("/");
542                 ALLOC_CHECK(path_name, done);
543         }
544         else {
545                 path_name = SMB_STRDUP(smb_fname->base_name);
546                 ALLOC_CHECK(path_name, done);
547                 path_name[base - smb_fname->base_name] = '\0';
548                 base++;
549         }
550
551         /* original filename with path */
552         DEBUG(10, ("recycle: fname = %s\n", smb_fname_str_dbg(smb_fname)));
553         /* original path */
554         DEBUG(10, ("recycle: fpath = %s\n", path_name));
555         /* filename without path */
556         DEBUG(10, ("recycle: base = %s\n", base));
557
558         if (matchparam(recycle_exclude(handle), base)) {
559                 DEBUG(3, ("recycle: file %s is excluded \n", base));
560                 rc = SMB_VFS_NEXT_UNLINK(handle, smb_fname);
561                 goto done;
562         }
563
564         if (matchdirparam(recycle_exclude_dir(handle), path_name)) {
565                 DEBUG(3, ("recycle: directory %s is excluded \n", path_name));
566                 rc = SMB_VFS_NEXT_UNLINK(handle, smb_fname);
567                 goto done;
568         }
569
570         if (recycle_keep_dir_tree(handle) == True) {
571                 if (asprintf(&temp_name, "%s/%s", repository, path_name) == -1) {
572                         ALLOC_CHECK(temp_name, done);
573                 }
574         } else {
575                 temp_name = SMB_STRDUP(repository);
576         }
577         ALLOC_CHECK(temp_name, done);
578
579         exist = recycle_directory_exist(handle, temp_name);
580         if (exist) {
581                 DEBUG(10, ("recycle: Directory already exists\n"));
582         } else {
583                 DEBUG(10, ("recycle: Creating directory %s\n", temp_name));
584                 if (recycle_create_dir(handle, temp_name) == False) {
585                         DEBUG(3, ("recycle: Could not create directory, "
586                                   "purging %s...\n",
587                                   smb_fname_str_dbg(smb_fname)));
588                         rc = SMB_VFS_NEXT_UNLINK(handle, smb_fname);
589                         goto done;
590                 }
591         }
592
593         if (asprintf(&final_name, "%s/%s", temp_name, base) == -1) {
594                 ALLOC_CHECK(final_name, done);
595         }
596
597         /* Create smb_fname with final base name and orig stream name. */
598         status = create_synthetic_smb_fname(talloc_tos(), final_name,
599                                             smb_fname->stream_name, NULL,
600                                             &smb_fname_final);
601         if (!NT_STATUS_IS_OK(status)) {
602                 rc = SMB_VFS_NEXT_UNLINK(handle, smb_fname);
603                 goto done;
604         }
605
606         /* new filename with path */
607         DEBUG(10, ("recycle: recycled file name: %s\n",
608                    smb_fname_str_dbg(smb_fname_final)));
609
610         /* check if we should delete file from recycle bin */
611         if (recycle_file_exist(handle, smb_fname_final)) {
612                 if (recycle_versions(handle) == False || matchparam(recycle_noversions(handle), base) == True) {
613                         DEBUG(3, ("recycle: Removing old file %s from recycle "
614                                   "bin\n", smb_fname_str_dbg(smb_fname_final)));
615                         if (SMB_VFS_NEXT_UNLINK(handle,
616                                                 smb_fname_final) != 0) {
617                                 DEBUG(1, ("recycle: Error deleting old file: %s\n", strerror(errno)));
618                         }
619                 }
620         }
621
622         /* rename file we move to recycle bin */
623         i = 1;
624         while (recycle_file_exist(handle, smb_fname_final)) {
625                 SAFE_FREE(final_name);
626                 if (asprintf(&final_name, "%s/Copy #%d of %s", temp_name, i++, base) == -1) {
627                         ALLOC_CHECK(final_name, done);
628                 }
629                 TALLOC_FREE(smb_fname_final->base_name);
630                 smb_fname_final->base_name = talloc_strdup(smb_fname_final,
631                                                            final_name);
632                 if (smb_fname_final->base_name == NULL) {
633                         rc = SMB_VFS_NEXT_UNLINK(handle, smb_fname);
634                         goto done;
635                 }
636         }
637
638         DEBUG(10, ("recycle: Moving %s to %s\n", smb_fname_str_dbg(smb_fname),
639                 smb_fname_str_dbg(smb_fname_final)));
640         rc = SMB_VFS_NEXT_RENAME(handle, smb_fname, smb_fname_final);
641         if (rc != 0) {
642                 DEBUG(3, ("recycle: Move error %d (%s), purging file %s "
643                           "(%s)\n", errno, strerror(errno),
644                           smb_fname_str_dbg(smb_fname),
645                           smb_fname_str_dbg(smb_fname_final)));
646                 rc = SMB_VFS_NEXT_UNLINK(handle, smb_fname);
647                 goto done;
648         }
649
650         /* touch access date of moved file */
651         if (recycle_touch(handle) == True || recycle_touch_mtime(handle))
652                 recycle_do_touch(handle, smb_fname_final,
653                                  recycle_touch_mtime(handle));
654
655 done:
656         SAFE_FREE(path_name);
657         SAFE_FREE(temp_name);
658         SAFE_FREE(final_name);
659         TALLOC_FREE(smb_fname_final);
660         TALLOC_FREE(repository);
661         return rc;
662 }
663
664 static struct vfs_fn_pointers vfs_recycle_fns = {
665         .connect_fn = recycle_connect,
666         .disconnect = recycle_disconnect,
667         .unlink = recycle_unlink
668 };
669
670 NTSTATUS vfs_recycle_init(void);
671 NTSTATUS vfs_recycle_init(void)
672 {
673         NTSTATUS ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "recycle",
674                                         &vfs_recycle_fns);
675
676         if (!NT_STATUS_IS_OK(ret))
677                 return ret;
678
679         vfs_recycle_debug_level = debug_add_class("recycle");
680         if (vfs_recycle_debug_level == -1) {
681                 vfs_recycle_debug_level = DBGC_VFS;
682                 DEBUG(0, ("vfs_recycle: Couldn't register custom debugging class!\n"));
683         } else {
684                 DEBUG(10, ("vfs_recycle: Debug class number of 'recycle': %d\n", vfs_recycle_debug_level));
685         }
686
687         return ret;
688 }