/* Unix SMB/Netbios implementation. Version 3.0 VFS initialisation and support functions Copyright (C) Tim Potter 1999 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 "includes.h" extern int DEBUGLEVEL; /* Some structures to help us initialise the vfs operations table */ struct vfs_syminfo { char *name; void *fptr; }; /* Default vfs hooks. WARNING: The order of these initialisers is very important. They must be in the same order as defined in vfs.h. Change at your own peril. */ struct vfs_ops default_vfs_ops = { /* Disk operations */ vfswrap_dummy_connect, vfswrap_dummy_disconnect, vfswrap_disk_free, /* Directory operations */ vfswrap_opendir, vfswrap_readdir, vfswrap_mkdir, vfswrap_rmdir, vfswrap_closedir, /* File operations */ vfswrap_open, vfswrap_close, vfswrap_read, vfswrap_write, vfswrap_lseek, vfswrap_rename, vfswrap_fsync, vfswrap_stat, vfswrap_fstat, vfswrap_lstat, vfswrap_unlink, vfswrap_chmod, vfswrap_fchmod, vfswrap_chown, vfswrap_fchown, vfswrap_chdir, vfswrap_getwd, vfswrap_utime, vfswrap_ftruncate, vfswrap_lock, vfswrap_symlink, vfswrap_readlink, vfswrap_fget_nt_acl, vfswrap_get_nt_acl, vfswrap_fset_nt_acl, vfswrap_set_nt_acl, #if defined(HAVE_NO_ACLS) NULL, NULL #else vfswrap_chmod_acl, vfswrap_fchmod_acl #endif }; /**************************************************************************** initialise default vfs hooks ****************************************************************************/ static BOOL vfs_init_default(connection_struct *conn) { DEBUG(3, ("Initialising default vfs hooks\n")); memcpy(&conn->vfs_ops, &default_vfs_ops, sizeof(struct vfs_ops)); return True; } /**************************************************************************** initialise custom vfs hooks ****************************************************************************/ #ifdef HAVE_LIBDL static BOOL vfs_init_custom(connection_struct *conn) { int vfs_version = -1; struct vfs_ops *ops, *(*init_fptr)(int *, struct vfs_ops *); DEBUG(3, ("Initialising custom vfs hooks from %s\n", lp_vfsobj(SNUM(conn)))); /* Open object file */ if ((conn->dl_handle = sys_dlopen(lp_vfsobj(SNUM(conn)), RTLD_NOW | RTLD_GLOBAL)) == NULL) { DEBUG(0, ("Error opening %s: %s\n", lp_vfsobj(SNUM(conn)), sys_dlerror())); return False; } /* Get handle on vfs_init() symbol */ init_fptr = (struct vfs_ops *(*)(int *, struct vfs_ops *))sys_dlsym(conn->dl_handle, "vfs_init"); if (init_fptr == NULL) { DEBUG(0, ("No vfs_init() symbol found in %s\n", lp_vfsobj(SNUM(conn)))); return False; } /* Initialise vfs_ops structure */ conn->vfs_ops = default_vfs_ops; if ((ops = init_fptr(&vfs_version, &conn->vfs_ops)) == NULL) { DEBUG(0, ("vfs_init function from %s failed\n", lp_vfsobj(SNUM(conn)))); return False; } if (vfs_version != SMB_VFS_INTERFACE_VERSION) { DEBUG(0, ("vfs_init returned wrong interface version info (was %d, should be %d)\n", vfs_version, SMB_VFS_INTERFACE_VERSION )); return False; } if (ops != &conn->vfs_ops) { memcpy(&conn->vfs_ops, ops, sizeof(struct vfs_ops)); } return True; } #endif /***************************************************************** Generic VFS init. ******************************************************************/ BOOL vfs_init(connection_struct *conn) { if (*lp_vfsobj(SNUM(conn))) { #ifdef HAVE_LIBDL /* Loadable object file */ if (!vfs_init_custom(conn)) { DEBUG(0, ("vfs_init: vfs_init_custom failed\n")); return False; } return True; #else DEBUG(0, ("vfs_init: No libdl present - cannot use VFS objects\n")); return False; #endif } /* Normal share - initialise with disk access functions */ return vfs_init_default(conn); } /******************************************************************* Check if directory exists. ********************************************************************/ BOOL vfs_directory_exist(connection_struct *conn, const char *dname, SMB_STRUCT_STAT *st) { SMB_STRUCT_STAT st2; BOOL ret; if (!st) st = &st2; if (vfs_stat(conn,dname,st) != 0) return(False); ret = S_ISDIR(st->st_mode); if(!ret) errno = ENOTDIR; return ret; } /******************************************************************* vfs getwd wrapper ********************************************************************/ char *vfs_getwd(connection_struct *conn, char *path) { return conn->vfs_ops.getwd(conn,path); } /******************************************************************* vfs mkdir wrapper ********************************************************************/ int vfs_mkdir(connection_struct *conn, const char *name, mode_t mode) { int ret; SMB_STRUCT_STAT sbuf; if(!(ret=conn->vfs_ops.mkdir(conn,name,mode))) { /* * Check if high bits should have been set, * then (if bits are missing): add them. * Consider bits automagically set by UNIX, i.e. SGID bit from parent dir. */ if(mode & ~(S_IRWXU|S_IRWXG|S_IRWXO) && !vfs_stat(conn,name,&sbuf) && (mode & ~sbuf.st_mode)) vfs_chmod(conn,name,sbuf.st_mode | (mode & ~sbuf.st_mode)); } return ret; } /******************************************************************* Check if a vfs file exists. ********************************************************************/ BOOL vfs_file_exist(connection_struct *conn,const char *fname,SMB_STRUCT_STAT *sbuf) { SMB_STRUCT_STAT st; if (!sbuf) sbuf = &st; ZERO_STRUCTP(sbuf); if (vfs_stat(conn,fname,sbuf) != 0) return(False); return(S_ISREG(sbuf->st_mode)); } /**************************************************************************** Read data from fsp on the vfs. (note: EINTR re-read differs from vfs_write_data) ****************************************************************************/ ssize_t vfs_read_data(files_struct *fsp, char *buf, size_t byte_count) { size_t total=0; while (total < byte_count) { ssize_t ret = fsp->conn->vfs_ops.read(fsp, fsp->fd, buf + total, byte_count - total); if (ret == 0) return total; if (ret == -1) { if (errno == EINTR) continue; else return -1; } total += ret; } return (ssize_t)total; } /**************************************************************************** Write data to a fd on the vfs. ****************************************************************************/ ssize_t vfs_write_data(files_struct *fsp,const char *buffer,size_t N) { size_t total=0; ssize_t ret; while (total < N) { ret = fsp->conn->vfs_ops.write(fsp,fsp->fd,buffer + total,N - total); if (ret == -1) return -1; if (ret == 0) return total; total += ret; } return (ssize_t)total; } /**************************************************************************** An allocate file space call using the vfs interface. Allocates space for a file from a filedescriptor. Returns 0 on success, -1 on failure. ****************************************************************************/ int vfs_allocate_file_space(files_struct *fsp, SMB_OFF_T len) { int ret; SMB_STRUCT_STAT st; struct vfs_ops *vfs_ops = &fsp->conn->vfs_ops; if (!lp_strict_allocate(SNUM(fsp->conn))) return vfs_set_filelen(fsp, len); release_level_2_oplocks_on_change(fsp); /* * Actually try and commit the space on disk.... */ DEBUG(10,("vfs_allocate_file_space: file %s, len %.0f\n", fsp->fsp_name, (double)len )); ret = vfs_fstat(fsp,fsp->fd,&st); if (ret == -1) return ret; if (len == st.st_size) return 0; if (len < st.st_size) { /* Shrink - use ftruncate. */ DEBUG(10,("vfs_allocate_file_space: file %s, shrink. Current size %.0f\n", fsp->fsp_name, (double)st.st_size )); if ((ret = vfs_ops->ftruncate(fsp, fsp->fd, len)) != -1) { set_filelen_write_cache(fsp, len); } return ret; } /* Grow - we need to write out the space.... */ { static unsigned char zero_space[65536]; SMB_OFF_T start_pos = st.st_size; SMB_OFF_T len_to_write = len - st.st_size; SMB_OFF_T retlen; DEBUG(10,("vfs_allocate_file_space: file %s, grow. Current size %.0f\n", fsp->fsp_name, (double)st.st_size )); if ((retlen = vfs_ops->lseek(fsp, fsp->fd, start_pos, SEEK_SET)) != start_pos) return -1; while ( len_to_write > 0) { SMB_OFF_T current_len_to_write = MIN(sizeof(zero_space),len_to_write); retlen = vfs_ops->write(fsp,fsp->fd,zero_space,current_len_to_write); if (retlen <= 0) { /* Write fail - return to original size. */ int save_errno = errno; fsp->conn->vfs_ops.ftruncate(fsp, fsp->fd, st.st_size); errno = save_errno; DEBUG(10,("vfs_allocate_file_space: file %s, grow. write fail %s\n", fsp->fsp_name, strerror(errno) )); return -1; } DEBUG(10,("vfs_allocate_file_space: file %s, grow. wrote %.0f\n", fsp->fsp_name, (double)current_len_to_write )); len_to_write -= retlen; } set_filelen_write_cache(fsp, len); } return 0; } /**************************************************************************** A vfs set_filelen call. set the length of a file from a filedescriptor. Returns 0 on success, -1 on failure. ****************************************************************************/ int vfs_set_filelen(files_struct *fsp, SMB_OFF_T len) { int ret; release_level_2_oplocks_on_change(fsp); if ((ret = fsp->conn->vfs_ops.ftruncate(fsp, fsp->fd, len)) != -1) set_filelen_write_cache(fsp, len); return ret; } /**************************************************************************** Transfer some data between two file_struct's. ****************************************************************************/ SMB_OFF_T vfs_transfer_file(int in_fd, files_struct *in_fsp, int out_fd, files_struct *out_fsp, SMB_OFF_T n, char *header, int headlen, int align) { static char *buf=NULL; static int size=0; char *buf1,*abuf; SMB_OFF_T total = 0; DEBUG(4,("vfs_transfer_file n=%.0f (head=%d) called\n",(double)n,headlen)); /* Check we have at least somewhere to read from */ SMB_ASSERT((in_fd != -1) || (in_fsp != NULL)); if (size == 0) { size = lp_readsize(); size = MAX(size,1024); } while (!buf && size>0) { buf = (char *)Realloc(buf,size+8); if (!buf) size /= 2; } if (!buf) { DEBUG(0,("Can't allocate transfer buffer!\n")); exit(1); } abuf = buf + (align%8); if (header) n += headlen; while (n > 0) { int s = (int)MIN(n,(SMB_OFF_T)size); int ret,ret2=0; ret = 0; if (header && (headlen >= MIN(s,1024))) { buf1 = header; s = headlen; ret = headlen; headlen = 0; header = NULL; } else { buf1 = abuf; } if (header && headlen > 0) { ret = MIN(headlen,size); memcpy(buf1,header,ret); headlen -= ret; header += ret; if (headlen <= 0) header = NULL; } if (s > ret) { ret += in_fsp ? in_fsp->conn->vfs_ops.read(in_fsp,in_fsp->fd,buf1+ret,s-ret) : read(in_fd,buf1+ret,s-ret); } if (ret > 0) { if (out_fsp) ret2 = out_fsp->conn->vfs_ops.write(out_fsp,out_fsp->fd,buf1,ret); else ret2= (out_fd != -1) ? write_data(out_fd,buf1,ret) : ret; } if (ret2 > 0) total += ret2; /* if we can't write then dump excess data */ if (ret2 != ret) vfs_transfer_file(in_fd, in_fsp, -1,NULL,n-(ret+headlen),NULL,0,0); if (ret <= 0 || ret2 != ret) return(total); n -= ret; } return(total); } /******************************************************************* A vfs_readdir wrapper which just returns the file name. ********************************************************************/ char *vfs_readdirname(connection_struct *conn, void *p) { struct dirent *ptr; char *dname; if (!p) return(NULL); ptr = (struct dirent *)conn->vfs_ops.readdir(conn,p); if (!ptr) return(NULL); dname = ptr->d_name; #ifdef NEXT2 if (telldir(p) < 0) return(NULL); #endif #ifdef HAVE_BROKEN_READDIR /* using /usr/ucb/cc is BAD */ dname = dname - 2; #endif return(dname); } /* VFS options not quite working yet */ #if 0 /*************************************************************************** handle the interpretation of the vfs option parameter *************************************************************************/ static BOOL handle_vfs_option(char *pszParmValue, char **ptr) { struct vfs_options *new_option, **options = (struct vfs_options **)ptr; int i; /* Create new vfs option */ new_option = (struct vfs_options *)malloc(sizeof(*new_option)); if (new_option == NULL) { return False; } ZERO_STRUCTP(new_option); /* Get name and value */ new_option->name = strtok(pszParmValue, "="); if (new_option->name == NULL) { return False; } while(isspace(*new_option->name)) { new_option->name++; } for (i = strlen(new_option->name); i > 0; i--) { if (!isspace(new_option->name[i - 1])) break; } new_option->name[i] = '\0'; new_option->name = strdup(new_option->name); new_option->value = strtok(NULL, "="); if (new_option->value != NULL) { while(isspace(*new_option->value)) { new_option->value++; } for (i = strlen(new_option->value); i > 0; i--) { if (!isspace(new_option->value[i - 1])) break; } new_option->value[i] = '\0'; new_option->value = strdup(new_option->value); } /* Add to list */ DLIST_ADD(*options, new_option); return True; } #endif /******************************************************************* A wrapper for vfs_chdir(). ********************************************************************/ int vfs_ChDir(connection_struct *conn, char *path) { int res; static pstring LastDir=""; if (strcsequal(path,".")) return(0); if (*path == '/' && strcsequal(LastDir,path)) return(0); DEBUG(3,("vfs_ChDir to %s\n",path)); res = vfs_chdir(conn,path); if (!res) pstrcpy(LastDir,path); return(res); } /* number of list structures for a caching GetWd function. */ #define MAX_GETWDCACHE (50) struct { SMB_DEV_T dev; /* These *must* be compatible with the types returned in a stat() call. */ SMB_INO_T inode; /* These *must* be compatible with the types returned in a stat() call. */ char *dos_path; /* The pathname in DOS format. */ BOOL valid; } ino_list[MAX_GETWDCACHE]; extern BOOL use_getwd_cache; /**************************************************************************** Prompte a ptr (to make it recently used) ****************************************************************************/ static void array_promote(char *array,int elsize,int element) { char *p; if (element == 0) return; p = (char *)malloc(elsize); if (!p) { DEBUG(5,("array_promote: malloc fail\n")); return; } memcpy(p,array + element * elsize, elsize); memmove(array + elsize,array,elsize*element); memcpy(array,p,elsize); free(p); } /******************************************************************* Return the absolute current directory path - given a UNIX pathname. Note that this path is returned in DOS format, not UNIX format. Note this can be called with conn == NULL. ********************************************************************/ char *vfs_GetWd(connection_struct *conn, char *path) { pstring s; static BOOL getwd_cache_init = False; SMB_STRUCT_STAT st, st2; int i; *s = 0; if (!use_getwd_cache) return(vfs_getwd(conn,path)); /* init the cache */ if (!getwd_cache_init) { getwd_cache_init = True; for (i=0;i