fix wrong string handling
[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  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23  */
24
25 #include "includes.h"
26
27 #define ALLOC_CHECK(ptr, label) do { if ((ptr) == NULL) { DEBUG(0, ("recycle.bin: out of memory!\n")); errno = ENOMEM; goto label; } } while(0)
28
29 static int vfs_recycle_debug_level = DBGC_VFS;
30
31 #undef DBGC_CLASS
32 #define DBGC_CLASS vfs_recycle_debug_level
33
34 static const char *delimiter = "|";             /* delimiter for options */
35
36 /* One per connection */
37
38 typedef struct recycle_bin_struct
39 {
40         TALLOC_CTX *mem_ctx;
41         char    *repository;            /* name of the recycle bin directory */
42         BOOL    keep_dir_tree;          /* keep directory structure of deleted file in recycle bin */
43         BOOL    versions;               /* create versions of deleted files with identical name */
44         BOOL    touch;                  /* touch access date of deleted file */
45         char    *exclude;               /* which files to exclude */
46         char    *exclude_dir;           /* which directories to exclude */
47         char    *noversions;            /* which files to exclude from versioning */
48         SMB_OFF_T maxsize;              /* maximum file size to be saved */
49 } recycle_bin_struct;
50
51 typedef struct recycle_bin_connections {
52         int conn;
53         recycle_bin_struct *data;
54         struct recycle_bin_connections *next;
55 } recycle_bin_connections;
56
57 typedef struct recycle_bin_private_data {
58         TALLOC_CTX *mem_ctx;
59         recycle_bin_connections *conns;
60 } recycle_bin_private_data;
61
62 struct smb_vfs_handle_struct *recycle_bin_private_handle;
63
64 /* VFS operations */
65 static struct vfs_ops default_vfs_ops;   /* For passthrough operation */
66
67 static int recycle_connect(struct connection_struct *conn, const char *service, const char *user);
68 static void recycle_disconnect(struct connection_struct *conn);
69 static int recycle_unlink(connection_struct *, const char *);
70
71 #define VFS_OP(x) ((void *) x)
72
73 static vfs_op_tuple recycle_ops[] = {
74
75         /* Disk operations */
76         {VFS_OP(recycle_connect),       SMB_VFS_OP_CONNECT,     SMB_VFS_LAYER_TRANSPARENT},
77         {VFS_OP(recycle_disconnect),    SMB_VFS_OP_DISCONNECT,  SMB_VFS_LAYER_TRANSPARENT},
78
79         /* File operations */
80         {VFS_OP(recycle_unlink),        SMB_VFS_OP_UNLINK,      SMB_VFS_LAYER_TRANSPARENT},
81
82         {NULL,                          SMB_VFS_OP_NOOP,        SMB_VFS_LAYER_NOOP}
83 };
84
85 /**
86  * VFS initialisation function.
87  *
88  * @retval initialised vfs_op_tuple array
89  **/
90 static vfs_op_tuple *recycle_init(const struct vfs_ops *def_vfs_ops,
91                         struct smb_vfs_handle_struct *vfs_handle)
92 {
93         TALLOC_CTX *mem_ctx = NULL;
94
95         DEBUG(10, ("Initializing VFS module recycle\n"));
96         memcpy(&default_vfs_ops, def_vfs_ops, sizeof(struct vfs_ops));
97         vfs_recycle_debug_level = debug_add_class("vfs_recycle_bin");
98         if (vfs_recycle_debug_level == -1) {
99                 vfs_recycle_debug_level = DBGC_VFS;
100                 DEBUG(0, ("vfs_recycle: Couldn't register custom debugging class!\n"));
101         } else {
102                 DEBUG(0, ("vfs_recycle: Debug class number of 'vfs_recycle': %d\n", vfs_recycle_debug_level));
103         }
104
105         recycle_bin_private_handle = vfs_handle;
106         if (!(mem_ctx = talloc_init("recycle bin data"))) {
107                 DEBUG(0, ("Failed to allocate memory in VFS module recycle_bin\n"));
108                 return NULL;
109         }
110
111         recycle_bin_private_handle->data = talloc(mem_ctx, sizeof(recycle_bin_private_data));
112         if (recycle_bin_private_handle->data == NULL) {
113                 DEBUG(0, ("Failed to allocate memory in VFS module recycle_bin\n"));
114                 return NULL;
115         }
116         ((recycle_bin_private_data *)(recycle_bin_private_handle->data))->mem_ctx = mem_ctx;
117         ((recycle_bin_private_data *)(recycle_bin_private_handle->data))->conns = NULL;
118
119         return recycle_ops;
120 }
121
122 static int recycle_connect(struct connection_struct *conn, const char *service, const char *user)
123 {
124         TALLOC_CTX *ctx = NULL;
125         recycle_bin_struct *recbin;
126         recycle_bin_connections *recconn;
127         recycle_bin_connections *recconnbase;
128         recycle_bin_private_data *recdata;
129         char *tmp_str;
130
131         DEBUG(10, ("Called for service %s (%d) as user %s\n", service, SNUM(conn), user));
132
133         if (recycle_bin_private_handle)
134                 recdata = (recycle_bin_private_data *)(recycle_bin_private_handle->data);
135         else {
136                 DEBUG(0, ("Recycle bin not initialized!\n"));
137                 return -1;
138         }
139
140         if (!(ctx = talloc_init("recycle bin connection"))) {
141                 DEBUG(0, ("Failed to allocate memory in VFS module recycle_bin\n"));
142                 return -1;
143         }
144
145         recbin = talloc(ctx, sizeof(recycle_bin_struct));
146         if (recbin == NULL) {
147                 DEBUG(0, ("Failed to allocate memory in VFS module recycle_bin\n"));
148                 return -1;
149         }
150         recbin->mem_ctx = ctx;
151
152         /* Set defaults */
153         recbin->repository = talloc_strdup(recbin->mem_ctx, ".recycle");
154         ALLOC_CHECK(recbin->repository, error);
155         recbin->keep_dir_tree = False;
156         recbin->versions = False;
157         recbin->touch = False;
158         recbin->exclude = "";
159         recbin->exclude_dir = "";
160         recbin->noversions = "";
161         recbin->maxsize = 0;
162
163         /* parse configuration options */
164         if ((tmp_str = lp_parm_string(SNUM(conn), "vfs_recycle_bin", "repository")) != NULL) {
165                 recbin->repository = talloc_sub_conn(recbin->mem_ctx, conn, tmp_str);
166                 ALLOC_CHECK(recbin->repository, error);
167                 trim_string(recbin->repository, "/", "/");
168                 DEBUG(5, ("recycle.bin: repository = %s\n", recbin->repository));
169         }
170         
171         recbin->keep_dir_tree = lp_parm_bool(SNUM(conn), "vfs_recycle_bin", "keeptree");
172         DEBUG(5, ("recycle.bin: keeptree = %d\n", recbin->keep_dir_tree));
173         
174         recbin->versions = lp_parm_bool(SNUM(conn), "vfs_recycle_bin", "versions");
175         DEBUG(5, ("recycle.bin: versions = %d\n", recbin->versions));
176         
177         recbin->touch = lp_parm_bool(SNUM(conn), "vfs_recycle_bin", "touch");
178         DEBUG(5, ("recycle.bin: touch = %d\n", recbin->touch));
179
180         recbin->maxsize = lp_parm_ulong(SNUM(conn), "vfs_recycle_bin", "maxsize");
181         if (recbin->maxsize == 0) {
182                 recbin->maxsize = -1;
183                 DEBUG(5, ("recycle.bin: maxsize = -infinite-\n"));
184         } else {
185                 DEBUG(5, ("recycle.bin: maxsize = %ld\n", (long int)recbin->maxsize));
186         }
187
188         if ((tmp_str = lp_parm_string(SNUM(conn), "vfs_recycle_bin", "exclude")) != NULL) {
189                 recbin->exclude = talloc_strdup(recbin->mem_ctx, tmp_str);
190                 ALLOC_CHECK(recbin->exclude, error);
191                 DEBUG(5, ("recycle.bin: exclude = %s\n", recbin->exclude));
192         }
193         if ((tmp_str = lp_parm_string(SNUM(conn), "vfs_recycle_bin", "exclude_dir")) != NULL) {
194                 recbin->exclude_dir = talloc_strdup(recbin->mem_ctx, tmp_str);
195                 ALLOC_CHECK(recbin->exclude_dir, error);
196                 DEBUG(5, ("recycle.bin: exclude_dir = %s\n", recbin->exclude_dir));
197         }
198         if ((tmp_str = lp_parm_string(SNUM(conn), "vfs_recycle_bin", "noversions")) != NULL) {
199                 recbin->noversions = talloc_strdup(recbin->mem_ctx, tmp_str);
200                 ALLOC_CHECK(recbin->noversions, error);
201                 DEBUG(5, ("recycle.bin: noversions = %s\n", recbin->noversions));
202         }
203
204         recconn = talloc(recdata->mem_ctx, sizeof(recycle_bin_connections));
205         if (recconn == NULL) {
206                 DEBUG(0, ("Failed to allocate memory in VFS module recycle_bin\n"));
207                 goto error;
208         }
209         recconn->conn = SNUM(conn);
210         recconn->data = recbin;
211         recconn->next = NULL;
212         if (recdata->conns) {
213                 recconnbase = recdata->conns;
214                 while (recconnbase->next != NULL) recconnbase = recconnbase->next;
215                 recconnbase->next = recconn;
216         } else {
217                 recdata->conns = recconn;
218         }
219         return default_vfs_ops.connect(conn, service, user);
220
221 error:
222         talloc_destroy(ctx);
223         return -1;
224 }
225
226 static void recycle_disconnect(struct connection_struct *conn)
227 {
228         recycle_bin_private_data *recdata;
229         recycle_bin_connections *recconn;
230
231         DEBUG(10, ("Disconnecting VFS module recycle bin\n"));
232
233         if (recycle_bin_private_handle)
234                 recdata = (recycle_bin_private_data *)(recycle_bin_private_handle->data);
235         else {
236                 DEBUG(0, ("Recycle bin not initialized!\n"));
237                 return;
238         }
239
240         if (recdata) {
241                 if (recdata->conns) {
242                         if (recdata->conns->conn == SNUM(conn)) {
243                                 talloc_destroy(recdata->conns->data->mem_ctx);
244                                 recdata->conns = recdata->conns->next;
245                         } else {
246                                 recconn = recdata->conns;
247                                 while (recconn->next) {
248                                         if (recconn->next->conn == SNUM(conn)) {
249                                                 talloc_destroy(recconn->next->data->mem_ctx);
250                                                 recconn->next = recconn->next->next;
251                                                 break;
252                                         }
253                                         recconn = recconn->next;
254                                 }
255                         }
256                 }
257         }
258         default_vfs_ops.disconnect(conn);
259 }
260
261 static BOOL recycle_directory_exist(connection_struct *conn, const char *dname)
262 {
263         SMB_STRUCT_STAT st;
264
265         if (default_vfs_ops.stat(conn, dname, &st) == 0) {
266                 if (S_ISDIR(st.st_mode)) {
267                         return True;
268                 }
269         }
270
271         return False;
272 }
273
274 static BOOL recycle_file_exist(connection_struct *conn, const char *fname)
275 {
276         SMB_STRUCT_STAT st;
277
278         if (default_vfs_ops.stat(conn, fname, &st) == 0) {
279                 if (S_ISREG(st.st_mode)) {
280                         return True;
281                 }
282         }
283
284         return False;
285 }
286
287 /**
288  * Return file size
289  * @param conn connection
290  * @param fname file name
291  * @return size in bytes
292  **/
293 static SMB_OFF_T recycle_get_file_size(connection_struct *conn, const char *fname)
294 {
295         SMB_STRUCT_STAT st;
296         if (default_vfs_ops.stat(conn, fname, &st) != 0) {
297                 DEBUG(0,("recycle.bin: stat for %s returned %s\n", fname, strerror(errno)));
298                 return (SMB_OFF_T)0;
299         }
300         return(st.st_size);
301 }
302
303 /**
304  * Create directory tree
305  * @param conn connection
306  * @param dname Directory tree to be created
307  * @return Returns True for success
308  **/
309 static BOOL recycle_create_dir(connection_struct *conn, const char *dname)
310 {
311         int len;
312         mode_t mode;
313         char *new_dir = NULL;
314         char *tmp_str = NULL;
315         char *token;
316         char *tok_str;
317         BOOL ret = False;
318
319         mode = S_IREAD | S_IWRITE | S_IEXEC;
320
321         tmp_str = strdup(dname);
322         ALLOC_CHECK(tmp_str, done);
323         tok_str = tmp_str;
324
325         len = strlen(dname);
326         new_dir = (char *)malloc(len + 1);
327         ALLOC_CHECK(new_dir, done);
328         *new_dir = '\0';
329
330         /* Create directory tree if neccessary */
331         for(token = strtok(tok_str, "/"); token; token = strtok(NULL, "/")) {
332                 safe_strcat(new_dir, token, len);
333                 if (recycle_directory_exist(conn, new_dir))
334                         DEBUG(10, ("recycle.bin: dir %s already exists\n", new_dir));
335                 else {
336                         DEBUG(5, ("recycle.bin: creating new dir %s\n", new_dir));
337                         if (default_vfs_ops.mkdir(conn, new_dir, mode) != 0) {
338                                 DEBUG(1,("recycle.bin: mkdir failed for %s with error: %s\n", new_dir, strerror(errno)));
339                                 ret = False;
340                                 goto done;
341                         }
342                 }
343                 safe_strcat(new_dir, "/", len);
344                 }
345
346         ret = True;
347 done:
348         SAFE_FREE(tmp_str);
349         SAFE_FREE(new_dir);
350         return ret;
351 }
352
353 /**
354  * Check if needle is contained exactly in haystack
355  * @param haystack list of parameters separated by delimimiter character
356  * @param needle string to be matched exactly to haystack
357  * @return True if found
358  **/
359 static BOOL checkparam(const char *haystack, const char *needle)
360 {
361         char *token;
362         char *tok_str;
363         char *tmp_str;
364         BOOL ret = False;
365
366         if (haystack == NULL || strlen(haystack) == 0 || needle == NULL || strlen(needle) == 0) {
367                 return False;
368         }
369
370         tmp_str = strdup(haystack);
371         ALLOC_CHECK(tmp_str, done);
372         token = tok_str = tmp_str;
373
374         for(token = strtok(tok_str, delimiter); token; token = strtok(NULL, delimiter)) {
375                 if(strcmp(token, needle) == 0) {
376                         ret = True;
377                         goto done;
378                 }
379         }
380 done:
381         SAFE_FREE(tmp_str);
382         return ret;
383 }
384
385 /**
386  * Check if needle is contained in haystack, * and ? patterns are resolved
387  * @param haystack list of parameters separated by delimimiter character
388  * @param needle string to be matched exectly to haystack including pattern matching
389  * @return True if found
390  **/
391 static BOOL matchparam(const char *haystack, const char *needle)
392 {
393         char *token;
394         char *tok_str;
395         char *tmp_str;
396         BOOL ret = False;
397
398         if (haystack == NULL || strlen(haystack) == 0 || needle == NULL || strlen(needle) == 0) {
399                 return False;
400         }
401
402         tmp_str = strdup(haystack);
403         ALLOC_CHECK(tmp_str, done);
404         token = tok_str = tmp_str;
405
406         for(token = strtok(tok_str, delimiter); token; token = strtok(NULL, delimiter)) {
407                 if (!unix_wild_match(token, needle)) {
408                         ret = True;
409                         goto done;
410                 }
411         }
412 done:
413         SAFE_FREE(tmp_str);
414         return ret;
415 }
416
417 /**
418  * Touch access date
419  **/
420 static void recycle_touch(connection_struct *conn, const char *fname)
421 {
422         SMB_STRUCT_STAT st;
423         struct utimbuf tb;
424         time_t currtime;
425
426         if (default_vfs_ops.stat(conn, fname, &st) != 0) {
427                 DEBUG(0,("recycle.bin: stat for %s returned %s\n", fname, strerror(errno)));
428                 return;
429         }
430         currtime = time(&currtime);
431         tb.actime = currtime;
432         tb.modtime = st.st_mtime;
433
434         if (default_vfs_ops.utime(conn, fname, &tb) == -1 )
435                 DEBUG(0, ("recycle.bin: touching %s failed, reason = %s\n", fname, strerror(errno)));
436         }
437
438 /**
439  * Check if file should be recycled
440  **/
441 static int recycle_unlink(connection_struct *conn, const char *file_name)
442 {
443         recycle_bin_private_data *recdata;
444         recycle_bin_connections *recconn;
445         recycle_bin_struct *recbin;
446         char *path_name = NULL;
447         char *temp_name = NULL;
448         char *final_name = NULL;
449         const char *base;
450         int i;
451 /*      SMB_BIG_UINT dfree, dsize, bsize;       */
452         SMB_OFF_T file_size; /* space_avail;    */
453         BOOL exist;
454         int rc = -1;
455
456         recbin = NULL;
457         if (recycle_bin_private_handle) {
458                 recdata = (recycle_bin_private_data *)(recycle_bin_private_handle->data);
459                 if (recdata) {
460                         if (recdata->conns) {
461                                 recconn = recdata->conns;
462                                 while (recconn && recconn->conn != SNUM(conn)) recconn = recconn->next;
463                                 if (recconn != NULL) {
464                                         recbin = recconn->data;
465                                 }
466                         }
467                 }
468         }
469         if (recbin == NULL) {
470                 DEBUG(0, ("Recycle bin not initialized!\n"));
471                 rc = default_vfs_ops.unlink(conn, file_name);
472                 goto done;
473         }
474
475         if(!recbin->repository || *(recbin->repository) == '\0') {
476                 DEBUG(3, ("Recycle path not set, purging %s...\n", file_name));
477                 rc = default_vfs_ops.unlink(conn, file_name);
478                 goto done;
479         }
480
481         /* we don't recycle the recycle bin... */
482         if (strncmp(file_name, recbin->repository, strlen(recbin->repository)) == 0) {
483                 DEBUG(3, ("File is within recycling bin, unlinking ...\n"));
484                 rc = default_vfs_ops.unlink(conn, file_name);
485                 goto done;
486         }
487
488         file_size = recycle_get_file_size(conn, file_name);
489         /* it is wrong to purge filenames only because they are empty imho
490          *   --- simo
491          *
492         if(fsize == 0) {
493                 DEBUG(3, ("File %s is empty, purging...\n", file_name));
494                 rc = default_vfs_ops.unlink(conn,file_name);
495                 goto done;
496         }
497          */
498
499         /* FIXME: this is wrong, we should check the hole size of the recycle bin is
500          * not greater then maxsize, not the size of the single file, also it is better
501          * to remove older files
502          */
503         if(recbin->maxsize > 0 && file_size > recbin->maxsize) {
504                 DEBUG(3, ("File %s exceeds maximum recycle size, purging... \n", file_name));
505                 rc = default_vfs_ops.unlink(conn, file_name);
506                 goto done;
507         }
508
509         /* FIXME: this is wrong: moving files with rename does not change the disk space
510          * allocation
511          *
512         space_avail = default_vfs_ops.disk_free(conn, ".", True, &bsize, &dfree, &dsize) * 1024L;
513         DEBUG(5, ("space_avail = %Lu, file_size = %Lu\n", space_avail, file_size));
514         if(space_avail < file_size) {
515                 DEBUG(3, ("Not enough diskspace, purging file %s\n", file_name));
516                 rc = default_vfs_ops.unlink(conn, file_name);
517                 goto done;
518         }
519          */
520
521         /* extract filename and path */
522         path_name = (char *)malloc(PATH_MAX);
523         ALLOC_CHECK(path_name, done);
524         *path_name = '\0';
525         safe_strcpy(path_name, file_name, PATH_MAX - 1);
526         base = strrchr(path_name, '/');
527         if (base == NULL) {
528                 base = file_name;
529                 safe_strcpy(path_name, "/", PATH_MAX - 1);
530         }
531         else {
532                 base++;
533         }
534
535         DEBUG(10, ("recycle.bin: fname = %s\n", file_name));    /* original filename with path */
536         DEBUG(10, ("recycle.bin: fpath = %s\n", path_name));    /* original path */
537         DEBUG(10, ("recycle.bin: base = %s\n", base));          /* filename without path */
538
539         if (matchparam(recbin->exclude, base)) {
540                 DEBUG(3, ("recycle.bin: file %s is excluded \n", base));
541                 rc = default_vfs_ops.unlink(conn, file_name);
542                 goto done;
543         }
544
545         /* FIXME: this check will fail if we have more than one level of directories,
546          * we shoud check for every level 1, 1/2, 1/2/3, 1/2/3/4 .... 
547          *      ---simo
548          */
549         if (checkparam(recbin->exclude_dir, path_name)) {
550                 DEBUG(3, ("recycle.bin: directory %s is excluded \n", path_name));
551                 rc = default_vfs_ops.unlink(conn, file_name);
552                 goto done;
553         }
554
555         temp_name = (char *)malloc(PATH_MAX);
556         ALLOC_CHECK(temp_name, done);
557         safe_strcpy(temp_name, recbin->repository, PATH_MAX - 1);
558         *temp_name = '\0';
559         
560         /* see if we need to recreate the original directory structure in the recycle bin */
561         if (recbin->keep_dir_tree == True) {
562                 safe_strcat(temp_name, "/", PATH_MAX - 1);
563                 safe_strcat(temp_name, path_name, PATH_MAX - 1);
564         }
565
566         exist = recycle_directory_exist(conn, temp_name);
567         if (exist) {
568                 DEBUG(10, ("recycle.bin: Directory already exists\n"));
569         } else {
570                 DEBUG(10, ("recycle.bin: Creating directory %s\n", temp_name));
571                 if (recycle_create_dir(conn, temp_name) == False) {
572                         DEBUG(3, ("Could not create directory, purging %s...\n", file_name));
573                         rc = default_vfs_ops.unlink(conn, file_name);
574                         goto done;
575                 }
576         }
577
578         asprintf(&final_name, "%s/%s", temp_name, base);
579         ALLOC_CHECK(final_name, done);
580         DEBUG(10, ("recycle.bin: recycled file name: %s\n", temp_name));                /* new filename with path */
581
582         /* check if we should delete file from recycle bin */
583         if (recycle_file_exist(conn, final_name)) {
584                 if (recbin->versions == False || matchparam(recbin->noversions, base) == True) {
585                         DEBUG(3, ("recycle.bin: Removing old file %s from recycle bin\n", final_name));
586                         if (default_vfs_ops.unlink(conn, final_name) != 0) {
587                                 DEBUG(1, ("recycle.bin: Error deleting old file: %s\n", strerror(errno)));
588                         }
589                 }
590         }
591
592         /* rename file we move to recycle bin */
593         i = 1;
594         while (recycle_file_exist(conn, final_name)) {
595                 snprintf(final_name, PATH_MAX, "%s/Copy #%d of %s", temp_name, i++, base);
596         }
597
598         DEBUG(10, ("recycle.bin: Moving %s to %s\n", file_name, final_name));
599         rc = default_vfs_ops.rename(conn, file_name, final_name);
600         if (rc != 0) {
601                 DEBUG(3, ("recycle.bin: Move error %d (%s), purging file %s (%s)\n", errno, strerror(errno), file_name, final_name));
602                 rc = default_vfs_ops.unlink(conn, file_name);
603                 goto done;
604         }
605
606         /* touch access date of moved file */
607         if (recbin->touch == True )
608                 recycle_touch(conn, final_name);
609
610 done:
611         SAFE_FREE(path_name);
612         SAFE_FREE(temp_name);
613         SAFE_FREE(final_name);
614         return rc;
615 }
616
617 int vfs_recycle_init(void)
618 {
619         return smb_register_vfs("recycle", recycle_init, SMB_VFS_INTERFACE_VERSION);
620 }