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, Juergen Hasch - added some options.
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
40 const char delimiter
= '|'; /* delimiter for options */
42 /* One per connection */
44 typedef struct recycle_bin_struct
47 char *recycle_bin
; /* name of the recycle bin directory */
48 BOOL keep_directories
; /* keep directory structure of deleted file in recycle bin */
49 BOOL versions
; /* create versions of deleted files with identical name */
50 BOOL touch
; /* touch access date of deleted file */
51 char *exclude
; /* which files to exclude */
52 char *exclude_dir
; /* which directories to exclude */
53 char *noversions
; /* which files to exclude from versioning */
54 SMB_OFF_T max_size
; /* maximum file size to be saved */
57 /* Global Variables */
58 static recycle_bin_struct
*current
;
62 extern struct vfs_ops default_vfs_ops
; /* For passthrough operation */
64 static int recycle_unlink(connection_struct
*, const char *);
65 static int recycle_connect(struct connection_struct
*conn
, const char *service
, const char *user
);
66 static void recycle_disconnect(struct connection_struct
*conn
);
68 BOOL
checkparam(char *haystack
,char *needle
);
70 struct vfs_ops recycle_ops
= {
74 recycle_connect
, /* connect */
75 recycle_disconnect
, /* disconnect */
78 /* Directory operations */
106 NULL
, /* ftruncate */
113 NULL
, /* fget_nt_acl */
114 NULL
, /* get_nt_acl */
115 NULL
, /* fset_nt_acl */
116 NULL
, /* set_nt_acl */
118 NULL
, /* chmod_acl */
119 NULL
, /* fchmod_acl */
121 NULL
, /* sys_acl_get_entry */
122 NULL
, /* sys_acl_get_tag_type */
123 NULL
, /* sys_acl_get_permset */
124 NULL
, /* sys_acl_get_qualifier */
125 NULL
, /* sys_acl_get_file */
126 NULL
, /* sys_acl_get_fd */
127 NULL
, /* sys_acl_clear_perms */
128 NULL
, /* sys_acl_add_perm */
129 NULL
, /* sys_acl_to_text */
130 NULL
, /* sys_acl_init */
131 NULL
, /* sys_acl_create_entry */
132 NULL
, /* sys_acl_set_tag_type */
133 NULL
, /* sys_acl_set_qualifier */
134 NULL
, /* sys_acl_set_permset */
135 NULL
, /* sys_acl_valid */
136 NULL
, /* sys_acl_set_file */
137 NULL
, /* sys_acl_set_fd */
138 NULL
, /* sys_acl_delete_def_file */
139 NULL
, /* sys_acl_get_perm */
140 NULL
, /* sys_acl_free_text */
141 NULL
, /* sys_acl_free_acl */
142 NULL
/* sys_acl_free_qualifier */
146 * Parse recycle bin configuration parameters
148 * @retval False if out of memory
150 static BOOL
do_parameter(char *pszParmName
, char *pszParmValue
)
152 if (StrCaseCmp("name",pszParmName
)==0) {
153 current
->recycle_bin
= (char *)talloc(current
->ctx
,sizeof(pstring
));
154 if (current
->recycle_bin
== NULL
)
156 current
->recycle_bin
= safe_strcpy(current
->recycle_bin
,pszParmValue
,sizeof(pstring
));
157 standard_sub_basic(current
->recycle_bin
, strlen(current
->recycle_bin
));
158 trim_string(current
->recycle_bin
,"/","/");
159 DEBUG(10, ("name=%s\n", current
->recycle_bin
));
160 } else if (StrCaseCmp("mode",pszParmName
)==0) {
161 if (checkparam(pszParmValue
,"KEEP_DIRECTORIES") == True
)
162 current
->keep_directories
= True
;
163 if (checkparam(pszParmValue
,"VERSIONS") == True
)
164 current
->versions
= True
;
165 if (checkparam(pszParmValue
,"TOUCH") == True
)
166 current
->touch
= True
;
167 DEBUG(10, ("mode=%s\n", pszParmValue
));
168 } else if (StrCaseCmp("maxsize",pszParmName
)==0) {
169 current
->max_size
= strtoul(pszParmValue
,NULL
,10);
170 DEBUG(10, ("max_size=%ld\n", (long int)current
->max_size
));
171 } else if (StrCaseCmp("exclude",pszParmName
)==0) {
172 current
->exclude
= talloc_strdup(current
->ctx
, pszParmValue
);
173 if (current
->exclude
== NULL
)
175 DEBUG(10, ("exclude=%s\n", current
->exclude
));
176 } else if (StrCaseCmp("excludedir",pszParmName
)==0) {
177 current
->exclude_dir
= talloc_strdup(current
->ctx
, pszParmValue
);
178 if (current
->exclude_dir
== NULL
)
180 DEBUG(10, ("exclude_dir=%s\n", current
->exclude_dir
));
181 } else if (StrCaseCmp("noversions",pszParmName
)==0) {
182 current
->noversions
= talloc_strdup(current
->ctx
, pszParmValue
);
183 if (current
->noversions
== NULL
)
185 DEBUG(10, ("noversions=%s\n", current
->noversions
));
191 * We don't care for sections in configuration file
194 static BOOL
do_section(char *pszSectionName
)
200 * VFS initialisation function.
202 * @retval initialised vfs_ops structure
204 struct vfs_ops
*vfs_init(int *vfs_version
, struct vfs_ops
*def_vfs_ops
)
206 struct vfs_ops tmp_ops
;
207 DEBUG(3, ("Initializing VFS module recycle\n"));
209 *vfs_version
= SMB_VFS_INTERFACE_VERSION
;
210 memcpy(&tmp_ops
, def_vfs_ops
, sizeof(struct vfs_ops
));
211 tmp_ops
.unlink
= recycle_unlink
;
212 tmp_ops
.connect
= recycle_connect
;
213 tmp_ops
.disconnect
= recycle_disconnect
;
214 memcpy(&recycle_ops
, &tmp_ops
, sizeof(struct vfs_ops
));
218 static int recycle_connect(struct connection_struct
*conn
, const char *service
, const char *user
)
223 TALLOC_CTX
*ctx
=NULL
;
225 DEBUG(3,("Called for service %s (%d) as user %s\n", service
, SNUM(conn
), user
));
227 if (!(ctx
= talloc_init_named("recycle bin"))) {
228 DEBUG(0, ("Failed to allocate memory in VFS module recycle_bin\n"));
232 /* read configuration file */
234 p
= (const char *)lp_vfs_options(SNUM(conn
));
235 if (p
!= NULL
&& strlen(p
) > 0) {
236 pstrcpy(conf_file
,p
);
237 DEBUG(10,("Using configuration file %s\n",conf_file
));
240 current
= talloc(ctx
,sizeof(recycle_bin_struct
));
241 if ( current
== NULL
) {
242 DEBUG(0, ("Failed to allocate memory in VFS module recycle_bin\n"));
247 current
->recycle_bin
= talloc_strdup(ctx
,".recycle");
248 current
->keep_directories
= False
;
249 current
->versions
= False
;
250 current
->touch
= False
;
251 current
->exclude
= "";
252 current
->exclude_dir
= "";
253 current
->noversions
= "";
254 current
->max_size
= 0;
255 if (strlen(conf_file
) > 0) {
256 rc
=pm_process( conf_file
, do_section
, do_parameter
);
257 DEBUG(10, ("pm_process returned %d\n", rc
));
259 conn
->vfs_private
= (void *)current
;
263 static void recycle_disconnect(struct connection_struct
*conn
)
265 DEBUG(3, ("Disconnecting VFS module recycle_bin\n"));
266 talloc_destroy(((recycle_bin_struct
*)conn
->vfs_private
)->ctx
);
267 default_vfs_ops
.disconnect(conn
);
270 static BOOL
recycle_XXX_exist(connection_struct
*conn
, const char *dname
, BOOL isdir
)
274 if (default_vfs_ops
.stat(conn
,dname
,&st
) != 0)
278 return S_ISDIR(st
.st_mode
) ? True
: False
;
280 return S_ISREG(st
.st_mode
) ? True
: False
;
283 static BOOL
recycle_directory_exist(connection_struct
*conn
, const char *dname
)
285 return recycle_XXX_exist(conn
, dname
, True
);
288 static BOOL
recycle_file_exist(connection_struct
*conn
, const char *fname
)
290 return recycle_XXX_exist(conn
, fname
, False
);
295 * @param conn connection
296 * @param fname file name
297 * @return size in bytes
299 static SMB_OFF_T
recycle_get_file_size(connection_struct
*conn
, const char *fname
)
302 if (default_vfs_ops
.stat(conn
,fname
,&st
) != 0) {
303 DEBUG(0,("stat for %s returned %s\n",fname
,strerror(errno
)));
310 * Create directory tree
311 * @param conn connection
312 * @param dname Directory tree to be created
313 * @return Returns True for success
315 static BOOL
recycle_create_dir(connection_struct
*conn
, const char *dname
)
325 mode
=S_IREAD
|S_IWRITE
|S_IEXEC
;
326 pstrcpy(tempstr
,dname
);
328 /* Create directory tree if neccessary */
329 while((c
=strsep(&y
,"/"))) {
331 if (recycle_directory_exist(conn
,newdir
))
332 DEBUG(3, ("dir %s already exists\n",newdir
));
334 DEBUG(3, ("creating new dir %s\n",newdir
));
335 i
=default_vfs_ops
.mkdir(conn
,newdir
,mode
);
337 DEBUG(3,("mkdir failed for %s with error %s\n",newdir
,strerror(errno
)));
347 * Check if needle is contained exactly in haystack
348 * @param haystack list of parameters separated by delimimiter character
349 * @param needle string to be matched exactly to haystack
350 * @return True if found
352 BOOL
checkparam(char *haystack
,char *needle
)
358 if (haystack
==NULL
|| strlen(haystack
)==0 || needle
== NULL
|| strlen(needle
)== 0)
361 pstrcpy(str
,haystack
);
365 for (i
=0; i
< len
; i
++, p
++) {
366 if (*p
== delimiter
|| *p
== '\0') {
368 if(strncmp(c
,needle
,c
-p
) == 0)
377 * Check if needle is contained in haystack, * and ? patterns are resolved
378 * @param haystack list of parameters separated by delimimiter character
379 * @param needle string to be matched exectly to haystack including pattern matching
380 * @return True if found
382 BOOL
matchparam(char *haystack
,char *needle
)
388 if (haystack
==NULL
|| strlen(haystack
)==0 || needle
== NULL
|| strlen(needle
)== 0)
391 pstrcpy(str
,haystack
);
395 for (i
=0; i
< len
; i
++, p
++) {
396 if (*p
== delimiter
|| *p
== '\0') {
398 if (!unix_wild_match(c
,needle
))
409 void recycle_touch(connection_struct
*conn
, const char *fname
)
415 if (default_vfs_ops
.stat(conn
,fname
,&st
) != 0) {
416 DEBUG(0,("stat for %s returned %s\n",fname
,strerror(errno
)));
419 current
= time(¤t
);
421 tb
.modtime
= st
.st_mtime
;
423 if (default_vfs_ops
.utime(conn
, fname
, &tb
) == -1 )
424 DEBUG(0, ("Touching %s failed, reason = %s\n",fname
,strerror(errno
)));
428 * Check if file should be recycled
430 static int recycle_unlink(connection_struct
*conn
, const char *inname
)
432 pstring fname
,fpath
, bin
;
434 int i
=1, len
, addlen
;
435 SMB_BIG_UINT dfree
,dsize
,bsize
;
436 SMB_OFF_T fsize
,space_avail
;
440 pstrcpy(fname
,inname
);
441 if (conn
->vfs_private
)
442 current
= (recycle_bin_struct
*)conn
->vfs_private
;
444 DEBUG(0,("Recycle bin not initialized!\n"));
445 return default_vfs_ops
.unlink(conn
,fname
);
448 if(!current
->recycle_bin
|| !*(current
->recycle_bin
)) {
449 DEBUG(3, ("Recycle path not set, purging %s...\n", fname
));
450 return default_vfs_ops
.unlink(conn
,fname
);
453 /* we don't recycle the recycle bin... */
454 if (strstr(fname
,current
->recycle_bin
)==fname
) {
455 DEBUG(3, ("File is within recycling bin\n"));
456 return default_vfs_ops
.unlink(conn
,fname
);
459 fsize
= recycle_get_file_size(conn
,fname
);
461 DEBUG(3, ("File %s is empty, purging...\n", fname
));
462 return default_vfs_ops
.unlink(conn
,fname
);
465 if(current
->max_size
> 0 && fsize
> current
->max_size
) {
466 DEBUG(3, ("File %s exceeds maximum recycle size, purging... \n", fname
));
467 return default_vfs_ops
.unlink(conn
,fname
);
470 space_avail
= default_vfs_ops
.disk_free(conn
,".",True
,&bsize
,&dfree
,&dsize
)*1024L;
471 DEBUG(10,("space_avail = %Lu, fsize = %Lu\n",space_avail
,fsize
));
472 if(space_avail
< fsize
) {
473 DEBUG(3, ("Not enough diskspace, purging file %s\n",fname
));
474 return default_vfs_ops
.unlink(conn
,fname
);
477 /* extract filename and path */
479 pstrcat(fpath
, fname
);
480 base
= strrchr(fpath
, '/');
482 ext
= strrchr(fname
, '.');
483 base
= (char *)fname
;
487 ext
= strrchr(base
, '.');
491 DEBUG(10, ("fname:%s\n", fname
)); /* original filename with path */
492 DEBUG(10, ("fpath:%s\n", fpath
)); /* original path */
493 DEBUG(10, ("base:%s\n", base
)); /* filename without path */
494 DEBUG(10, ("ext:%s\n", ext
)); /* filename extension */
496 if (matchparam(current
->exclude
,base
)) {
497 DEBUG(3, ("file %s is excluded \n",base
));
498 return default_vfs_ops
.unlink(conn
,fname
);
501 if (checkparam(current
->exclude_dir
,fpath
)) {
502 DEBUG(3, ("directory %s is excluded \n",fpath
));
503 return default_vfs_ops
.unlink(conn
,fname
);
506 pstrcpy(bin
, current
->recycle_bin
);
508 /* see if we need to recreate the original directory structure in the recycle bin */
509 if (current
->keep_directories
== True
)
512 exist
=recycle_directory_exist(conn
,bin
);
514 DEBUG(10, ("Directory already exists\n"));
516 DEBUG(10, ("Creating directory %s\n",bin
));
517 rc
=recycle_create_dir(conn
,bin
);
520 DEBUG(3, ("Could not create directory, purging %s...\n", fname
));
521 return default_vfs_ops
.unlink(conn
,fname
);
527 DEBUG(10, ("bin:%s\n", bin
)); /* new filename with path */
529 /* check if we should delete file from recycle bin */
530 if (recycle_file_exist(conn
,bin
)) {
531 if (current
->versions
== False
|| matchparam(current
->noversions
,base
) == True
) {
532 DEBUG(3, ("Removing old file %s from recycle bin\n",bin
));
533 default_vfs_ops
.unlink(conn
,bin
);
537 /* rename file we move to recycle bin */
539 addlen
= sizeof(pstring
)-len
-1;
540 while(recycle_file_exist(conn
,bin
)) {
541 slprintf(bin
+len
, addlen
, " (Copy #%d)", i
++);
545 DEBUG(10, ("Moving source=%s to dest=%s\n", fname
, bin
));
546 rc
= default_vfs_ops
.rename(conn
, fname
, bin
);
548 DEBUG(3, ("Move error %d (%s), purging file %s (%s)\n", errno
, strerror(errno
),fname
,bin
));
549 return default_vfs_ops
.unlink(conn
,fname
);
552 /* touch access date of moved file */
553 if (current
->touch
== True
)
554 recycle_touch(conn
,bin
);