/* $Id$ * lufs interface module file objects implementation for libcaptive * Copyright (C) 2003 Jan Kratochvil * * 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; exactly version 2 of June 1991 is required * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "config.h" #include #include "captivefs-misc.h" #include "captivefs-attr.h" #include "captivefs-vfs.h" #include #include #include #include #include /* Config: */ #define CLIENT_LUFS_USE_COUNT "client-lufs-use_count" /* map: (const gchar *utf8) -> (CaptiveFileObject *) */ static GHashTable *FileHandle_hash; G_LOCK_DEFINE_STATIC(FileHandle_hash); static void FileHandle_hash_init(void) { G_LOCK(FileHandle_hash); if (!FileHandle_hash) { FileHandle_hash=g_hash_table_new_full(g_str_hash,g_str_equal, (GDestroyNotify)g_free, /* key_destroy_func */ (GDestroyNotify)NULL); /* value_destroy_func; handled by FileHandle_hash_captive_file_object_weak_notify() */ } G_UNLOCK(FileHandle_hash); } /* Do not: GObject->ref_count * as 'ref_count' may be increased for deleted files by ParentConnector * until the volume gets unmounted. Therefore any deleted file would not * be removed from our 'FileHandle_hash' and no object would be reinstantiable * under the original deleted name. */ static gint FileHandle_get_use_count(CaptiveFileObject *captive_file_object) { g_return_val_if_fail(CAPTIVE_FILE_IS_OBJECT(captive_file_object),0); return GPOINTER_TO_INT(g_object_get_data(G_OBJECT(captive_file_object),CLIENT_LUFS_USE_COUNT)); } static void FileHandle_set_use_count(CaptiveFileObject *captive_file_object,gint use_count) { g_return_if_fail(CAPTIVE_FILE_IS_OBJECT(captive_file_object)); g_object_set_data(G_OBJECT(captive_file_object),CLIENT_LUFS_USE_COUNT,GINT_TO_POINTER(use_count)); } /* FileHandle_hash_init(); must be executed! */ /* G_LOCK(FileHandle_hash); must be held! */ static void FileHandle_enter (struct captivefs_vfs *captivefs_vfs,CaptiveFileObject *captive_file_object,const gchar *name_normalized) { gint use_count; g_return_if_fail(captivefs_vfs_validate(captivefs_vfs)); g_return_if_fail(CAPTIVE_FILE_IS_OBJECT(captive_file_object)); g_return_if_fail(name_normalized); use_count=FileHandle_get_use_count(captive_file_object); if (captivefs_vfs->options.debug_messages) g_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"FileHandle_enter: name_normalized=%s, use_count++=%d", name_normalized,(int)use_count); g_assert(use_count>=0); use_count++; FileHandle_set_use_count(captive_file_object,use_count); if (use_count>1) { g_assert(captive_file_object==g_hash_table_lookup(FileHandle_hash,name_normalized)); return; } g_assert(NULL==g_hash_table_lookup(FileHandle_hash,name_normalized)); g_object_ref(captive_file_object); g_hash_table_insert(FileHandle_hash,g_strdup(name_normalized),captive_file_object); } /* FileHandle_hash_init(); must be executed! */ /* G_LOCK(FileHandle_hash); must be held! */ /* G_LOCK(libcaptive); must NOT be held! */ static void FileHandle_leave_locked (struct captivefs_vfs *captivefs_vfs,CaptiveFileObject *captive_file_object,const gchar *name) { gboolean errbool; gint use_count; gchar *name_normalized; g_return_if_fail(captivefs_vfs_validate(captivefs_vfs)); g_return_if_fail(CAPTIVE_FILE_IS_OBJECT(captive_file_object)); g_return_if_fail(name); name_normalized=captive_path_normalize(name); use_count=FileHandle_get_use_count(captive_file_object); if (captivefs_vfs->options.debug_messages) g_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"FileHandle_leave_locked: name_normalized=%s, use_count--=%d", name_normalized,(int)use_count); g_assert(use_count>=1); use_count--; FileHandle_set_use_count(captive_file_object,use_count); g_assert(captive_file_object==g_hash_table_lookup(FileHandle_hash,name_normalized)); if (use_count>=1) { g_free(name_normalized); return; } errbool=g_hash_table_remove(FileHandle_hash,name_normalized); g_assert(errbool==TRUE); G_LOCK(libcaptive); g_object_unref(captive_file_object); G_UNLOCK(libcaptive); g_assert(NULL==g_hash_table_lookup(FileHandle_hash,name_normalized)); g_free(name_normalized); } /* FileHandle_hash_init(); must be executed! */ /* G_LOCK(FileHandle_hash); must NOT be held! */ /* G_LOCK(libcaptive); must NOT be held! */ static void FileHandle_leave(struct captivefs_vfs *captivefs_vfs,CaptiveFileObject *captive_file_object,const gchar *name) { g_return_if_fail(captivefs_vfs_validate(captivefs_vfs)); g_return_if_fail(CAPTIVE_FILE_IS_OBJECT(captive_file_object)); g_return_if_fail(name); G_LOCK(FileHandle_hash); FileHandle_leave_locked(captivefs_vfs,captive_file_object,name); G_UNLOCK(FileHandle_hash); } /* 'name' is required to exist. */ static CaptiveFileObject *FileHandle_lookup_enter(struct captivefs_vfs *captivefs_vfs,const char *name) { CaptiveFileObject *captive_file_object; gchar *name_normalized; g_return_val_if_fail(captivefs_vfs_validate(captivefs_vfs),NULL); g_return_val_if_fail(name!=NULL,NULL); name_normalized=captive_path_normalize(name); FileHandle_hash_init(); G_LOCK(FileHandle_hash); captive_file_object=g_hash_table_lookup(FileHandle_hash,name_normalized); if (!captive_file_object) { G_UNLOCK(FileHandle_hash); g_warning("FileHandle_lookup_enter: FileHandle not found of: %s",name_normalized); g_free(name_normalized); return NULL; } g_assert(CAPTIVE_FILE_IS_OBJECT(captive_file_object)); FileHandle_enter(captivefs_vfs,captive_file_object,name_normalized); G_UNLOCK(FileHandle_hash); g_free(name_normalized); return captive_file_object; } /* 'name' will be opened if needed. */ static CaptiveFileObject *FileHandle_lookup_open_enter(struct captivefs_vfs *captivefs_vfs,const char *name,gboolean create) { CaptiveFileObject *captive_file_object; GnomeVFSResult errvfsresult; gchar *name_normalized; g_return_val_if_fail(captivefs_vfs_validate(captivefs_vfs),NULL); g_return_val_if_fail(name!=NULL,NULL); name_normalized=captive_path_normalize(name); FileHandle_hash_init(); G_LOCK(FileHandle_hash); captive_file_object=g_hash_table_lookup(FileHandle_hash,name_normalized); if (!create && captive_file_object) { g_assert(CAPTIVE_FILE_IS_OBJECT(captive_file_object)); FileHandle_enter(captivefs_vfs,captive_file_object,name_normalized); G_UNLOCK(FileHandle_hash); g_free(name_normalized); return captive_file_object; } if ( create && captive_file_object) { g_assert(CAPTIVE_FILE_IS_OBJECT(captive_file_object)); FileHandle_enter(captivefs_vfs,captive_file_object,name_normalized); G_UNLOCK(FileHandle_hash); g_free(name_normalized); return NULL; } /* FIXME: Respect 'gnome_vfs_open_mode'. * We would have to do some reopens of files already contained in 'FileHandle_hash'. * As W32 filesystem will allow us to open file 'GNOME_VFS_OPEN_WRITE' * even on read-only media we use the full open permissions always. */ if (!create) { G_LOCK(libcaptive); errvfsresult=captive_file_new_open(&captive_file_object,captivefs_vfs->captive_vfs_object,name_normalized, GNOME_VFS_OPEN_READ|GNOME_VFS_OPEN_WRITE|GNOME_VFS_OPEN_RANDOM); G_UNLOCK(libcaptive); /* HIDDEN SYSTEM files (FIXME: or just HIDDEN or just SYSTEM?) * refuse to be GNOME_VFS_OPEN_WRITE-opened. */ if (errvfsresult==GNOME_VFS_ERROR_ACCESS_DENIED) { G_LOCK(libcaptive); errvfsresult=captive_file_new_open(&captive_file_object,captivefs_vfs->captive_vfs_object,name_normalized, GNOME_VFS_OPEN_READ|GNOME_VFS_OPEN_RANDOM); G_UNLOCK(libcaptive); } } else { G_LOCK(libcaptive); errvfsresult=captive_file_new_create(&captive_file_object,captivefs_vfs->captive_vfs_object,name_normalized, GNOME_VFS_OPEN_READ|GNOME_VFS_OPEN_WRITE|GNOME_VFS_OPEN_RANDOM, /* mode */ FALSE, /* exclusive */ 0600); /* perm */ G_UNLOCK(libcaptive); } if (errvfsresult!=GNOME_VFS_OK) { G_UNLOCK(FileHandle_hash); g_free(name_normalized); return NULL; } FileHandle_enter(captivefs_vfs,captive_file_object,name_normalized); G_UNLOCK(FileHandle_hash); g_object_unref(captive_file_object); /* for captive_file_new_open() / captive_file_new_create() */ g_free(name_normalized); return captive_file_object; } /* Read a file/dir's attributes * Fill all relevant data into the fattr structure. * The uid/gid fields are just ownership hints hints: * != 0 => we own the file * == 0 => we don't own it * The credentials structure (if applicable and saved from _init) * can help determine ownership based on remote uids/gids. * * Notes: * If your filesysem doesn't natively support '.' or '..', * don't forget to special-case them here. * It is best to assume that name is a relative path, not an * absolute one. Thus, you need to either be keeping track of the * last accessed directory in readdir, or, as this code does, changing * to the current directory there. */ int captivefs_stat(struct captivefs_vfs *captivefs_vfs,const char *name,struct lufs_fattr *fattr) { GnomeVFSResult errvfsresult; CaptiveFileObject *captive_file_object; GnomeVFSFileInfo file_info; g_return_val_if_fail(captivefs_vfs_validate(captivefs_vfs),-1); g_return_val_if_fail(name!=NULL,-1); g_return_val_if_fail(fattr!=NULL,-1); if (captivefs_vfs->options.debug_messages) g_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"captivefs_stat: name=%s",name); name=CAPTIVEFS_FILENAME_TO_UTF8_ALLOCA(name); captive_file_object=FileHandle_lookup_open_enter(captivefs_vfs,name,FALSE); if (!captive_file_object) return -1; G_LOCK(libcaptive); errvfsresult=captive_file_file_info_get(captive_file_object,&file_info); G_UNLOCK(libcaptive); FileHandle_leave(captivefs_vfs,captive_file_object,name); if (errvfsresult!=GNOME_VFS_OK) return -1; if (!captivefs_GnomeVFSFileInfo_to_lufs_fattr(captivefs_vfs,fattr,&file_info)) return -1; return 0; } /* Create a file */ int captivefs_create(struct captivefs_vfs *captivefs_vfs,const char *file,int mode) { CaptiveFileObject *captive_file_object; g_return_val_if_fail(captivefs_vfs_validate(captivefs_vfs),-1); g_return_val_if_fail(file!=NULL,-1); if (captivefs_vfs->options.debug_messages) g_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"captivefs_create: file=%s,mode=0x%X",file,mode); file=CAPTIVEFS_FILENAME_TO_UTF8_ALLOCA(file); captive_file_object=FileHandle_lookup_open_enter(captivefs_vfs,file,TRUE); if (!captive_file_object) return -1; FileHandle_leave(captivefs_vfs,captive_file_object,file); return 0; } /* Delete a file */ int captivefs_unlink(struct captivefs_vfs *captivefs_vfs,const char *file) { GnomeVFSResult errvfsresult; CaptiveFileObject *captive_file_object; g_return_val_if_fail(captivefs_vfs_validate(captivefs_vfs),-1); g_return_val_if_fail(file!=NULL,-1); if (captivefs_vfs->options.debug_messages) g_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"captivefs_unlink: file=%s",file); file=CAPTIVEFS_FILENAME_TO_UTF8_ALLOCA(file); captive_file_object=FileHandle_lookup_open_enter(captivefs_vfs,file,FALSE); if (!captive_file_object) return -1; G_LOCK(libcaptive); errvfsresult=captive_file_remove(captive_file_object); G_UNLOCK(libcaptive); FileHandle_leave(captivefs_vfs,captive_file_object,file); if (errvfsresult!=GNOME_VFS_OK) return -1; return 0; } /* Rename a file/dir */ int captivefs_rename(struct captivefs_vfs *captivefs_vfs,const char *old_name,const char *new_name) { GnomeVFSResult errvfsresult; CaptiveFileObject *captive_file_object; gint use_count; g_return_val_if_fail(captivefs_vfs_validate(captivefs_vfs),-1); g_return_val_if_fail(old_name!=NULL,-1); g_return_val_if_fail(new_name!=NULL,-1); if (captivefs_vfs->options.debug_messages) g_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"captivefs_rename: old_name=%s,new_name=%s",old_name,new_name); old_name=CAPTIVEFS_FILENAME_TO_UTF8_ALLOCA(old_name); new_name=CAPTIVEFS_FILENAME_TO_UTF8_ALLOCA(new_name); captive_file_object=FileHandle_lookup_open_enter(captivefs_vfs,old_name,FALSE); if (!captive_file_object) return -1; G_LOCK(FileHandle_hash); use_count=FileHandle_get_use_count(captive_file_object); g_assert(use_count>=1); if (use_count!=1) { if (captivefs_vfs->options.debug_messages) g_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"captivefs_rename: old_name=%s,new_name=%s: BUSY use_count=%d", old_name,new_name,(int)use_count); G_UNLOCK(FileHandle_hash); FileHandle_leave(captivefs_vfs,captive_file_object,old_name); return -1; } G_LOCK(libcaptive); errvfsresult=captive_file_move(captive_file_object,new_name, FALSE); /* force_replace */ G_UNLOCK(libcaptive); FileHandle_leave_locked(captivefs_vfs,captive_file_object,old_name); G_UNLOCK(FileHandle_hash); if (errvfsresult!=GNOME_VFS_OK) return -1; return 0; } /* Open a file * * Notes: * By default, LUFS has no concept of file handles. To implement file * handles, take a look at the atbl class in sshfs - it is easy to cut&paste * for use, and can be easily adapted for whatever purpose you need handles * for. * * Unlike the POSIX open command which has both a "mode" variable and * a "flags" variable, this only has a "mode" variable. To convert to the * POSIX version, ->flags=mode^O_ACCMODE and ->mode=mode&O_ACCMODE. */ int captivefs_open(struct captivefs_vfs *captivefs_vfs,const char *file,unsigned mode) { CaptiveFileObject *captive_file_object; GnomeVFSOpenMode gnome_vfs_open_mode; /* We may be called from the parent. */ g_return_val_if_fail(captivefs_vfs!=NULL,FALSE); g_return_val_if_fail(file!=NULL,-1); if (captivefs_vfs->options.debug_messages) g_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"captivefs_open: file=%s,mode=0x%X",file,mode); file=CAPTIVEFS_FILENAME_TO_UTF8_ALLOCA(file); if (!captivefs_vfs_validate(captivefs_vfs)) return -1; if (mode & ~(O_RDONLY|O_WRONLY|O_RDWR)) { g_warning("captivefs_open: Unrecognized 'mode' 0x%X",mode); return -1; } switch (mode & (O_RDONLY|O_WRONLY|O_RDWR)) { case O_RDONLY: gnome_vfs_open_mode=GNOME_VFS_OPEN_READ; break; case O_WRONLY: gnome_vfs_open_mode=GNOME_VFS_OPEN_WRITE; break; case O_RDWR: gnome_vfs_open_mode=GNOME_VFS_OPEN_READ|GNOME_VFS_OPEN_WRITE; break; default: g_warning("captivefs_open: Unrecognized 'mode' 0x%X",mode); return -1; } captive_file_object=FileHandle_lookup_open_enter(captivefs_vfs,file,FALSE); if (!captive_file_object) return -1; /* Leave 'captive_file_object' entered. */ return 0; } int captivefs_release(struct captivefs_vfs *captivefs_vfs,const char *file) { CaptiveFileObject *captive_file_object; g_return_val_if_fail(captivefs_vfs_validate(captivefs_vfs),-1); g_return_val_if_fail(file!=NULL,-1); if (captivefs_vfs->options.debug_messages) g_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"captivefs_release: file=%s",file); file=CAPTIVEFS_FILENAME_TO_UTF8_ALLOCA(file); if (!(captive_file_object=FileHandle_lookup_enter(captivefs_vfs,file))) return -1; FileHandle_leave(captivefs_vfs,captive_file_object,file); /* for FileHandle_lookup_enter() */ FileHandle_leave(captivefs_vfs,captive_file_object,file); return 0; } /* Read from a file. Changed to use the (2) routines not for efficiency, * but to make it work with 64-bit offsets :-(. */ int captivefs_read(struct captivefs_vfs *captivefs_vfs,const char *file,long long offset,unsigned long count,void *buf) { CaptiveFileObject *captive_file_object; GnomeVFSFileSize bytes_read; GnomeVFSResult errvfsresult; g_return_val_if_fail(captivefs_vfs_validate(captivefs_vfs),-1); g_return_val_if_fail(file!=NULL,-1); g_return_val_if_fail(buf!=NULL || count==0,-1); if (captivefs_vfs->options.debug_messages) g_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"captivefs_read: file=%s,offset=%" G_GINT64_FORMAT ",count=0x%lX", file,(gint64)offset,count); file=CAPTIVEFS_FILENAME_TO_UTF8_ALLOCA(file); if (!(captive_file_object=FileHandle_lookup_enter(captivefs_vfs,file))) return -1; /* Do not unlock 'libcaptive' between seek() and read()! */ G_LOCK(libcaptive); errvfsresult=captive_file_seek(captive_file_object,GNOME_VFS_SEEK_START,offset); if (errvfsresult!=GNOME_VFS_OK) { G_UNLOCK(libcaptive); FileHandle_leave(captivefs_vfs,captive_file_object,file); return -1; } errvfsresult=captive_file_read(captive_file_object,buf,count,&bytes_read); G_UNLOCK(libcaptive); FileHandle_leave(captivefs_vfs,captive_file_object,file); if (errvfsresult!=GNOME_VFS_OK) return -1; if (bytes_read>INT_MAX) { g_warning("captivefs_read: Read %" G_GUINT64_FORMAT " bytes > INT_MAX=%d; dropped data", (guint64)bytes_read,INT_MAX); bytes_read=INT_MAX; } return bytes_read; } /* Write to a file */ int captivefs_write(struct captivefs_vfs *captivefs_vfs,const char *file,long long offset,unsigned long count,const void *buf) { CaptiveFileObject *captive_file_object; GnomeVFSFileSize bytes_written; GnomeVFSResult errvfsresult; g_return_val_if_fail(captivefs_vfs_validate(captivefs_vfs),-1); g_return_val_if_fail(file!=NULL,-1); g_return_val_if_fail(buf!=NULL || count==0,-1); if (captivefs_vfs->options.debug_messages) g_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"captivefs_write: file=%s,offset=%" G_GINT64_FORMAT ",count=0x%lX", file,(gint64)offset,count); file=CAPTIVEFS_FILENAME_TO_UTF8_ALLOCA(file); if (!(captive_file_object=FileHandle_lookup_enter(captivefs_vfs,file))) return -1; /* Do not unlock 'libcaptive' between seek() and write()! */ G_LOCK(libcaptive); errvfsresult=captive_file_seek(captive_file_object,GNOME_VFS_SEEK_START,offset); if (errvfsresult!=GNOME_VFS_OK) { G_UNLOCK(libcaptive); FileHandle_leave(captivefs_vfs,captive_file_object,file); return -1; } errvfsresult=captive_file_write(captive_file_object,buf,count,&bytes_written); G_UNLOCK(libcaptive); FileHandle_leave(captivefs_vfs,captive_file_object,file); if (errvfsresult!=GNOME_VFS_OK) return -1; if (bytes_written>INT_MAX) { g_warning("captivefs_written: Written %" G_GUINT64_FORMAT " bytes > INT_MAX=%d; dropped data", (guint64)bytes_written,INT_MAX); bytes_written=INT_MAX; } return bytes_written; } /* Change a file/dir's attributes */ int captivefs_setattr(struct captivefs_vfs *captivefs_vfs,const char *file,const struct lufs_fattr *fattr) { GnomeVFSFileInfo file_info; GnomeVFSResult errvfsresult; CaptiveFileObject *captive_file_object; g_return_val_if_fail(captivefs_vfs_validate(captivefs_vfs),-1); g_return_val_if_fail(file!=NULL,-1); g_return_val_if_fail(fattr!=NULL,-1); if (captivefs_vfs->options.debug_messages) g_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"captivefs_setattr: file=%s",file); file=CAPTIVEFS_FILENAME_TO_UTF8_ALLOCA(file); if (!captivefs_lufs_fattr_to_GnomeVFSFileInfo(&file_info,fattr)) return -1; captive_file_object=FileHandle_lookup_open_enter(captivefs_vfs,file,FALSE); if (!captive_file_object) return -1; G_LOCK(libcaptive); errvfsresult=captive_file_file_info_set(captive_file_object,&file_info, 0 | 0 /* 'GNOME_VFS_SET_FILE_INFO_NAME' is never set */ | (!(file_info.valid_fields&GNOME_VFS_FILE_INFO_FIELDS_PERMISSIONS) ? 0 : GNOME_VFS_SET_FILE_INFO_PERMISSIONS) | 0 /* 'GNOME_VFS_SET_FILE_INFO_OWNER' is never set */ | (!(file_info.valid_fields&(0 | GNOME_VFS_FILE_INFO_FIELDS_ATIME | GNOME_VFS_FILE_INFO_FIELDS_MTIME | GNOME_VFS_FILE_INFO_FIELDS_CTIME)) ? 0 : GNOME_VFS_SET_FILE_INFO_TIME)); G_UNLOCK(libcaptive); G_LOCK(libcaptive); if (errvfsresult==GNOME_VFS_OK) errvfsresult=captive_file_truncate(captive_file_object,fattr->f_size); G_UNLOCK(libcaptive); FileHandle_leave(captivefs_vfs,captive_file_object,file); if (errvfsresult!=GNOME_VFS_OK) return -1; return 0; }