summaryrefslogtreecommitdiffstats
path: root/examples/VFS/recycle/recycle.c
diff options
context:
space:
mode:
Diffstat (limited to 'examples/VFS/recycle/recycle.c')
-rw-r--r--examples/VFS/recycle/recycle.c556
1 files changed, 556 insertions, 0 deletions
diff --git a/examples/VFS/recycle/recycle.c b/examples/VFS/recycle/recycle.c
new file mode 100644
index 00000000000..4d793395ce3
--- /dev/null
+++ b/examples/VFS/recycle/recycle.c
@@ -0,0 +1,556 @@
+/*
+ * Recycle bin VFS module for Samba.
+ *
+ * Copyright (C) 2001, Brandon Stone, Amherst College, <bbstone@amherst.edu>.
+ * Copyright (C) 2002, Jeremy Allison - modified to make a VFS module.
+ * Copyright (C) 2002, Juergen Hasch - added some options.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <sys/stat.h>
+#ifdef HAVE_UTIME_H
+#include <utime.h>
+#endif
+#ifdef HAVE_DIRENT_H
+#include <dirent.h>
+#endif
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#include <errno.h>
+#include <string.h>
+#include <includes.h>
+#include <vfs.h>
+
+const char delimiter = '|'; /* delimiter for options */
+
+/* One per connection */
+
+typedef struct recycle_bin_struct
+{
+ TALLOC_CTX *ctx;
+ char *recycle_bin; /* name of the recycle bin directory */
+ BOOL keep_directories; /* keep directory structure of deleted file in recycle bin */
+ BOOL versions; /* create versions of deleted files with identical name */
+ BOOL touch; /* touch access date of deleted file */
+ char *exclude; /* which files to exclude */
+ char *exclude_dir; /* which directories to exclude */
+ char *noversions; /* which files to exclude from versioning */
+ SMB_OFF_T max_size; /* maximum file size to be saved */
+} recycle_bin_struct;
+
+/* Global Variables */
+static recycle_bin_struct *current;
+
+/* VFS operations */
+
+extern struct vfs_ops default_vfs_ops; /* For passthrough operation */
+
+static int recycle_unlink(connection_struct *, const char *);
+static int recycle_connect(struct connection_struct *conn, const char *service, const char *user);
+static void recycle_disconnect(struct connection_struct *conn);
+
+BOOL checkparam(char *haystack,char *needle);
+
+struct vfs_ops recycle_ops = {
+
+ /* Disk operations */
+
+ recycle_connect, /* connect */
+ recycle_disconnect, /* disconnect */
+ NULL, /* disk free */
+
+ /* Directory operations */
+
+ NULL, /* opendir */
+ NULL, /* readdir */
+ NULL, /* mkdir */
+ NULL, /* rmdir */
+ NULL, /* closedir */
+
+ /* File operations */
+
+ NULL, /* open */
+ NULL, /* close */
+ NULL, /* read */
+ NULL, /* write */
+ NULL, /* lseek */
+ NULL, /* rename */
+ NULL, /* fsync */
+ NULL, /* stat */
+ NULL, /* fstat */
+ NULL, /* lstat */
+ recycle_unlink,
+ NULL, /* chmod */
+ NULL, /* fchmod */
+ NULL, /* chown */
+ NULL, /* fchown */
+ NULL, /* chdir */
+ NULL, /* getwd */
+ NULL, /* utime */
+ NULL, /* ftruncate */
+ NULL, /* lock */
+ NULL, /* symlink */
+ NULL, /* readlink */
+ NULL, /* link */
+ NULL, /* mknod */
+ NULL, /* realpath */
+ NULL, /* fget_nt_acl */
+ NULL, /* get_nt_acl */
+ NULL, /* fset_nt_acl */
+ NULL, /* set_nt_acl */
+
+ NULL, /* chmod_acl */
+ NULL, /* fchmod_acl */
+
+ NULL, /* sys_acl_get_entry */
+ NULL, /* sys_acl_get_tag_type */
+ NULL, /* sys_acl_get_permset */
+ NULL, /* sys_acl_get_qualifier */
+ NULL, /* sys_acl_get_file */
+ NULL, /* sys_acl_get_fd */
+ NULL, /* sys_acl_clear_perms */
+ NULL, /* sys_acl_add_perm */
+ NULL, /* sys_acl_to_text */
+ NULL, /* sys_acl_init */
+ NULL, /* sys_acl_create_entry */
+ NULL, /* sys_acl_set_tag_type */
+ NULL, /* sys_acl_set_qualifier */
+ NULL, /* sys_acl_set_permset */
+ NULL, /* sys_acl_valid */
+ NULL, /* sys_acl_set_file */
+ NULL, /* sys_acl_set_fd */
+ NULL, /* sys_acl_delete_def_file */
+ NULL, /* sys_acl_get_perm */
+ NULL, /* sys_acl_free_text */
+ NULL, /* sys_acl_free_acl */
+ NULL /* sys_acl_free_qualifier */
+};
+
+/**
+ * Parse recycle bin configuration parameters
+ *
+ * @retval False if out of memory
+ **/
+static BOOL do_parameter(char *pszParmName, char *pszParmValue)
+{
+ if (StrCaseCmp("name",pszParmName)==0) {
+ current->recycle_bin = (char *)talloc(current->ctx,sizeof(pstring));
+ if (current->recycle_bin == NULL)
+ return False;
+ current->recycle_bin = safe_strcpy(current->recycle_bin,pszParmValue,sizeof(pstring));
+ standard_sub_basic(current->recycle_bin, strlen(current->recycle_bin));
+ trim_string(current->recycle_bin,"/","/");
+ DEBUG(10, ("name=%s\n", current->recycle_bin));
+ } else if (StrCaseCmp("mode",pszParmName)==0) {
+ if (checkparam(pszParmValue,"KEEP_DIRECTORIES") == True)
+ current->keep_directories = True;
+ if (checkparam(pszParmValue,"VERSIONS") == True)
+ current->versions = True;
+ if (checkparam(pszParmValue,"TOUCH") == True)
+ current->touch = True;
+ DEBUG(10, ("mode=%s\n", pszParmValue));
+ } else if (StrCaseCmp("maxsize",pszParmName)==0) {
+ current->max_size = strtoul(pszParmValue,NULL,10);
+ DEBUG(10, ("max_size=%ld\n", (long int)current->max_size));
+ } else if (StrCaseCmp("exclude",pszParmName)==0) {
+ current->exclude = talloc_strdup(current->ctx, pszParmValue);
+ if (current->exclude == NULL)
+ return False;
+ DEBUG(10, ("exclude=%s\n", current->exclude));
+ } else if (StrCaseCmp("excludedir",pszParmName)==0) {
+ current->exclude_dir = talloc_strdup(current->ctx, pszParmValue);
+ if (current->exclude_dir == NULL)
+ return False;
+ DEBUG(10, ("exclude_dir=%s\n", current->exclude_dir));
+ } else if (StrCaseCmp("noversions",pszParmName)==0) {
+ current->noversions = talloc_strdup(current->ctx, pszParmValue);
+ if (current->noversions == NULL)
+ return False;
+ DEBUG(10, ("noversions=%s\n", current->noversions));
+ }
+ return True;
+}
+
+/**
+ * We don't care for sections in configuration file
+ *
+ **/
+static BOOL do_section(char *pszSectionName)
+{
+ return True;
+}
+
+/**
+ * VFS initialisation function.
+ *
+ * @retval initialised vfs_ops structure
+ **/
+struct vfs_ops *vfs_init(int *vfs_version, struct vfs_ops *def_vfs_ops)
+{
+ struct vfs_ops tmp_ops;
+ DEBUG(3, ("Initializing VFS module recycle\n"));
+
+ *vfs_version = SMB_VFS_INTERFACE_VERSION;
+ memcpy(&tmp_ops, def_vfs_ops, sizeof(struct vfs_ops));
+ tmp_ops.unlink = recycle_unlink;
+ tmp_ops.connect = recycle_connect;
+ tmp_ops.disconnect = recycle_disconnect;
+ memcpy(&recycle_ops, &tmp_ops, sizeof(struct vfs_ops));
+ return &recycle_ops;
+}
+
+static int recycle_connect(struct connection_struct *conn, const char *service, const char *user)
+{
+ const char *p;
+ pstring conf_file;
+ int rc;
+ TALLOC_CTX *ctx=NULL;
+
+ DEBUG(3,("Called for service %s (%d) as user %s\n", service, SNUM(conn), user));
+
+ if (!(ctx = talloc_init_named("recycle bin"))) {
+ DEBUG(0, ("Failed to allocate memory in VFS module recycle_bin\n"));
+ return 0;
+ }
+
+ /* read configuration file */
+ *conf_file='\0';
+ p = (const char *)lp_vfs_options(SNUM(conn));
+ if (p != NULL && strlen(p) > 0) {
+ pstrcpy(conf_file,p);
+ DEBUG(10,("Using configuration file %s\n",conf_file));
+ }
+
+ current = talloc(ctx,sizeof(recycle_bin_struct));
+ if ( current == NULL) {
+ DEBUG(0, ("Failed to allocate memory in VFS module recycle_bin\n"));
+ return -1;
+ }
+ current->ctx = ctx;
+ /* Set defaults */
+ current->recycle_bin = talloc_strdup(ctx,".recycle");
+ current->keep_directories = False;
+ current->versions = False;
+ current->touch = False;
+ current->exclude = "";
+ current->exclude_dir = "";
+ current->noversions = "";
+ current->max_size = 0;
+ if (strlen(conf_file) > 0) {
+ rc=pm_process( conf_file, do_section, do_parameter);
+ DEBUG(10, ("pm_process returned %d\n", rc));
+ }
+ conn->vfs_private= (void *)current;
+ return 0;
+}
+
+static void recycle_disconnect(struct connection_struct *conn)
+{
+ DEBUG(3, ("Disconnecting VFS module recycle_bin\n"));
+ talloc_destroy(((recycle_bin_struct*)conn->vfs_private)->ctx);
+ default_vfs_ops.disconnect(conn);
+}
+
+static BOOL recycle_XXX_exist(connection_struct *conn, const char *dname, BOOL isdir)
+{
+ SMB_STRUCT_STAT st;
+
+ if (default_vfs_ops.stat(conn,dname,&st) != 0)
+ return(False);
+
+ if (isdir)
+ return S_ISDIR(st.st_mode) ? True : False;
+ else
+ return S_ISREG(st.st_mode) ? True : False;
+}
+
+static BOOL recycle_directory_exist(connection_struct *conn, const char *dname)
+{
+ return recycle_XXX_exist(conn, dname, True);
+}
+
+static BOOL recycle_file_exist(connection_struct *conn, const char *fname)
+{
+ return recycle_XXX_exist(conn, fname, False);
+}
+
+/**
+ * Return file size
+ * @param conn connection
+ * @param fname file name
+ * @return size in bytes
+ **/
+static SMB_OFF_T recycle_get_file_size(connection_struct *conn, const char *fname)
+{
+ SMB_STRUCT_STAT st;
+ if (default_vfs_ops.stat(conn,fname,&st) != 0) {
+ DEBUG(0,("stat for %s returned %s\n",fname,strerror(errno)));
+ return (SMB_OFF_T)0;
+ }
+ return(st.st_size);
+}
+
+/**
+ * Create directory tree
+ * @param conn connection
+ * @param dname Directory tree to be created
+ * @return Returns True for success
+ **/
+static BOOL recycle_create_dir(connection_struct *conn, const char *dname)
+{
+ char *c,*y;
+ int i;
+
+ mode_t mode;
+ pstring tempstr;
+ pstring newdir;
+
+ *newdir='\0';
+ mode=S_IREAD|S_IWRITE|S_IEXEC;
+ pstrcpy(tempstr,dname);
+ y=tempstr;
+ /* Create directory tree if neccessary */
+ for(c = strtok(y,"/"); c; c= strtok(NULL,"/")) {
+ pstrcat(newdir,c);
+ if (recycle_directory_exist(conn,newdir))
+ DEBUG(3, ("dir %s already exists\n",newdir));
+ else {
+ DEBUG(3, ("creating new dir %s\n",newdir));
+ i=default_vfs_ops.mkdir(conn,newdir,mode);
+ if (i) {
+ DEBUG(3,("mkdir failed for %s with error %s\n",newdir,strerror(errno)));
+ return False;
+ }
+ }
+ pstrcat(newdir,"/");
+ }
+ return True;
+}
+
+/**
+ * Check if needle is contained exactly in haystack
+ * @param haystack list of parameters separated by delimimiter character
+ * @param needle string to be matched exactly to haystack
+ * @return True if found
+ **/
+BOOL checkparam(char *haystack,char *needle)
+{
+ char *p,*c;
+ pstring str;
+ int i,len;
+
+ if (haystack==NULL || strlen(haystack)==0 || needle == NULL || strlen(needle)== 0)
+ return False;
+
+ pstrcpy(str,haystack);
+ len=strlen(str)+1;
+ p=c=str;
+
+ for (i=0; i < len; i++, p++) {
+ if (*p == delimiter || *p == '\0') {
+ *p='\0';
+ if(strncmp(c,needle,c-p) == 0)
+ return True;
+ c=p+1;
+ }
+ }
+ return False;
+}
+
+/**
+ * Check if needle is contained in haystack, * and ? patterns are resolved
+ * @param haystack list of parameters separated by delimimiter character
+ * @param needle string to be matched exectly to haystack including pattern matching
+ * @return True if found
+ **/
+BOOL matchparam(char *haystack,char *needle)
+{
+ char *p,*c;
+ pstring str;
+ int i,len;
+
+ if (haystack==NULL || strlen(haystack)==0 || needle == NULL || strlen(needle)== 0)
+ return False;
+
+ pstrcpy(str,haystack);
+ len=strlen(str)+1;
+ p=c=str;
+
+ for (i=0; i < len; i++, p++) {
+ if (*p == delimiter || *p == '\0') {
+ *p='\0';
+ if (!unix_wild_match(c,needle))
+ return True;
+ c=p+1;
+ }
+ }
+ return False;
+}
+
+/**
+ * Touch access date
+ **/
+void recycle_touch(connection_struct *conn, const char *fname)
+{
+ SMB_STRUCT_STAT st;
+ struct utimbuf tb;
+ time_t current;
+
+ if (default_vfs_ops.stat(conn,fname,&st) != 0) {
+ DEBUG(0,("stat for %s returned %s\n",fname,strerror(errno)));
+ return;
+ }
+ current = time(&current);
+ tb.actime = current;
+ tb.modtime = st.st_mtime;
+
+ if (default_vfs_ops.utime(conn, fname, &tb) == -1 )
+ DEBUG(0, ("Touching %s failed, reason = %s\n",fname,strerror(errno)));
+ }
+
+/**
+ * Check if file should be recycled
+ **/
+static int recycle_unlink(connection_struct *conn, const char *inname)
+{
+ pstring fname,fpath, bin;
+ char *base, *ext;
+ int i=1, len, addlen;
+ SMB_BIG_UINT dfree,dsize,bsize;
+ SMB_OFF_T fsize,space_avail;
+ BOOL exist;
+ int rc;
+
+ pstrcpy(fname,inname);
+ if (conn->vfs_private)
+ current = (recycle_bin_struct *)conn->vfs_private;
+ else {
+ DEBUG(0,("Recycle bin not initialized!\n"));
+ return default_vfs_ops.unlink(conn,fname);
+ }
+
+ if(!current->recycle_bin || !*(current->recycle_bin)) {
+ DEBUG(3, ("Recycle path not set, purging %s...\n", fname));
+ return default_vfs_ops.unlink(conn,fname);
+ }
+
+ /* we don't recycle the recycle bin... */
+ if (strstr(fname,current->recycle_bin)==fname) {
+ DEBUG(3, ("File is within recycling bin\n"));
+ return default_vfs_ops.unlink(conn,fname);
+ }
+
+ fsize = recycle_get_file_size(conn,fname);
+ if(fsize == 0) {
+ DEBUG(3, ("File %s is empty, purging...\n", fname));
+ return default_vfs_ops.unlink(conn,fname);
+ }
+
+ if(current->max_size > 0 && fsize > current->max_size) {
+ DEBUG(3, ("File %s exceeds maximum recycle size, purging... \n", fname));
+ return default_vfs_ops.unlink(conn,fname);
+ }
+
+ space_avail = default_vfs_ops.disk_free(conn,".",True,&bsize,&dfree,&dsize)*1024L;
+ DEBUG(10,("space_avail = %Lu, fsize = %Lu\n",space_avail,fsize));
+ if(space_avail < fsize) {
+ DEBUG(3, ("Not enough diskspace, purging file %s\n",fname));
+ return default_vfs_ops.unlink(conn,fname);
+ }
+
+ /* extract filename and path */
+ pstrcpy(fpath,"/");
+ pstrcat(fpath, fname);
+ base = strrchr(fpath, '/');
+ if (base == NULL) {
+ ext = strrchr(fname, '.');
+ base = (char *)fname;
+ pstrcpy(fpath,"/");
+ }
+ else {
+ ext = strrchr(base, '.');
+ *(base++) = '\0';
+ }
+
+ DEBUG(10, ("fname:%s\n", fname)); /* original filename with path */
+ DEBUG(10, ("fpath:%s\n", fpath)); /* original path */
+ DEBUG(10, ("base:%s\n", base)); /* filename without path */
+ DEBUG(10, ("ext:%s\n", ext)); /* filename extension */
+
+ if (matchparam(current->exclude,base)) {
+ DEBUG(3, ("file %s is excluded \n",base));
+ return default_vfs_ops.unlink(conn,fname);
+ }
+
+ if (checkparam(current->exclude_dir,fpath)) {
+ DEBUG(3, ("directory %s is excluded \n",fpath));
+ return default_vfs_ops.unlink(conn,fname);
+ }
+
+ pstrcpy(bin, current->recycle_bin);
+
+ /* see if we need to recreate the original directory structure in the recycle bin */
+ if (current->keep_directories == True)
+ pstrcat(bin, fpath);
+
+ exist=recycle_directory_exist(conn,bin);
+ if (exist)
+ DEBUG(10, ("Directory already exists\n"));
+ else {
+ DEBUG(10, ("Creating directory %s\n",bin));
+ rc=recycle_create_dir(conn,bin);
+ if (rc == False)
+ {
+ DEBUG(3, ("Could not create directory, purging %s...\n", fname));
+ return default_vfs_ops.unlink(conn,fname);
+ }
+ }
+
+ pstrcat(bin, "/");
+ pstrcat(bin,base);
+ DEBUG(10, ("bin:%s\n", bin)); /* new filename with path */
+
+ /* check if we should delete file from recycle bin */
+ if (recycle_file_exist(conn,bin)) {
+ if (current->versions == False || matchparam(current->noversions,base) == True) {
+ DEBUG(3, ("Removing old file %s from recycle bin\n",bin));
+ default_vfs_ops.unlink(conn,bin);
+ }
+ }
+
+ /* rename file we move to recycle bin */
+ len = strlen(bin);
+ addlen = sizeof(pstring)-len-1;
+ while(recycle_file_exist(conn,bin)) {
+ slprintf(bin+len, addlen, " (Copy #%d)", i++);
+ pstrcat(bin, ext);
+ }
+
+ DEBUG(10, ("Moving source=%s to dest=%s\n", fname, bin));
+ rc = default_vfs_ops.rename(conn, fname, bin);
+ if (rc == -1) {
+ DEBUG(3, ("Move error %d (%s), purging file %s (%s)\n", errno, strerror(errno),fname,bin));
+ return default_vfs_ops.unlink(conn,fname);
+ }
+
+ /* touch access date of moved file */
+ if (current->touch == True )
+ recycle_touch(conn,bin);
+ return rc;
+}