/* libarchive plugin for Tux Commander * version 0.2.2, designed for libarchive v2.5.5 - v2.7.1 (recommended) * Copyright (C) 2008-2009 Tomas Bzatek * Check for updates on tuxcmd.sourceforge.net * * Uses libarchive library * Copyright (c) 2003-2007 Tim Kientzle * http://code.google.com/p/libarchive/ * * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include "tuxcmd-vfs.h" #include "vfsutils.h" #include "strutils.h" #include "filelist.h" #include "filelist-vfs-intf.h" #include #include #ifndef MODULE_SHARED # include #endif #if ARCHIVE_VERSION_NUMBER >= 3000000 #error "This module is designed for libarchive 2.x series only." #endif #if ARCHIVE_VERSION_NUMBER < 2005005 #error "libarchive 2.5.5 or greater is required to compile this module." #endif #define MODULE_VERSION "0.2.2" #define MODULE_BUILD_DATE "2009-12-12" #define DEFAULT_BLOCK_SIZE 65536 /******************************************************************************************************/ /** Auxiliary classes */ /************** ****************/ /* declaration of the global plugin object */ struct TVFSGlobs { TVFSLogFunc log_func; char *curr_dir; char *archive_path; guint32 block_size; struct PathTree *files; struct VfsFilelistData *vfs_filelist; guint64 total_size; TVFSAskQuestionCallback callback_ask_question; TVFSAskPasswordCallback callback_ask_password; TVFSProgressCallback callback_progress; void *callback_data; /* copy operation private */ struct archive *op_archive; }; /******************************************************************************************************/ /** Basic initialization functions */ /************** ****************/ struct TVFSGlobs * VFSNew (TVFSLogFunc log_func) { struct TVFSGlobs * globs; globs = g_malloc0 (sizeof (struct TVFSGlobs)); globs->block_size = DEFAULT_BLOCK_SIZE; globs->callback_data = NULL; globs->callback_ask_question = NULL; globs->callback_ask_password = NULL; globs->callback_progress = NULL; globs->op_archive = NULL; globs->log_func = log_func; if (globs->log_func != NULL) globs->log_func ("libarchive plugin: VFSInit"); return globs; } void VFSFree (struct TVFSGlobs *globs) { if (globs->log_func != NULL) globs->log_func ("libarchive plugin: VFSDestroy"); g_free (globs); } void VFSSetCallbacks (struct TVFSGlobs *globs, TVFSAskQuestionCallback ask_question_callback, TVFSAskPasswordCallback ask_password_callback, TVFSProgressCallback progress_func, void *data) { globs->callback_ask_question = ask_question_callback; globs->callback_ask_password = ask_password_callback; globs->callback_progress = progress_func; globs->callback_data = data; } int VFSVersion() { return cVFSVersion; } struct TVFSInfo * VFSGetInfo() { struct TVFSInfo *module_info = g_malloc0 (sizeof (struct TVFSInfo)); #ifdef MODULE_SHARED const char *shared_module = ", dynamically linked"; #else const char *shared_module = ", statically linked"; #endif module_info->ID = g_strdup ("libarchive_plugin"); module_info->Name = g_strdup ("libarchive plugin"); module_info->About = g_strdup_printf ("version %s, build date: %s\nusing %s%s\n", MODULE_VERSION, MODULE_BUILD_DATE, ARCHIVE_LIBRARY_VERSION, shared_module); module_info->Copyright = g_strdup ("Plugin Copyright (C) 2008-2009 Tomáš Bžatek\nlibarchive sources Copyright (c) 2003-2007 Tim Kientzle"); return module_info; } guint32 VFSGetCapabilities () { return VFS_CAP_CAN_CREATE_ARCHIVES | VFS_CAP_ARCHIVE_STREAMING; } char * VFSGetArchiveExts () { /* Make sure these end with a semicolon */ #define BASIC_FORMATS "tar;cpio;iso;a;shar;" #define BZIP_FORMATS "tar.bz2;tbz2;" #define ZLIB_FORMATS "tar.gz;tgz;tar.z;deb;" #define XZ_FORMATS "tar.lzma;tar.xz;" #define LZMA_FORMATS "tar.lzma;" char *formats; #ifndef MODULE_SHARED #ifdef HAVE_BZLIB_H #define FORMAT_BZLIB BZIP_FORMATS #else #define FORMAT_BZLIB #endif #ifdef HAVE_ZLIB_H #define FORMAT_ZLIB ZLIB_FORMATS #else #define FORMAT_ZLIB #endif #if HAVE_LZMA_H && HAVE_LIBLZMA #define FORMAT_LZMA XZ_FORMATS #elif HAVE_LZMADEC_H && HAVE_LIBLZMADEC #define FORMAT_LZMA LZMA_FORMATS #else #define FORMAT_LZMA #endif formats = g_strdup (BASIC_FORMATS FORMAT_ZLIB FORMAT_BZLIB FORMAT_LZMA); #else struct archive *archive; char *s; /* Determine supported compression formats by calling each one. */ /* We ignore external program pass-through */ archive = archive_read_new (); formats = g_strdup (BASIC_FORMATS); if (archive_read_support_compression_bzip2 (archive) == ARCHIVE_OK) { s = g_strconcat (formats, BZIP_FORMATS, NULL); g_free (formats); formats = s; } if (archive_read_support_compression_gzip (archive) == ARCHIVE_OK) { s = g_strconcat (formats, ZLIB_FORMATS, NULL); g_free (formats); formats = s; } #if ARCHIVE_VERSION_NUMBER >= 2006990 if (archive_read_support_compression_xz (archive) == ARCHIVE_OK) { s = g_strconcat (formats, XZ_FORMATS, NULL); g_free (formats); formats = s; } else #endif #if ARCHIVE_VERSION_NUMBER >= 2005902 if (archive_read_support_compression_lzma (archive) == ARCHIVE_OK) { s = g_strconcat (formats, LZMA_FORMATS, NULL); g_free (formats); formats = s; } #endif archive_read_finish (archive); #endif #ifdef MODULE_SHARED g_print ("(II) dynamically linked\n"); #else g_print ("(II) statically linked\n"); #endif g_print ("(II) VFSGetArchiveExts: supported archives = '%s'\n", formats); return formats; } /**************************************************************************************************************************************/ /**************************************************************************************************************************************/ static gboolean libarchive_open (struct archive **a, const char *filename, guint32 block_size, GError **error) { int r; *a = archive_read_new (); /* Register supported formats */ archive_read_support_compression_all (*a); archive_read_support_format_all (*a); r = archive_read_open_filename (*a, filename, block_size); if (r) { fprintf (stderr, "(EE) libarchive_open: error occured when opening archive: %s\n", archive_error_string (*a)); g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (archive_errno (*a)), archive_error_string (*a)); return FALSE; } return TRUE; } gboolean VFSOpenArchive (struct TVFSGlobs *globs, const char *sName, GError **error) { struct archive *a; struct archive_entry *entry; struct TVFSItem *item; int r; char *s; guint64 inode_no; gboolean res; globs->files = filelist_tree_new (); globs->vfs_filelist = vfs_filelist_new (globs->files); globs->curr_dir = NULL; globs->archive_path = g_strdup (sName); globs->total_size = 0; fprintf (stderr, "(--) VFSOpenArchive: trying to open archive '%s'...\n", globs->archive_path); res = libarchive_open (&a, globs->archive_path, globs->block_size, error); if (res) { inode_no = 0; for (;;) { entry = NULL; r = archive_read_next_header (a, &entry); if (r == ARCHIVE_EOF) { break; } else if (r == ARCHIVE_WARN) { log ("(WW) VFSOpenArchive: file '%s' - libarchive warning: '%s'\n", archive_entry_pathname (entry), archive_error_string (a)); } else if (r != ARCHIVE_OK) { fprintf (stderr, "(EE) VFSOpenArchive: error occured while reading archive: '%s'\n", archive_error_string (a)); g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (archive_errno (a)), archive_error_string (a)); res = FALSE; break; } log ("found file: %s, mode = %x\n", archive_entry_pathname (entry), archive_entry_mode (entry)); inode_no++; /* Create a TVFSItem entry and fill all info */ item = g_malloc0 (sizeof (struct TVFSItem)); item->iSize = (guint64) archive_entry_size (entry); item->iPackedSize = -1; /* no support */ item->inode_no = inode_no; globs->total_size += item->iSize; item->iMode = archive_entry_mode (entry); item->ItemType = vRegular; /* fallback */ item->IsLink = FALSE; switch (archive_entry_filetype (entry)) { case AE_IFDIR: item->ItemType = vDirectory; break; case AE_IFIFO: item->ItemType = vFifo; break; case AE_IFCHR: item->ItemType = vChardev; break; case AE_IFBLK: item->ItemType = vBlockdev; break; case AE_IFSOCK: item->ItemType = vSock; break; case AE_IFLNK: item->IsLink = TRUE; break; case AE_IFMT: item->ItemType = vOther; break; case AE_IFREG: default: item->ItemType = vRegular; break; } if (item->IsLink) item->sLinkTo = g_strdup (archive_entry_symlink (entry)); /* item->iUID = geteuid(); item->iGID = getegid(); */ item->iUID = archive_entry_uid (entry); item->iGID = archive_entry_gid (entry); item->m_time = archive_entry_mtime (entry); item->c_time = archive_entry_ctime (entry); item->a_time = archive_entry_atime (entry); /* Create valid UTF-8 filename */ if (g_utf8_validate (archive_entry_pathname (entry), -1, NULL)) s = g_strdup (archive_entry_pathname (entry)); else { if (archive_entry_pathname_w (entry)) s = wide_to_utf8 (archive_entry_pathname_w (entry)); else s = g_filename_display_name (archive_entry_pathname (entry)); } /* Add item to the global tree and continue with next file */ filelist_tree_add_item (globs->files, s, item, archive_entry_pathname (entry), inode_no); g_free (s); } archive_read_close (a); } archive_read_finish (a); fprintf (stderr, "(II) VFSOpenArchive: done. \n"); /* Resolve symlinks */ GTimer *timer = g_timer_new (); filelist_tree_resolve_symlinks (globs->files); g_print (" filelist_tree_resolve_symlinks: elapsed %d ms\n", (int) (g_timer_elapsed (timer, NULL) * 1000)); g_timer_destroy (timer); printf ("\n\nList of items:\n"); filelist_tree_print (globs->files); return res; } gboolean VFSClose (struct TVFSGlobs *globs, GError **error) { if (globs) { fprintf (stderr, "(II) VFSClose: Freeing objects...\n"); if (globs->vfs_filelist) vfs_filelist_free (globs->vfs_filelist); if (globs->files) filelist_tree_free (globs->files); g_free (globs->archive_path); g_free (globs->curr_dir); } return TRUE; } char * VFSGetPath (struct TVFSGlobs *globs) { return include_trailing_path_sep (globs->curr_dir); } void VFSGetFileSystemInfo (struct TVFSGlobs *globs, const char *APath, gint64 *FSSize, gint64 *FSFree, char **FSLabel) { if (FSSize) *FSSize = globs->total_size; if (FSFree) *FSFree = 0; if (FSLabel) *FSLabel = NULL; } /******************************************************************************************************/ gboolean VFSChangeDir (struct TVFSGlobs *globs, const char *NewPath, GError **error) { char *s; s = vfs_filelist_change_dir (globs->vfs_filelist, NewPath, error); if (s) { globs->curr_dir = s; return TRUE; } else return FALSE; } gboolean VFSGetPasswordRequired (struct TVFSGlobs *globs) { /* no support for encrypted archives in libarchive */ return FALSE; } /******************************************************************************************************/ struct TVFSItem * VFSListFirst (struct TVFSGlobs *globs, const char *sDir, gboolean FollowSymlinks, gboolean AddFullPath, GError **error) { printf ("(--) VFSListFirst: Going to list all items in '%s'\n", sDir); return vfs_filelist_list_first (globs->vfs_filelist, sDir, FollowSymlinks, AddFullPath, error); } struct TVFSItem * VFSListNext (struct TVFSGlobs *globs, GError **error) { return vfs_filelist_list_next (globs->vfs_filelist, error); } gboolean VFSListClose (struct TVFSGlobs *globs, GError **error) { return vfs_filelist_list_close (globs->vfs_filelist, error); } /******************************************************************************************************/ struct TVFSItem * VFSFileInfo (struct TVFSGlobs *globs, const char *AFileName, gboolean FollowSymlinks, gboolean AddFullPath, GError **error) { printf ("(--) VFSFileInfo: requested info for object '%s'\n", AFileName); return vfs_filelist_file_info (globs->vfs_filelist, AFileName, FollowSymlinks, AddFullPath, error); } /******************************************************************************************************/ /** Recursive tree size counting */ /************** ****************/ guint64 VFSGetDirSize (struct TVFSGlobs *globs, const char *APath) { if (! globs) return 0; return vfs_filelist_get_dir_size (globs->vfs_filelist, APath); } void VFSBreakGetDirSize (struct TVFSGlobs *globs) { printf ("(WW) VFSBreakGetDirSize: calling break\n"); if (globs) vfs_filelist_get_dir_size_break (globs->vfs_filelist); } /******************************************************************************************************/ /** Methods modifying the archive */ /************** ****************/ #if 0 TVFSResult VFSMkDir (struct TVFSGlobs *globs, const char *sDirName) { printf ("(WW) VFSMkDir: Not supported in libarchive plugin.\n"); return cVFS_Not_Supported; } TVFSResult VFSRemove (struct TVFSGlobs *globs, const char *APath) { printf ("(WW) VFSRemove: Not supported in libarchive plugin.\n"); return cVFS_Not_Supported; } TVFSResult VFSRename (struct TVFSGlobs *globs, const char *sSrcName, const char *sDstName) { printf ("(WW) VFSRename: Not supported in libarchive plugin.\n"); return cVFS_Not_Supported; } TVFSResult VFSMakeSymLink (struct TVFSGlobs *globs, const char *NewFileName, const char *PointTo) { printf ("(WW) VFSMakeSymLink: Not supported in libarchive plugin.\n"); return cVFS_Not_Supported; } TVFSResult VFSChmod (struct TVFSGlobs *globs, const char *FileName, guint32 Mode) { printf ("(WW) VFSChmod: Not supported in libarchive plugin.\n"); return cVFS_Not_Supported; } TVFSResult VFSChown (struct TVFSGlobs *globs, const char *FileName, guint32 UID, guint32 GID) { printf ("(WW) VFSChown: Not supported in libarchive plugin.\n"); return cVFS_Not_Supported; } TVFSResult VFSChangeTimes (struct TVFSGlobs *globs, char *APath, guint32 mtime, guint32 atime) { printf ("(WW) VFSChangeTimes: Not supported in libarchive plugin.\n"); return cVFS_Not_Supported; } #endif /******************************************************************************************************/ /************** **/ #if 0 TVFSFileDes VFSOpenFile(struct TVFSGlobs *globs, const char *APath, int Mode, int *Error) { *Error = cVFS_Not_Supported; return (TVFSFileDes)0; } TVFSResult VFSCloseFile(struct TVFSGlobs *globs, TVFSFileDes FileDescriptor) { return cVFS_Not_Supported; } u_int64_t VFSFileSeek(struct TVFSGlobs *globs, TVFSFileDes FileDescriptor, u_int64_t AbsoluteOffset, int *Error) { *Error = cVFS_Not_Supported; return 0; } int VFSReadFile(struct TVFSGlobs *globs, TVFSFileDes FileDescriptor, void *Buffer, int ABlockSize, int *Error) { *Error = cVFS_Not_Supported; return 0; } int VFSWriteFile(struct TVFSGlobs *globs, TVFSFileDes FileDescriptor, void *Buffer, int BytesCount, int *Error) { *Error = cVFS_Not_Supported; return 0; } #endif void VFSSetBlockSize (struct TVFSGlobs *globs, guint32 Value) { if (globs) globs->block_size = Value; } gboolean VFSIsOnSameFS (struct TVFSGlobs *globs, const char *Path1, const char *Path2, gboolean FollowSymlinks) { printf ("(WW) VFSIsOnSameFS: Not supported in libarchive plugin.\n"); return TRUE; } gboolean VFSTwoSameFiles (struct TVFSGlobs *globs, const char *Path1, const char *Path2, gboolean FollowSymlinks) { printf ("(WW) VFSTwoSameFiles: Not supported in libarchive, comparing by paths.\n"); return compare_two_same_files (Path1, Path2); } /******************************************************************************************************/ /************** **/ /** * The following code has been stolen from archive_read_data_into_fd.c (libarchive sources) and modified to allow progress callbacks * Quote: "This implementation minimizes copying of data and is sparse-file aware." **/ static gboolean my_archive_read_data_into_fd (struct TVFSGlobs *globs, struct archive *a, struct archive_entry *entry, const char *sDstName, size_t max_block, gboolean Append, GError **error) { int r; int fd; const void *buff; size_t size, bytes_to_write; ssize_t bytes_written, total_written; off_t offset; off_t output_offset; guint64 file_size; gboolean cancel = FALSE; int saved_errno; printf ("(II) my_archive_read_data_into_fd: extracting to '%s', Append = %d\n", sDstName, Append); if (Append) fd = open (sDstName, O_APPEND | O_WRONLY); else fd = open (sDstName, O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (fd < 0) { saved_errno = errno; fprintf (stderr, "(EE) my_archive_read_data_into_fd: error occured while extracting data: %s\n", strerror (saved_errno)); g_set_error (error, G_IO_ERROR, g_io_error_from_errno (saved_errno), "Error extracting data: %s", g_strerror (saved_errno)); return FALSE; } total_written = 0; output_offset = 0; file_size = (guint64) archive_entry_size (entry); while ((r = archive_read_data_block (a, &buff, &size, &offset)) == ARCHIVE_OK) { const char *p = buff; if (offset > output_offset) { lseek (fd, offset - output_offset, SEEK_CUR); output_offset = offset; } while (size > 0) { if (cancel) break; bytes_to_write = size; if (bytes_to_write > max_block) bytes_to_write = max_block; bytes_written = write (fd, p, bytes_to_write); if (bytes_written < 0) { saved_errno = errno; fprintf (stderr, "(EE) my_archive_read_data_into_fd: error occured while extracting data: %s\n", strerror (saved_errno)); g_set_error (error, G_IO_ERROR, g_io_error_from_errno (saved_errno), "Error writing data: %s", g_strerror (saved_errno)); close (fd); return FALSE; } output_offset += bytes_written; total_written += bytes_written; p += bytes_written; size -= bytes_written; log (" (II) my_archive_read_data_into_fd: bytes_written = %zd, total_written = %zd\n", bytes_written, total_written); if (globs->callback_progress) if (! globs->callback_progress (total_written, NULL, globs->callback_data)) { cancel = TRUE; break; } } } if (r != ARCHIVE_OK && r != ARCHIVE_EOF) { fprintf (stderr, "(EE) my_archive_read_data_into_fd: error closing extracted file: %s\n", strerror (errno)); g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (archive_errno (a)), archive_error_string (a)); close (fd); return FALSE; } if (close (fd)) { saved_errno = errno; fprintf (stderr, "(EE) my_archive_read_data_into_fd: error closing extracted file: %s\n", strerror (errno)); g_set_error (error, G_IO_ERROR, g_io_error_from_errno (saved_errno), "Error closing extracted file: %s", g_strerror (saved_errno)); return FALSE; } if (cancel) { if (unlink (sDstName)) fprintf (stderr, "(EE) my_archive_read_data_into_fd: error unlinking cancelled extraction: %s\n", strerror (errno)); g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Operation has been cancelled."); return FALSE; } printf ("(II) my_archive_read_data_into_fd: done.\n"); return TRUE; } gboolean VFSStartCopyOperation (struct TVFSGlobs *globs, GError **error) { if (globs->op_archive != NULL) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_ALREADY_MOUNTED, "globs->op_archive != NULL"); return FALSE; } printf ("(II) VFSStartCopyOperation: opening archive '%s'\n", globs->archive_path); return libarchive_open (&globs->op_archive, globs->archive_path, globs->block_size, error); } gboolean VFSStopCopyOperation (struct TVFSGlobs *globs, GError **error) { if (globs->op_archive == NULL) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_ALREADY_MOUNTED, "globs->op_archive == NULL"); return FALSE; } printf ("(II) VFSStopCopyOperation: closing archive.\n"); archive_read_close (globs->op_archive); archive_read_finish (globs->op_archive); globs->op_archive = NULL; return TRUE; } gboolean VFSCopyToLocal (struct TVFSGlobs *globs, const char *sSrcName, const char *sDstName, gboolean Append, GError **error) { struct PathTree *node; const char *src; struct archive_entry *entry; int r; gboolean found; gboolean res; if (globs->op_archive == NULL) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "globs->op_archive == NULL"); return FALSE; } if (sSrcName == NULL || sDstName == NULL || strlen (sSrcName) < 1 || strlen (sDstName) < 1) { printf ("(EE) VFSCopyToLocal: The value of 'sSrcName' or 'sDstName' is NULL or empty\n"); g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "The value of 'sSrcName' or 'sDstName' is NULL or empty."); return FALSE; } printf ("(II) VFSCopyToLocal: copying file '%s' out to '%s'\n", sSrcName, sDstName); node = filelist_tree_find_node_by_path (globs->files, sSrcName); if (! node) { fprintf (stderr, "(EE) VFSCopyToLocal: cannot find file '%s'\n", sSrcName); g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "cannot find file '%s'", sSrcName); return FALSE; } src = node->original_pathstr; if (! src) { fprintf (stderr, "(WW) VFSCopyToLocal: cannot determine original filename\n"); src = sSrcName; } printf ("(II) VFSCopyToLocal: new src path: '%s'\n", src); found = FALSE; res = TRUE; for (;;) { entry = NULL; r = archive_read_next_header (globs->op_archive, &entry); if (r == ARCHIVE_EOF) { break; } else if (r == ARCHIVE_WARN) { log ("(WW) VFSCopyToLocal: file '%s' - libarchive warning: '%s'\n", archive_entry_pathname (entry), archive_error_string (globs->op_archive)); } else if (r != ARCHIVE_OK) { fprintf (stderr, "(EE) VFSCopyToLocal: error occured while reading archive: '%s'\n", archive_error_string (globs->op_archive)); g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (archive_errno (globs->op_archive)), archive_error_string (globs->op_archive)); break; } if (g_strcmp0 (src, archive_entry_pathname (entry)) == 0) { found = TRUE; res = my_archive_read_data_into_fd (globs, globs->op_archive, entry, sDstName, globs->block_size, Append, error); break; } } if (! found && res) { fprintf (stderr, "(EE) VFSCopyToLocal: file not found in archive.\n"); g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "File not found in archive."); res = FALSE; } fprintf (stderr, "(II) VFSCopyToLocal: finished. \n"); return res; } gboolean VFSCopyFromLocal (struct TVFSGlobs *globs, const char *sSrcName, const char *sDstName, gboolean Append, GError **error) { printf ("(WW) VFSCopyFromLocal: Not supported in libarchive plugin.\n"); g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Not supported in libarchive plugin."); return FALSE; } /********** * TODO: * * - archive testing (needs new VFS API) * - write support * - support creating new archives (needs new VFS API) * ***/