merging for 2.2.6pre1
[Samba.git] / examples / VFS / recycle / recycle.c
blob4e7b6c7bcf31c65327857ad80095afb8d9ead5c2
1 /*
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.
23 #include "config.h"
24 #include <stdio.h>
25 #include <sys/stat.h>
26 #ifdef HAVE_UTIME_H
27 #include <utime.h>
28 #endif
29 #ifdef HAVE_DIRENT_H
30 #include <dirent.h>
31 #endif
32 #ifdef HAVE_FCNTL_H
33 #include <fcntl.h>
34 #endif
35 #include <errno.h>
36 #include <string.h>
37 #include <includes.h>
38 #include <vfs.h>
40 const char delimiter = '|'; /* delimiter for options */
42 /* One per connection */
44 typedef struct recycle_bin_struct
46 TALLOC_CTX *ctx;
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 */
55 } recycle_bin_struct;
57 /* Global Variables */
58 static recycle_bin_struct *current;
60 /* VFS operations */
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 = {
72 /* Disk operations */
74 recycle_connect, /* connect */
75 recycle_disconnect, /* disconnect */
76 NULL, /* disk free */
78 /* Directory operations */
80 NULL, /* opendir */
81 NULL, /* readdir */
82 NULL, /* mkdir */
83 NULL, /* rmdir */
84 NULL, /* closedir */
86 /* File operations */
88 NULL, /* open */
89 NULL, /* close */
90 NULL, /* read */
91 NULL, /* write */
92 NULL, /* lseek */
93 NULL, /* rename */
94 NULL, /* fsync */
95 NULL, /* stat */
96 NULL, /* fstat */
97 NULL, /* lstat */
98 recycle_unlink,
99 NULL, /* chmod */
100 NULL, /* fchmod */
101 NULL, /* chown */
102 NULL, /* fchown */
103 NULL, /* chdir */
104 NULL, /* getwd */
105 NULL, /* utime */
106 NULL, /* ftruncate */
107 NULL, /* lock */
108 NULL, /* symlink */
109 NULL, /* readlink */
110 NULL, /* link */
111 NULL, /* mknod */
112 NULL, /* realpath */
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)
155 return False;
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)
174 return False;
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)
179 return False;
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)
184 return False;
185 DEBUG(10, ("noversions=%s\n", current->noversions));
187 return True;
191 * We don't care for sections in configuration file
194 static BOOL do_section(char *pszSectionName)
196 return True;
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));
215 return &recycle_ops;
218 static int recycle_connect(struct connection_struct *conn, const char *service, const char *user)
220 const char *p;
221 pstring conf_file;
222 int rc;
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"));
229 return 0;
232 /* read configuration file */
233 *conf_file='\0';
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"));
243 return -1;
245 current->ctx = ctx;
246 /* Set defaults */
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;
260 return 0;
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)
272 SMB_STRUCT_STAT st;
274 if (default_vfs_ops.stat(conn,dname,&st) != 0)
275 return(False);
277 if (isdir)
278 return S_ISDIR(st.st_mode) ? True : False;
279 else
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);
294 * Return file size
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)
301 SMB_STRUCT_STAT st;
302 if (default_vfs_ops.stat(conn,fname,&st) != 0) {
303 DEBUG(0,("stat for %s returned %s\n",fname,strerror(errno)));
304 return (SMB_OFF_T)0;
306 return(st.st_size);
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)
317 char *c,*y;
318 int i;
320 mode_t mode;
321 pstring tempstr;
322 pstring newdir;
324 *newdir='\0';
325 mode=S_IREAD|S_IWRITE|S_IEXEC;
326 pstrcpy(tempstr,dname);
327 y=tempstr;
328 /* Create directory tree if neccessary */
329 while((c=strsep(&y,"/"))) {
330 pstrcat(newdir,c);
331 if (recycle_directory_exist(conn,newdir))
332 DEBUG(3, ("dir %s already exists\n",newdir));
333 else {
334 DEBUG(3, ("creating new dir %s\n",newdir));
335 i=default_vfs_ops.mkdir(conn,newdir,mode);
336 if (i) {
337 DEBUG(3,("mkdir failed for %s with error %s\n",newdir,strerror(errno)));
338 return False;
341 pstrcat(newdir,"/");
343 return True;
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)
354 char *p,*c;
355 pstring str;
356 int i,len;
358 if (haystack==NULL || strlen(haystack)==0 || needle == NULL || strlen(needle)== 0)
359 return False;
361 pstrcpy(str,haystack);
362 len=strlen(str)+1;
363 p=c=str;
365 for (i=0; i < len; i++, p++) {
366 if (*p == delimiter || *p == '\0') {
367 *p='\0';
368 if(strncmp(c,needle,c-p) == 0)
369 return True;
370 c=p+1;
373 return False;
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)
384 char *p,*c;
385 pstring str;
386 int i,len;
388 if (haystack==NULL || strlen(haystack)==0 || needle == NULL || strlen(needle)== 0)
389 return False;
391 pstrcpy(str,haystack);
392 len=strlen(str)+1;
393 p=c=str;
395 for (i=0; i < len; i++, p++) {
396 if (*p == delimiter || *p == '\0') {
397 *p='\0';
398 if (!unix_wild_match(c,needle))
399 return True;
400 c=p+1;
403 return False;
407 * Touch access date
409 void recycle_touch(connection_struct *conn, const char *fname)
411 SMB_STRUCT_STAT st;
412 struct utimbuf tb;
413 time_t current;
415 if (default_vfs_ops.stat(conn,fname,&st) != 0) {
416 DEBUG(0,("stat for %s returned %s\n",fname,strerror(errno)));
417 return;
419 current = time(&current);
420 tb.actime = current;
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;
433 char *base, *ext;
434 int i=1, len, addlen;
435 SMB_BIG_UINT dfree,dsize,bsize;
436 SMB_OFF_T fsize,space_avail;
437 BOOL exist;
438 int rc;
440 pstrcpy(fname,inname);
441 if (conn->vfs_private)
442 current = (recycle_bin_struct *)conn->vfs_private;
443 else {
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);
460 if(fsize == 0) {
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 */
478 pstrcpy(fpath,"/");
479 pstrcat(fpath, fname);
480 base = strrchr(fpath, '/');
481 if (base == NULL) {
482 ext = strrchr(fname, '.');
483 base = (char *)fname;
484 pstrcpy(fpath,"/");
486 else {
487 ext = strrchr(base, '.');
488 *(base++) = '\0';
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)
510 pstrcat(bin, fpath);
512 exist=recycle_directory_exist(conn,bin);
513 if (exist)
514 DEBUG(10, ("Directory already exists\n"));
515 else {
516 DEBUG(10, ("Creating directory %s\n",bin));
517 rc=recycle_create_dir(conn,bin);
518 if (rc == False)
520 DEBUG(3, ("Could not create directory, purging %s...\n", fname));
521 return default_vfs_ops.unlink(conn,fname);
525 pstrcat(bin, "/");
526 pstrcat(bin,base);
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 */
538 len = strlen(bin);
539 addlen = sizeof(pstring)-len-1;
540 while(recycle_file_exist(conn,bin)) {
541 slprintf(bin+len, addlen, " (Copy #%d)", i++);
542 pstrcat(bin, ext);
545 DEBUG(10, ("Moving source=%s to dest=%s\n", fname, bin));
546 rc = default_vfs_ops.rename(conn, fname, bin);
547 if (rc == -1) {
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);
555 return rc;