diff -Nru fuse-zip-0.2.8/bigBuffer.cpp fuse-zip-0.2.12/bigBuffer.cpp --- fuse-zip-0.2.8/bigBuffer.cpp 2009-11-18 18:09:36.000000000 +0000 +++ fuse-zip-0.2.12/bigBuffer.cpp 2010-02-07 00:19:02.000000000 +0000 @@ -1,6 +1,7 @@ //////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2008 by Alexander Galanin // -// gaa.nnov@mail.ru // +// Copyright (C) 2008-2010 by Alexander Galanin // +// al@galanin.nnov.ru // +// http://galanin.nnov.ru/~al // // // // This program is free software; you can redistribute it and/or modify // // it under the terms of the GNU Lesser General Public License as // @@ -25,62 +26,176 @@ #include "bigBuffer.h" #include "fileNode.h" +/** + * Class that keep chunk of file data. + */ +class BigBuffer::ChunkWrapper { +private: + /** + * Pointer that keeps data for chunk. Can be NULL. + */ + char *m_ptr; + +public: + /** + * By default internal buffer is NULL, so this can be used for creating + * sparse files. + */ + ChunkWrapper(): m_ptr(NULL) { + } + + /** + * Take ownership on internal pointer from 'other' object. + */ + ChunkWrapper(const ChunkWrapper &other) { + m_ptr = other.m_ptr; + const_cast(&other)->m_ptr = NULL; + } + + /** + * Free pointer if allocated. + */ + ~ChunkWrapper() { + if (m_ptr != NULL) { + free(m_ptr); + } + } + + /** + * Take ownership on internal pointer from 'other' object. + */ + ChunkWrapper &operator=(const ChunkWrapper &other) { + if (&other != this) { + m_ptr = other.m_ptr; + const_cast(&other)->m_ptr = NULL; + } + return *this; + } + + /** + * Return pointer to internal storage and initialize it if needed. + */ + char *ptr(bool init = false) { + if (init && m_ptr == NULL) { + m_ptr = (char *)malloc(chunkSize); + } + return m_ptr; + } + + /** + * Fill 'dest' with internal buffer content. + * If m_ptr is NULL, destination bytes is zeroed. + * + * @param dest Destination buffer. + * @param offset Offset in internal buffer to start reading from. + * @param count Number of bytes to be read. + * + * @return Number of bytes actually read. It can differ with 'count' + * if offset+count>chunkSize. + */ + size_t read(char *dest, offset_t offset, size_t count) const { + if (offset + count > chunkSize) { + count = chunkSize - offset; + } + if (m_ptr != NULL) { + memcpy(dest, m_ptr + offset, count); + } else { + memset(dest, 0, count); + } + return count; + } + + /** + * Fill internal buffer with bytes from 'src'. + * If m_ptr is NULL, memory for buffer is malloc()-ed and then head of + * allocated space is zeroed. After that byte copying is performed. + * + * @param src Source buffer. + * @param offset Offset in internal buffer to start writting from. + * @param count Number of bytes to be written. + * + * @return Number of bytes actually written. It can differ with + * 'count' if offset+count>chunkSize. + */ + size_t write(const char *src, offset_t offset, size_t count) { + if (offset + count > chunkSize) { + count = chunkSize - offset; + } + if (m_ptr == NULL) { + m_ptr = (char *)malloc(chunkSize); + if (m_ptr == NULL) { + throw std::bad_alloc(); + } + if (offset > 0) { + memset(m_ptr, 0, offset); + } + } + memcpy(m_ptr + offset, src, count); + return count; + } + + /** + * Clear tail of internal buffer with zeroes starting from 'offset'. + */ + void clearTail(offset_t offset) { + if (m_ptr != NULL && offset < chunkSize) { + memset(m_ptr + offset, 0, chunkSize - offset); + } + } + +}; + BigBuffer::BigBuffer(): len(0) { } +/** + * Read file data from zip file + */ BigBuffer::BigBuffer(struct zip *z, int nodeId, ssize_t length): len(length) { struct zip_file *zf = zip_fopen_index(z, nodeId, 0); - char *buf = (char*)malloc(chunkSize); - if (buf == NULL) { - throw std::bad_alloc(); + if (zf == NULL) { + throw std::exception(); } + chunks.resize(chunksCount(length), ChunkWrapper()); + unsigned int chunk = 0; ssize_t nr; - while ((nr = zip_fread(zf, buf, chunkSize)) > 0) { - chunks.push_back(buf); - buf = (char*)malloc(chunkSize); - if (buf == NULL) { - for (chunks_t::iterator i = chunks.begin(); i != chunks.end(); ++i) { - free(*i); - } - throw std::bad_alloc(); + while (length > 0) { + nr = zip_fread(zf, chunks[chunk].ptr(true), chunkSize); + if (nr < 0) { + zip_fclose(zf); + throw std::exception(); } + ++chunk; + length -= nr; } - free(buf); if (zip_fclose(zf)) { throw std::exception(); } } BigBuffer::~BigBuffer() { - for (chunks_t::iterator i = chunks.begin(); i != chunks.end(); ++i) { - if (*i != NULL) { - free(*i); - } - } } +/** + * Dispatch read requests to chunks of a file and write result to resulting + * buffer. + * Reading after end of file is not allowed, so 'size' is decreased to fit + * file boundaries. + */ int BigBuffer::read(char *buf, size_t size, offset_t offset) const { if (offset > len) { return -EINVAL; } - int chunk = offset / chunkSize; - int pos = offset % chunkSize; - int nread = 0; + int chunk = chunkNumber(offset); + int pos = chunkOffset(offset); if (size > unsigned(len - offset)) { size = len - offset; } + int nread = size; while (size > 0) { - size_t r = chunkSize - pos; - if (r > size) { - r = size; - } - if (chunks[chunk] != NULL) { - memcpy(buf, chunks[chunk] + pos, r); - } else { - memset(buf, 0, r); - } + size_t r = chunks[chunk].read(buf, pos, size); + size -= r; - nread += r; buf += r; ++chunk; pos = 0; @@ -88,30 +203,29 @@ return nread; } +/** + * Dispatch write request to chunks of a file and grow 'chunks' vector if + * necessary. + * If 'offset' is after file end, tail of last chunk cleared before growing. + */ int BigBuffer::write(const char *buf, size_t size, offset_t offset) { - int chunk = offset / chunkSize; - int pos = offset % chunkSize; - int nwritten = 0; - for (unsigned int i = chunks.size(); i <= (offset + size) / chunkSize; ++i) { - chunks.push_back(NULL); - } - if (len < offset || size > unsigned(len - offset)) { + int chunk = chunkNumber(offset); + int pos = chunkOffset(offset); + int nwritten = size; + + if (offset > len) { + if (len > 0) { + chunks[chunkNumber(len)].clearTail(chunkOffset(len)); + } + len = size + offset; + } else if (size > unsigned(len - offset)) { len = size + offset; } + chunks.resize(chunksCount(len)); while (size > 0) { - size_t w = chunkSize - pos; - if (w > size) { - w = size; - } - if (chunks[chunk] == NULL) { - chunks[chunk] = (char*)malloc(chunkSize); - if (!chunks[chunk]) { - return -EIO; - } - } - memcpy(chunks[chunk] + pos, buf, w); + size_t w = chunks[chunk].write(buf, pos, size); + size -= w; - nwritten += w; buf += w; ++ chunk; pos = 0; @@ -119,17 +233,15 @@ return nwritten; } -int BigBuffer::truncate(offset_t offset) { - if (offset < len) { - for (unsigned int i = (offset + chunkSize - 1)/chunkSize + 1; i < chunks.size(); ++i) { - if (chunks[i] != NULL) { - free(chunks[i]); - } - } +void BigBuffer::truncate(offset_t offset) { + chunks.resize(chunksCount(offset)); + + if (offset > len && len > 0) { + // Fill end of last non-empty chunk with zeroes + chunks[chunkNumber(len)].clearTail(chunkOffset(len)); } + len = offset; - chunks.resize((len + chunkSize - 1)/chunkSize, NULL); - return 0; } ssize_t BigBuffer::zipUserFunctionCallback(void *state, void *data, size_t len, enum zip_source_cmd cmd) { diff -Nru fuse-zip-0.2.8/bigBuffer.h fuse-zip-0.2.12/bigBuffer.h --- fuse-zip-0.2.8/bigBuffer.h 2009-11-18 18:09:36.000000000 +0000 +++ fuse-zip-0.2.12/bigBuffer.h 2010-02-07 00:19:02.000000000 +0000 @@ -1,6 +1,7 @@ //////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2008 by Alexander Galanin // -// gaa.nnov@mail.ru // +// Copyright (C) 2008-2010 by Alexander Galanin // +// al@galanin.nnov.ru // +// http://galanin.nnov.ru/~al // // // // This program is free software; you can redistribute it and/or modify // // it under the terms of the GNU Lesser General Public License as // @@ -30,9 +31,12 @@ class BigBuffer { private: - static const int chunkSize = 4*1024; //4 Kilobytes + //TODO: use >> and << + static const unsigned int chunkSize = 4*1024; //4 Kilobytes - typedef std::vector chunks_t; + class ChunkWrapper; + + typedef std::vector chunks_t; struct CallBackStruct { size_t pos; @@ -43,6 +47,28 @@ chunks_t chunks; static ssize_t zipUserFunctionCallback(void *state, void *data, size_t len, enum zip_source_cmd cmd); + + /** + * Return number of chunks needed to keep 'offset' bytes. + */ + inline unsigned int chunksCount(offset_t offset) const { + return (offset + chunkSize - 1) / chunkSize; + } + + /** + * Return number of chunk where 'offset'-th byte is located. + */ + inline unsigned int chunkNumber(offset_t offset) const { + return offset / chunkSize; + } + + /** + * Return offset inside chunk to 'offset'-th byte. + */ + inline int chunkOffset(offset_t offset) const { + return offset % chunkSize; + } + public: offset_t len; @@ -53,7 +79,17 @@ int read(char *buf, size_t size, offset_t offset) const; int write(const char *buf, size_t size, offset_t offset); int saveToZip(const FileNode *fileNode, struct zip *z, const char *fname, bool newFile, int index); - int truncate(offset_t offset); + + /** + * Truncate buffer at position offset. + * 1. Free chunks after offset + * 2. Resize chunks vector to a new size + * 3. Fill data block that made readable by resize with zeroes + * + * @throws + * std::bad_alloc If insufficient memory available + */ + void truncate(offset_t offset); }; #endif diff -Nru fuse-zip-0.2.8/changelog fuse-zip-0.2.12/changelog --- fuse-zip-0.2.8/changelog 2009-11-18 18:09:36.000000000 +0000 +++ fuse-zip-0.2.12/changelog 2010-02-07 00:19:02.000000000 +0000 @@ -1,3 +1,36 @@ +2010-02-07 Alexander Galanin + + * Released 0.2.12: + - Fixed problem with lost new file after truncate(). + - Fixed problems with rename(): Fixed various rename() problems: lost + path for subdirectory entries, duplicates of moved directories in a + hierarchy, invalid key in map after rename. + - Fixed unitialized values in read() call. + - Fixed memory leaks: buffer allocated for file content not freeed in + NEW state, incorrect buffer size in truncate(). + - Fixed non-fatal memory leaks: FUSE options not freeed after use, + memory leak in help/version mode, internal data structures not freeed + if FUSE setup failed. + - More correct corrupted files handling. + - More correct memory insufficiency errors handling. + +2010-01-26 Alexander Galanin + + * Released 0.2.11: + - Fixed issue #25: does not compile with libfuse <= 2.8 + +2010-01-09 Alexander Galanin + + * Released 0.2.10: + - Fixed issue #14: added '-r' option description. + - Added note about converting file names inside archive (for Russians + who uses 'another OS') + +2010-01-08 Alexander Galanin + + * Released 0.2.9: + - Fixed issue #22, now command-line options are correctly processed + 2009-11-18 Alexander Galanin * Released 0.2.8: diff -Nru fuse-zip-0.2.8/debian/changelog fuse-zip-0.2.12/debian/changelog --- fuse-zip-0.2.8/debian/changelog 2009-12-22 08:14:54.000000000 +0000 +++ fuse-zip-0.2.12/debian/changelog 2010-02-28 13:48:33.000000000 +0000 @@ -1,3 +1,12 @@ +fuse-zip (0.2.12-0ubuntu1) lucid; urgency=low + + * New upstream bugfix-only release. + * Bump Standards. + * Adjust build-dep on debhelper to ease backporters' life. + * Disable auto_test target to avoid FTBFS. + + -- Alessio Treglia Sun, 28 Feb 2010 14:48:24 +0100 + fuse-zip (0.2.8-0ubuntu2) lucid; urgency=low * Migrated to the 3.0 (quilt) format. diff -Nru fuse-zip-0.2.8/debian/control fuse-zip-0.2.12/debian/control --- fuse-zip-0.2.8/debian/control 2009-12-22 08:10:03.000000000 +0000 +++ fuse-zip-0.2.12/debian/control 2010-02-28 13:44:26.000000000 +0000 @@ -3,8 +3,11 @@ Priority: optional Maintainer: Ubuntu Developers XSBC-Original-Maintainer: Maia Kozheva -Build-Depends: debhelper (>= 7.0.50), libfuse-dev, libzip-dev (>= 0.8), pkg-config -Standards-Version: 3.8.3 +Build-Depends: debhelper (>= 7.0.50~), + libfuse-dev, + libzip-dev (>= 0.8), + pkg-config +Standards-Version: 3.8.4 Homepage: http://code.google.com/p/fuse-zip Package: fuse-zip diff -Nru fuse-zip-0.2.8/debian/rules fuse-zip-0.2.12/debian/rules --- fuse-zip-0.2.8/debian/rules 2009-12-22 08:10:03.000000000 +0000 +++ fuse-zip-0.2.12/debian/rules 2010-02-28 13:48:20.000000000 +0000 @@ -5,5 +5,7 @@ override_dh_auto_install: $(MAKE) INSTALLPREFIX=$(CURDIR)/debian/fuse-zip/usr install +override_dh_auto_test: + %: dh $@ diff -Nru fuse-zip-0.2.8/fileNode.cpp fuse-zip-0.2.12/fileNode.cpp --- fuse-zip-0.2.8/fileNode.cpp 2009-11-18 18:09:36.000000000 +0000 +++ fuse-zip-0.2.12/fileNode.cpp 2010-02-07 00:19:02.000000000 +0000 @@ -1,6 +1,7 @@ //////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2008 by Alexander Galanin // -// gaa.nnov@mail.ru // +// Copyright (C) 2008-2010 by Alexander Galanin // +// al@galanin.nnov.ru // +// http://galanin.nnov.ru/~al // // // // This program is free software; you can redistribute it and/or modify // // it under the terms of the GNU Lesser General Public License as // @@ -69,7 +70,7 @@ FileNode::~FileNode() { free(full_name); - if (state == OPENED || state == CHANGED) { + if (state == OPENED || state == CHANGED || state == NEW) { delete buffer; } } @@ -159,8 +160,10 @@ } void FileNode::rename_wo_reparenting(char *new_name) { + data->files.erase(full_name); free(full_name); parse_name(new_name); + data->files[new_name] = this; } int FileNode::open() { @@ -219,10 +222,18 @@ int FileNode::truncate(offset_t offset) { if (state != CLOSED) { - state = CHANGED; - return buffer->truncate(offset); + if (state != NEW) { + state = CHANGED; + } + try { + buffer->truncate(offset); + return 0; + } + catch (const std::bad_alloc &) { + return EIO; + } } else { - return -EBADF; + return EBADF; } } diff -Nru fuse-zip-0.2.8/fileNode.h fuse-zip-0.2.12/fileNode.h --- fuse-zip-0.2.8/fileNode.h 2009-11-18 18:09:36.000000000 +0000 +++ fuse-zip-0.2.12/fileNode.h 2010-02-07 00:19:02.000000000 +0000 @@ -1,6 +1,7 @@ //////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2008 by Alexander Galanin // -// gaa.nnov@mail.ru // +// Copyright (C) 2008-2010 by Alexander Galanin // +// al@galanin.nnov.ru // +// http://galanin.nnov.ru/~al // // // // This program is free software; you can redistribute it and/or modify // // it under the terms of the GNU Lesser General Public License as // @@ -50,6 +51,15 @@ void detach(); void rename(char *fname); + + /** + * Rename file without reparenting. + * + * 1. Remove file item from tree + * 2. Free ols file name string + * 3. Parse new name + * 4. Create link to new name in tree + */ void rename_wo_reparenting(char *new_name); int open(); @@ -57,6 +67,16 @@ int write(const char *buf, size_t size, offset_t offset); int close(); int save(); + + /** + * Truncate file. + * + * @return + * 0 If successful + * EBADF If file is currently closed + * EIO If insufficient memory available (because ENOMEM not + * listed in truncate() error codes) + */ int truncate(offset_t offset); inline bool isChanged() const { diff -Nru fuse-zip-0.2.8/fuse-zip.1 fuse-zip-0.2.12/fuse-zip.1 --- fuse-zip-0.2.8/fuse-zip.1 2009-11-18 18:09:36.000000000 +0000 +++ fuse-zip-0.2.12/fuse-zip.1 2010-02-07 00:19:02.000000000 +0000 @@ -1,15 +1,14 @@ .\" '\" t .\" ** The above line should force tbl to be a preprocessor ** .\" Man page for fuse-zip -.TH "fuse-zip" "1" "June 2008" "FUSE filesystem to read and modify ZIP archives" "FUSE filesystem to read and modify ZIP archives" +.TH "fuse-zip" "1" "January 2010" "FUSE filesystem to read and modify ZIP archives" "FUSE filesystem to read and modify ZIP archives" .SH "NAME" fuse\-zip \- a FUSE filesystem for zip archives with write support .SH "SYNOPSIS" .\" The general command line .B fuse\-zip +.RI [\| options \|] zip\-file -.RI [\| fusermount -.RI \| options \|] mount\-point .SH "OPTIONS" .TP @@ -19,12 +18,12 @@ \fB-V\fP print version .TP +\fB-r\fP +open archive in read\-only mode +.TP \fB-o opt[,opt...]\fP mount options .TP -\fB-q\fP -quiet -.TP \fB-f\fP don't detach from terminal .TP @@ -65,12 +64,12 @@ .BR fusermount (1). .SH "LICENSE" . -This is Free Software; this software is licensed under the LGPL version 2, as published by the Free Software Foundation, or later. +This is Free Software; this software is licensed under the LGPL version 3, as published by the Free Software Foundation, or later. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. . .SH "AUTHORS" . -Alexander Galanin . +Alexander Galanin http://galanin.nnov.ru/~al .br . This manual page was originally written by Kirill Zaitsev . Updated by Alexander Galanin. diff -Nru fuse-zip-0.2.8/fuse-zip.cpp fuse-zip-0.2.12/fuse-zip.cpp --- fuse-zip-0.2.8/fuse-zip.cpp 2009-11-18 18:09:36.000000000 +0000 +++ fuse-zip-0.2.12/fuse-zip.cpp 2010-02-07 00:19:02.000000000 +0000 @@ -1,6 +1,7 @@ //////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2008-2009 by Alexander Galanin // +// Copyright (C) 2008-2010 by Alexander Galanin // // al@galanin.nnov.ru // +// http://galanin.nnov.ru/~al // // // // This program is free software; you can redistribute it and/or modify // // it under the terms of the GNU Lesser General Public License as // @@ -20,14 +21,19 @@ #define FUSE_USE_VERSION 27 #define PROGRAM "fuse-zip" +#define VERSION "0.2.12" #define ERROR_STR_BUF_LEN 0x100 #define STANDARD_BLOCK_SIZE (512) +#define KEY_HELP (0) +#define KEY_VERSION (1) + #include +#include #include #include +#include #include -#include #include #include @@ -43,12 +49,27 @@ using namespace std; +/** + * Initialize filesystem + * + * Report current working dir and archive file name to syslog. + * + * @return filesystem-private data + */ static void *fusezip_init(struct fuse_conn_info *conn) { (void) conn; - syslog(LOG_INFO, "Mounting file system"); - return fuse_get_context()->private_data; + FuseZipData *data = (FuseZipData*)fuse_get_context()->private_data; + syslog(LOG_INFO, "Mounting file system on %s (cwd=%s)", data->m_archiveName, data->m_cwd); + return data; } +/** + * Destroy filesystem + * + * Save all modified data back to ZIP archive and report to syslog about completion. + * Note that filesystem unmounted before this method finishes + * (see http://code.google.com/p/fuse-zip/issues/detail?id=7). + */ static void fusezip_destroy(void *data) { FuseZipData *d = (FuseZipData*)data; // Saving changed data @@ -219,7 +240,7 @@ static int fusezip_ftruncate(const char *path, off_t offset, struct fuse_file_info *fi) { (void) path; - return ((FileNode*)fi->fh)->truncate(offset); + return -((FileNode*)fi->fh)->truncate(offset); } static int fusezip_truncate(const char *path, off_t offset) { @@ -238,22 +259,12 @@ return res; } if ((res = node->truncate(offset)) != 0) { - return res; + node->close(); + return -res; } return node->close(); } -int remove_node(FileNode *node) { - node->detach(); - int id = node->id; - delete node; - if (id >= 0) { - return (zip_delete (get_zip(), id) == 0)? 0 : -ENOENT; - } else { - return 0; - } -} - static int fusezip_unlink(const char *path) { if (*path == '\0') { return -ENOENT; @@ -265,7 +276,7 @@ if (node->is_dir) { return -EISDIR; } - return remove_node(node); + return -get_data()->removeNode(node); } static int fusezip_rmdir(const char *path) { @@ -282,7 +293,7 @@ if (!node->childs.empty()) { return -ENOTEMPTY; } - return remove_node(node); + return -get_data()->removeNode(node); } static int fusezip_mkdir(const char *path, mode_t mode) { @@ -315,13 +326,18 @@ } FileNode *new_node = get_file_node(new_path + 1); if (new_node != NULL) { - remove_node(new_node); + int res = get_data()->removeNode(new_node); + if (res !=0) { + return -res; + } } int len = strlen(new_path); + int oldLen = strlen(path + 1) + 1; char *new_name; if (!node->is_dir) { - len--; + --len; + --oldLen; } new_name = (char*)malloc(len + 1); if (new_path == NULL) { @@ -345,21 +361,25 @@ for (nodelist_t::const_iterator i = n->childs.begin(); i != n->childs.end(); ++i) { FileNode *nn = *i; q.push(nn); - char *name = (char*)malloc(len + strlen(nn->name) + 1); + char *name = (char*)malloc(len + strlen(nn->full_name) - oldLen + (nn->is_dir ? 2 : 1)); if (name == NULL) { //TODO: check that we are have enough memory before entering this loop return -ENOMEM; } strcpy(name, new_name); - strcpy(name + len, nn->name); - nn->rename_wo_reparenting(name); + strcpy(name + len, nn->full_name + oldLen); + if (nn->is_dir) { + strcat(name, "/"); + } zip_rename(z, nn->id, name); + nn->rename_wo_reparenting(name); } } } zip_rename(z, node->id, new_name); // Must be called after loop because new_name will be truncated node->rename(new_name); + return 0; } catch (...) { @@ -435,63 +455,190 @@ return 0; } +/** + * Print usage information + */ void print_usage() { - printf("USAGE: %s [fusermount options] \n", PROGRAM); + fprintf(stderr, "usage: %s [options] \n\n", PROGRAM); + fprintf(stderr, + "general options:\n" + " -o opt,[opt...] mount options\n" + " -h --help print help\n" + " -V --version print version\n" + " -r -o ro open archive in read-only mode\n" + " -f don't detach from terminal\n" + " -d turn on debugging, also implies -f\n" + "\n"); +} + +/** + * Print version information (fuse-zip and FUSE library) + */ +void print_version() { + fprintf(stderr, "%s version: %s\n", PROGRAM, VERSION); } -int main(int argc, char *argv[]) { - if (sizeof(void*) > sizeof(uint64_t)) { - fprintf(stderr,"%s: This program cannot be run on your system because of FUSE design limitation\n", PROGRAM); - return EXIT_FAILURE; - } - if (argc < 2) { - print_usage(); - return EXIT_FAILURE; - } - openlog(PROGRAM, LOG_PID, LOG_USER); +/** + * Initialize libzip and fuse-zip structures. + * + * @param fileName ZIP file name + * @return NULL if an error occured, otherwise pointer to FuseZipData structure. + */ +FuseZipData *initFuseZip(const char * fileName) { + FuseZipData *data = NULL; int err; struct zip *zip_file; - if ((zip_file = zip_open(argv[1], ZIP_CHECKCONS | ZIP_CREATE, &err)) == NULL) { + + openlog(PROGRAM, LOG_PID, LOG_USER); + + if ((zip_file = zip_open(fileName, ZIP_CHECKCONS | ZIP_CREATE, &err)) == NULL) { char err_str[ERROR_STR_BUF_LEN]; zip_error_to_str(err_str, ERROR_STR_BUF_LEN, err, errno); - fprintf(stderr, "%s: cannot open zip archive %s: %s\n", PROGRAM, argv[1], err_str); - return EXIT_FAILURE; + fprintf(stderr, "%s: cannot open zip archive %s: %s\n", PROGRAM, fileName, err_str); + return data; } - FuseZipData *data; + try { - char *cwd; -#ifdef _GNU_SOURCE - cwd = get_current_dir_name(); -#else -#if (PATH_MAX <= 0) -#error Something wrong with your system -#endif - int size = PATH_MAX; - cwd = (char*)malloc(size + 1); - if (cwd == NULL) { - throw std::bad_alloc(); + // current working directory + char *cwd = (char*)malloc(PATH_MAX + 1); + if (getcwd(cwd, PATH_MAX) == NULL) { + perror(NULL); + return data; } - while (getcwd(cwd, size) == NULL) { - free(cwd); - size += PATH_MAX; - cwd = (char*)malloc(size + 1); - if (cwd == NULL) { - throw std::bad_alloc(); - } + + data = new FuseZipData(fileName, zip_file, cwd); + if (data == NULL) { + throw std::bad_alloc(); } -#endif - data = new FuseZipData(zip_file, cwd); } catch (std::bad_alloc) { - fprintf(stderr, "%s: no enough memory\n", PROGRAM); - return EXIT_FAILURE; + fprintf(stderr, "%s: no enough memory\n", PROGRAM); } catch (std::exception) { fprintf(stderr, "%s: ZIP file corrupted\n", PROGRAM); + } + return data; +} + +/** + * Parameters for command-line argument processing function + */ +struct fusezip_param { + // help shown + bool help; + // version information shown + bool version; + // number of string arguments + int strArgCount; + // zip file name + const char *fileName; +}; + +/** + * Function to process arguments (called from fuse_opt_parse). + * + * @param data Pointer to fusezip_param structure + * @param arg is the whole argument or option + * @param key determines why the processing function was called + * @param outargs the current output argument list + * @return -1 on error, 0 if arg is to be discarded, 1 if arg should be kept + */ +static int process_arg(void *data, const char *arg, int key, struct fuse_args *outargs) { + struct fusezip_param *param = (fusezip_param*)data; + + (void)outargs; + + // 'magic' fuse_opt_proc return codes + const static int KEEP = 1; + const static int DISCARD = 0; + const static int ERROR = -1; + + switch (key) { + case KEY_HELP: { + print_usage(); + param->help = true; + return DISCARD; + } + + case KEY_VERSION: { + print_version(); + param->version = true; + return KEEP; + } + + case FUSE_OPT_KEY_NONOPT: { + ++param->strArgCount; + switch (param->strArgCount) { + case 1: { + // zip file name + param->fileName = arg; + return DISCARD; + } + case 2: { + // mountpoint + // keep it and then pass to FUSE initializer + return KEEP; + } + default: + fprintf(stderr, "%s: only two arguments allowed: filename and mountpoint\n", PROGRAM); + return ERROR; + } + } + + default: { + return KEEP; + } + } +} + +static const struct fuse_opt fusezip_opts[] = { + FUSE_OPT_KEY("-h", KEY_HELP), + FUSE_OPT_KEY("--help", KEY_HELP), + FUSE_OPT_KEY("-V", KEY_VERSION), + FUSE_OPT_KEY("--version", KEY_VERSION), + {NULL, 0, 0} +}; + +int main(int argc, char *argv[]) { + if (sizeof(void*) > sizeof(uint64_t)) { + fprintf(stderr,"%s: This program cannot be run on your system because of FUSE design limitation\n", PROGRAM); + return EXIT_FAILURE; + } + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + FuseZipData *data = NULL; + struct fusezip_param param; + param.help = false; + param.version = false; + param.strArgCount = 0; + param.fileName = NULL; + + if (fuse_opt_parse(&args, ¶m, fusezip_opts, process_arg)) { + fuse_opt_free_args(&args); return EXIT_FAILURE; } + // if all work is done inside options parsing... + if (param.help) { + fuse_opt_free_args(&args); + return EXIT_SUCCESS; + } + + // pass version switch to HELP library to see it's version + if (!param.version) { + // no file name passed + if (param.fileName == NULL) { + print_usage(); + fuse_opt_free_args(&args); + return EXIT_FAILURE; + } + + if ((data = initFuseZip(param.fileName)) == NULL) { + fuse_opt_free_args(&args); + return EXIT_FAILURE; + } + } + static struct fuse_operations fusezip_oper; fusezip_oper.init = fusezip_init; fusezip_oper.destroy = fusezip_destroy; @@ -523,13 +670,21 @@ fusezip_oper.listxattr = fusezip_listxattr; fusezip_oper.removexattr= fusezip_removexattr; +#if FUSE_VERSION >= 28 + // don't allow NULL path + fusezip_oper.flag_nullpath_ok = 0; +#endif + struct fuse *fuse; char *mountpoint; + // this flag ignored because libzip does not supports multithreading int multithreaded; int res; - fuse = fuse_setup(argc - 1, argv + 1, &fusezip_oper, sizeof(fusezip_oper), &mountpoint, &multithreaded, data); + fuse = fuse_setup(args.argc, args.argv, &fusezip_oper, sizeof(fusezip_oper), &mountpoint, &multithreaded, data); + fuse_opt_free_args(&args); if (fuse == NULL) { + delete data; return EXIT_FAILURE; } res = fuse_loop(fuse); diff -Nru fuse-zip-0.2.8/fuseZipData.cpp fuse-zip-0.2.12/fuseZipData.cpp --- fuse-zip-0.2.8/fuseZipData.cpp 2009-11-18 18:09:36.000000000 +0000 +++ fuse-zip-0.2.12/fuseZipData.cpp 2010-02-07 00:19:02.000000000 +0000 @@ -1,6 +1,7 @@ //////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2008 by Alexander Galanin // -// gaa.nnov@mail.ru // +// Copyright (C) 2008-2010 by Alexander Galanin // +// al@galanin.nnov.ru // +// http://galanin.nnov.ru/~al // // // // This program is free software; you can redistribute it and/or modify // // it under the terms of the GNU Lesser General Public License as // @@ -20,10 +21,11 @@ #include #include +#include #include "fuseZipData.h" -FuseZipData::FuseZipData(struct zip *z, char *cwd): m_zip(z), m_cwd(cwd) { +FuseZipData::FuseZipData(const char *archiveName, struct zip *z, char *cwd): m_zip(z), m_archiveName(archiveName), m_cwd(cwd) { if (cwd == NULL) { throw std::bad_alloc(); } @@ -64,3 +66,14 @@ } } +int FuseZipData::removeNode(FileNode *node) const { + node->detach(); + int id = node->id; + delete node; + if (id >= 0) { + return (zip_delete (m_zip, id) == 0)? 0 : ENOENT; + } else { + return 0; + } +} + diff -Nru fuse-zip-0.2.8/fuseZipData.h fuse-zip-0.2.12/fuseZipData.h --- fuse-zip-0.2.8/fuseZipData.h 2009-11-18 18:09:36.000000000 +0000 +++ fuse-zip-0.2.12/fuseZipData.h 2010-02-07 00:19:02.000000000 +0000 @@ -1,6 +1,7 @@ //////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2008 by Alexander Galanin // -// gaa.nnov@mail.ru // +// Copyright (C) 2008-2010 by Alexander Galanin // +// al@galanin.nnov.ru // +// http://galanin.nnov.ru/~al // // // // This program is free software; you can redistribute it and/or modify // // it under the terms of the GNU Lesser General Public License as // @@ -30,10 +31,26 @@ public: filemap_t files; struct zip *m_zip; + const char *m_archiveName; char *m_cwd; - FuseZipData(struct zip *z, char *cwd); + /** + * Keep archiveName and cwd in class fields and build file tree from z. + * + * 'cwd' and 'z' free()-ed in destructor. + * 'archiveName' should be managed externally. + */ + FuseZipData(const char *archiveName, struct zip *z, char *cwd); ~FuseZipData(); + + /** + * Detach node from tree, and delete associated entry in zip file if + * present. + * + * @param node Node to remove + * @return Error code or 0 is successful + */ + int removeNode(FileNode *node) const; }; #endif diff -Nru fuse-zip-0.2.8/.hg_archival.txt fuse-zip-0.2.12/.hg_archival.txt --- fuse-zip-0.2.8/.hg_archival.txt 1970-01-01 01:00:00.000000000 +0100 +++ fuse-zip-0.2.12/.hg_archival.txt 2010-02-07 00:19:02.000000000 +0000 @@ -0,0 +1,5 @@ +repo: 38472f434ab14d41686baaa73679d7fe4773f0f2 +node: 4de4d31c44f6b9a7d9ccb167c36cf0fd112ab181 +branch: default +latesttag: 0.2.12 +latesttagdistance: 1 diff -Nru fuse-zip-0.2.8/.hgignore fuse-zip-0.2.12/.hgignore --- fuse-zip-0.2.8/.hgignore 1970-01-01 01:00:00.000000000 +0100 +++ fuse-zip-0.2.12/.hgignore 2010-02-07 00:19:02.000000000 +0000 @@ -0,0 +1,16 @@ +syntax: glob + +# ignore vim swapfiles +.*.swp +# ignore source archives +fuse-zip*.tar.gz +# ignore generated files +*.o +fuse-zip +fuse-zip.1.gz +#tests +tests/blackbox/test.log + +# other related projects (needed for performance tests) +others + diff -Nru fuse-zip-0.2.8/.hgtags fuse-zip-0.2.12/.hgtags --- fuse-zip-0.2.8/.hgtags 1970-01-01 01:00:00.000000000 +0100 +++ fuse-zip-0.2.12/.hgtags 2010-02-07 00:19:02.000000000 +0000 @@ -0,0 +1,16 @@ +0ac39c893b9516994afab8e0524765a1cd54739b 0.2.0 +0d5865a7fb53959088bfd4b1edc028de8c64de53 0.2.6 +1e34f469bf9d39a34cf2dcd74be3762f75787cf9 0.2.5 +581b06b7cee2b7691125e2907683f4e834e90301 0.1.0 +5e0f0c7ae8049eb675386622c53d5f0b487512a6 0.2.3 +9513e1d44bf99244b9a037ea30cc01fbe8ea5856 0.2.1 +9ab3241a6822263558b2db2a9e26c0f4d2ca524f 0.2.4 +fd88cde887ac7405b81f2c9a73a61ed6af2fc99a 0.2.2 +80ad596796392ffedd0281c822d8f4bfd537134f 0.2.7 +ab463806418aff3429772941253e0f63503b58a2 0.2.8 +5edac8c7e535a992741658de1ba1fcb6058e1305 0.2.9 +5edac8c7e535a992741658de1ba1fcb6058e1305 0.2.9 +95321b27fefb951c3026beddbe5820ef4c3ac4b1 0.2.9 +19110f42d5f6ed094bbdeead843a969845b18907 0.2.10 +256f2f59af7b8e099479157507852cfd00bbf085 0.2.11 +e941c5cc7a9c1eb115fd1f4db7907dddc2cbc8dc 0.2.12 diff -Nru fuse-zip-0.2.8/libZipWrapper.cpp fuse-zip-0.2.12/libZipWrapper.cpp --- fuse-zip-0.2.8/libZipWrapper.cpp 2009-11-18 18:09:36.000000000 +0000 +++ fuse-zip-0.2.12/libZipWrapper.cpp 2010-02-07 00:19:02.000000000 +0000 @@ -1,6 +1,7 @@ //////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2008 by Alexander Galanin // -// gaa.nnov@mail.ru // +// Copyright (C) 2008-2010 by Alexander Galanin // +// al@galanin.nnov.ru // +// http://galanin.nnov.ru/~al // // // // This program is free software; you can redistribute it and/or modify // // it under the terms of the GNU Lesser General Public License as // diff -Nru fuse-zip-0.2.8/libZipWrapper.h fuse-zip-0.2.12/libZipWrapper.h --- fuse-zip-0.2.8/libZipWrapper.h 2009-11-18 18:09:36.000000000 +0000 +++ fuse-zip-0.2.12/libZipWrapper.h 2010-02-07 00:19:02.000000000 +0000 @@ -1,6 +1,7 @@ //////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2008 by Alexander Galanin // -// gaa.nnov@mail.ru // +// Copyright (C) 2008-2010 by Alexander Galanin // +// al@galanin.nnov.ru // +// http://galanin.nnov.ru/~al // // // // This program is free software; you can redistribute it and/or modify // // it under the terms of the GNU Lesser General Public License as // diff -Nru fuse-zip-0.2.8/makeArchives.sh fuse-zip-0.2.12/makeArchives.sh --- fuse-zip-0.2.8/makeArchives.sh 1970-01-01 01:00:00.000000000 +0100 +++ fuse-zip-0.2.12/makeArchives.sh 2010-02-07 00:19:02.000000000 +0000 @@ -0,0 +1,41 @@ +#!/bin/sh + +if [ $# != 0 ] +then + echo "usage: $0" + echo " generate archives for product" + exit 1 +fi + +version=`grep '#define VERSION' fuse-zip.cpp | sed 's/^[^"]*"//;s/".*$//'` +if [ "$version" = "" ] +then + echo "Unable to determine version" + exit 1 +fi + +dir=`mktemp -d` +pwd=`pwd` + +# make program tarball +id="fuse-zip-$version" +tmp="$dir/$id" + +hg archive -X performance\* -t tgz $id.tar.gz + +# make tests tarball +id="fuse-zip-tests-r`hg log performance_tests/ | head -n 1 | cut -d : -f 2 | sed 's/ //g'`" +tmp="$dir/$id" + +mkdir "$tmp" +mkdir "$tmp/kio_copy" +cp -t "$tmp" performance_tests/README performance_tests/run-tests.tcl performance_tests/unpackfs.config +cp -t "$tmp/kio_copy" performance_tests/kio_copy/kio_copy.pro performance_tests/kio_copy/main.cpp + +rm -rf "$id.tar.gz" +cd "$dir" +tar -cvzf "$pwd/$id.tar.gz" "$id" +cd "$pwd" + +rm -rf "$dir" + diff -Nru fuse-zip-0.2.8/Makefile fuse-zip-0.2.12/Makefile --- fuse-zip-0.2.8/Makefile 2009-11-18 18:09:36.000000000 +0000 +++ fuse-zip-0.2.12/Makefile 2010-02-07 00:19:02.000000000 +0000 @@ -30,7 +30,9 @@ distclean: clean doc-clean rm -f $(DEST) -clean: +clean: all-clean tarball-clean + +all-clean: rm -f $(CLEANFILES) $(MAN): $(MANSRC) @@ -52,3 +54,20 @@ rm -r "$(INSTALLPREFIX)/share/doc/$(DEST)" rm "$(INSTALLPREFIX)/share/man/man1/$(MAN)" +tarball: all doc + ./makeArchives.sh + +tarball-clean: + rm -f fuse-zip-*.tar.gz fuse-zip-tests-*.tar.gz + +debug: + make CXXFLAGS="-g $(CXXFLAGS)" + +test: $(DEST) + make -C tests + +valgrind: clean debug + make -C tests valgrind + +.PHONY: all doc clean distclean install uninstall tarball test + diff -Nru fuse-zip-0.2.8/README fuse-zip-0.2.12/README --- fuse-zip-0.2.8/README 2009-11-18 18:09:36.000000000 +0000 +++ fuse-zip-0.2.12/README 2010-02-07 00:19:02.000000000 +0000 @@ -49,6 +49,9 @@ -omodules=iconv,from_code=$charset1,to_code=$charset2 +Those Russian who uses archives from the "other OS" should use CP866 as +'charset1' and locale charset as 'charset2'. + See FUSE documentation for details. Look at /var/log/user.log in case of any errors. Binary files /tmp/1UCCz5jgKk/fuse-zip-0.2.8/tests/blackbox/data/bad-archive.zip and /tmp/EdoYjGxbbk/fuse-zip-0.2.12/tests/blackbox/data/bad-archive.zip differ Binary files /tmp/1UCCz5jgKk/fuse-zip-0.2.8/tests/blackbox/data/bad-crc.zip and /tmp/EdoYjGxbbk/fuse-zip-0.2.12/tests/blackbox/data/bad-crc.zip differ Binary files /tmp/1UCCz5jgKk/fuse-zip-0.2.8/tests/blackbox/data/cp866.zip and /tmp/EdoYjGxbbk/fuse-zip-0.2.12/tests/blackbox/data/cp866.zip differ diff -Nru fuse-zip-0.2.8/tests/blackbox/fuse-zip.test fuse-zip-0.2.12/tests/blackbox/fuse-zip.test --- fuse-zip-0.2.8/tests/blackbox/fuse-zip.test 1970-01-01 01:00:00.000000000 +0100 +++ fuse-zip-0.2.12/tests/blackbox/fuse-zip.test 2010-02-07 00:19:02.000000000 +0000 @@ -0,0 +1,1427 @@ +#!/bin/sh +# \ +exec tclsh "$0" "$@" + +package require Tcl 8.5 +package require BLT +package require Tclx +package require cmdline +package require struct::set +package require control + +::control::control assert enabled true + +namespace eval ::fusezip::test { + namespace import ::control::assert + + variable binary ../../fuse-zip + variable tmpdir [ exec mktemp -d "/tmp/fuse-zip-tests-[ pid ].XXXXXXXXXX" ] + variable timeout [ expr {1000*5} ] + + variable valgrind false + variable listTests false + + variable status {} + variable error {} + variable output {} + variable initializationState {} + + variable numPassed 0 + variable numFailed 0 + variable numSkipped 0 + variable failed {} + + # parseArgs -- + # + # Parse commnd-line arguments. + # Keys -list, -valgrind, -listfailed are recognized, all other parameters + # added to match list to filter tests. + + proc parseArgs {} { + variable matchList + upvar #0 argv argv + + set options { + {list "List tests and exit"} + {valgrind "Run tests under valgrind and check memory errors"} + {-help "Print this message"} + } + + if {[ catch { + array set p [ ::cmdline::getKnownOptions argv $options ] + } msg ]} { + puts stderr $msg + exit 1 + } + if {$p(-help)} { + puts [ ::cmdline::usage $options ] + exit 1 + } + variable listTests $p(list) + variable valgrind $p(valgrind) + + if {[ llength $argv ] != 0} { + set matchList $argv + } else { + set matchList * + } + } + + # wrapIfNeeded -- + # + # Wrap executable using valgrind if valgrinding is enabled (environment + # variable VALGRIND is set to 1). + # If env. variable VALGRIND_ARGS is present, it recognized as + # space-separated list of additional arguments for valgrind (for example, + # VALGRIND_ARGS="--gen-suppressions=all"). + + proc wrapIfNeeded {command} { + variable valgrind + + if {$valgrind} { + set additional {} + if {[ info exists ::env(VALGRIND_ARGS) ]} { + set additional [ split $::env(VALGRIND_ARGS) ] + } + set res [ list valgrind --leak-check=full --track-origins=yes --suppressions=valgrind.supp {*}$additional ] + } else { + set res {} + } + lappend res $command + return $res + } + + # fstest -- + # + # Execute 'script', check return code and clean up. + # If script exited with error, error information and filesystem stderr + # printed. + # Test is executed only if id matches at least one pattern in matchList. + # + # Arguments: + # id Test ID + # description Test Description + # script Test body + + proc fstest {id description script} { + variable mounted false + variable error + variable numPassed + variable numFailed + variable numSkipped + variable tmpdir + variable mountdir + variable matchList + variable failed + upvar fname fname + set fname {} + + set do false + foreach match $matchList { + if {[ string match $match $id ]} { + set do true + break + } + } + if {!$do} { + incr numSkipped + return + } + + file mkdir $tmpdir + set mountdir "[ file join $tmpdir mountPoint ]-$id" + file mkdir $mountdir + set code [ catch { + if {[ catch { + uplevel $script + } err opts ]} { + if {[ catch { + umount + } err2 opts2 ]} { + dict set opts2 -cause $opts + return -options $opts2 + } + return -options $opts + } + } ret opts ] + switch $code { + 0 { + # TCL_OK + incr numPassed + } + 1 { + # TCL_ERROR + forceumount + puts "\nTest `$id' ($description) failed." + puts "" + puts "Error: [ dict get $opts -errorinfo ]" + puts "Error code [ dict get $opts -code ], errorcode [ dict get $opts -errorcode ]" + if {[ dict exists $opts -cause ]} { + set opts [ dict get $opts -cause ] + puts "Caused by:" + puts "Error: [ dict get $opts -errorinfo ]" + puts "Error code [ dict get $opts -code ], errorcode [ dict get $opts -errorcode ]" + } + puts "" + puts "Filesystem output: [ join $error \n ]" + + lappend failed $id + incr numFailed + } + default { + puts "\nTest `$id' ($description) returned incorrect code($code): [ lindex {OK ERROR RETURN BREAK CONTINUE} $code ]" + incr numSkipped + } + } + file delete -force $tmpdir + } + + # mount -- + # + # Mount fuse-zip on archive fname to mountdir. + # Filesystem process started in background but without detaching from + # terminal and with debug mode enabled. Messages sent to stderr are + # parsed to determine finish of file system initialization process. + # + # Variables: + # fname Archive file name (from caller context) + # Arguments: + # args Additional file system arguments + + proc mount {args} { + upvar fname fname + variable binary + variable mountdir + variable stopped + variable output + variable error + variable mounted + variable initializationState + + set ns [ namespace current ] + set ${ns}::status {STARTED} + # if file system stopped in abnormal way, variable + # 'initializationState' is set to exit from vwait block. + set cmd [ list ${ns}::status {write unset} \ + [ list apply [ list {args} { + variable initializationState exited + } $ns ] ] \ + ] + trace add variable {*}$cmd + set output {} + set error {} + blt::bgexec ${ns}::status \ + -output ${ns}::output \ + -onerror ${ns}::processError \ + -linebuffered true \ + {*}[ wrapIfNeeded $binary ] {*}$args -d $fname $mountdir & + + vwait ${ns}::initializationState + trace remove variable {*}$cmd + if {$initializationState eq "exited"} { + error "Filesystem not mounted (status=[ set ${ns}::status ])" {} NOTMOUNTED + } + set mounted true + } + + # processError -- + # + # Process each line of stderr of called file system. + # Each line is appended to 'error' list to accumulate full output. + # If first message successfully went from filesystem, it assumed as + # mounted and listeners are notified by setting variable + # 'initializationState'. + # + # Arguments: + # data Line to process + + proc processError {data} { + variable initializationState + variable error + + if {[ regexp {^ unique: 1,.*[Ss]uccess.*, outsize: \d+$} $data ]} { + set initializationState success + } + lappend error $data + } + + # umount -- + # + # Unmount filesystem. + # Firstly try to umount FS in a standard way via fusermount -uz. + # If file system is not unmounted in a specified timeout, kill filesystem + # process and unmount in the hard way (fusermount -uz). + # If file system is already unmounted. do nothing. + + proc umount {} { + variable mounted + variable mountdir + variable status + variable timeout + variable output + variable error + variable valgrind + + if {!$mounted} { + return + } + + set ns [ namespace current ] + set statusVar ${ns}::status + if {$status ne ""} { + # not yet stopped + if {[ catch { + exec fusermount -uz $mountdir + } err opts ]} { + puts "Fusermount error: $err" + } + set afterId [ after $timeout [ list set $statusVar KILLED ] ] + vwait $statusVar + after cancel $afterId + } + lassign $status state pid code msg + set status {} + set mounted false + if {$state eq "KILLED"} { + catch {exec fusermount -uz $mountdir} + error "Filesystem unmounting timed out" + } else { + if {$code != 0} { + error "Filesystem returned error (code=$code)" + } + if {$output ne ""} { + error "Unexpected output from filesystem: $output" + } + checkValgrindOutput [ join $error \n ] + } + } + + # checkValgrindOutput -- + # + # Check valgrind output (if enabled) and throw error if at least one leak + # detected. + + proc checkValgrindOutput {data} { + variable valgrind + + if {$valgrind} { + if {[ regexp {==\d+== ERROR SUMMARY: (\d+) errors from (\d+) contexts \(suppressed: (\d+) from (\d+)\)} \ + [ lindex [ split $data "\n" ] end ] dummy errors contexts suppressed total ]} { + if {$errors != 0} { + error "valgrind: $errors memory errors detected!" + } + } else { + error "Unable to parse valgrind output" + } + } + } + + # forceumount -- + # + # Force kill filesystem process and free mountpoint + + proc forceumount {} { + variable mountdir + variable status + + catch {exec fusermount -u $mountdir} + set [ namespace current ]::status FORCE-KILLED + catch {exec fusermount -uz $mountdir} + } + + # create -- + # + # Create specified list of files and directories in specified directory + # (relative to tmpdir). + # + # Arguments: + # dir Destination directory + # files List of files and directories to add + # Format of item: + # / + + proc createContent {dir files} { + variable tmpdir + + file delete -force $tmpdir/$dir + file mkdir $tmpdir/$dir + + set pos 0 + while {$pos < [ llength $files ]} { + set name [ lindex $files $pos ] + incr pos + if {[ string index $name end ] == "/"} { + file mkdir $tmpdir/$dir/$name + } else { + makeFile [ lindex $files $pos ] $dir/$name + incr pos + } + } + } + + # create -- + # + # Create archive for tests containing specified list of files and + # directories and set it name to variable fname in caller context. + # + # Arguments: + # files List of files (as for createContent command) + + proc create {files} { + variable tmpdir + upvar fname fname + + set fname $tmpdir/test.zip + + createContent archiveSource $files + set pwd [ pwd ] + cd $tmpdir/archiveSource + if {[ catch { + exec zip -r $fname {*}[ glob * ] + } err opts ]} { + cd $pwd + return -options $opts + } + cd $pwd + } + + # check -- + # + # Check archive integrity by using 'unzip -t' command. + # Check file content with expected. + # If any problem detected, error is thrown. + # + # Arguments: + # files List of files (as for createContent command) + + proc check {files} { + upvar fname fname + variable tmpdir + + exec unzip -t $fname + + createContent expectedResult $files + file delete -force $tmpdir/testResult + file mkdir $tmpdir/testResult + exec unzip -d $tmpdir/testResult $fname + exec diff -ura $tmpdir/testResult $tmpdir/expectedResult + } + + # stripValgrindOutput -- + # + # Strip valgrind's output from data. If valgrinding is not enabled, + # return data as is. + + proc stripValgrindOutput {data} { + set res {} + foreach line [ split $data "\n" ] { + if {![ regexp {^==\d+== } $line ]} { + lappend res $line + } + } + return [ join $res "\n" ] + } + + # makeFile -- + # + # Version of tclTest::makeFile that does not creates newline at the end + # of file. + + proc makeFile {contents name {directory {}}} { + variable tmpdir + + if {$directory eq ""} { + set fname $tmpdir/$name + } else { + set fname $directory/$name + } + file mkdir [ file dirname $fname ] + set f [ open $fname w ] + puts -nonewline $f $contents + close $f + } + + # finalize -- + # + # Clean up temporary files, print statistics and exit. + + proc finalize {} { + variable numPassed + variable numFailed + variable numSkipped + variable tmpdir + variable listTests + variable failed + + forceumount + file delete -force $tmpdir + + if {!$listTests} { + if {[ llength $failed ] > 0} { + puts "Failed tests: [ join $failed ]" + } + puts [ format {Total: %3d Passed: %3d Failed: %3d Skipped %3d} \ + [ expr {$numPassed + $numFailed + $numSkipped} ] \ + $numPassed $numFailed $numSkipped \ + ] + if {$numFailed != 0} { + exit 1 + } else { + exit 0 + } + } else { + exit 0 + } + } + + ############################################################ + # INITIALIZATION + ############################################################ + + parseArgs + + signal trap {HUP INT TERM} "puts stderr Interrupt; ::[ namespace current ]::finalize" + + if {$listTests} { + # Stub for fstest that only prints test IDs and descriptions + + proc fstest {id description script} { + puts [ format "%-40s %s" $id $description ] + } + } + + ############################################################ + # TESTS + ############################################################ + + fstest usage {Usage test} { + lassign [ pipe ] r w + if {![ catch {exec -ignorestderr {*}[ wrapIfNeeded $binary ] 2>@ $w} res opts ]} { + close $w + close $r + error "Error code should be non-zero!" + } + close $w + set data [ string trim [ read $r ] ] + close $r + checkValgrindOutput $data + if {![ regexp {^usage: fuse-zip } [ stripValgrindOutput $data ] ]} { + error "Usage info expected, but `$data' given" + } + } + + fstest version {Version test} { + lassign [ pipe ] r w + if {![ catch {exec -ignorestderr {*}[ wrapIfNeeded $binary ] -V 2>@ $w} res opts ]} { + close $w + close $r + error "Error code should be non-zero!" + } + close $w + set data [ string trim [ read $r ] ] + close $r + checkValgrindOutput $data + if {![ regexp {^(.*?version.*?\d+\.\d+\n)+$} \ + "[ stripValgrindOutput $data ]\n" ]} { + error "Version info expected" + } + } + + fstest bad-mountpoint {Bad mountpoint} { + file delete -force $mountdir + if {![ catch {mount} err opts ] && + [ dict get $opts -errorcode ] eq "NOTMOUNTED"} { + error "Mount error is expected" + } else { + set msg [ stripValgrindOutput [ join $error \n ] ] + if {![ regexp {^fuse: bad mount point} $msg ]} { + error "Invalid error message" + } + } + } + + fstest bad-archive {Bad archive} { + set fname data/bad-archive.zip + if {![ catch {mount} err opts ] && + [ dict get $opts -errorcode ] eq "NOTMOUNTED"} { + error "Mount error is expected" + } else { + set msg [ stripValgrindOutput [ join $error \n ] ] + if {![ regexp {^fuse-zip: cannot open zip archive} $msg ]} { + error "Invalid error message" + } + } + } + + fstest mount-umount {Mount and unmount nonexistent file} { + set fname $tmpdir/nonexistent.zip + mount + umount + if {[ file exist $fname ]} { + error "Archive should not exist!" + } + } + + fstest add-file-to-empty-archive {Add file to empty archive} { + set fname $tmpdir/empty.zip + + mount + makeFile {file content} somefile $mountdir + umount + + check { + somefile {file content} + } + } + + fstest add-file {Add file to archive} { + create { + foo.bar foobar + f/ + f/o/ + f/o/o content + foo/ + foo/bar foo-bar + } + mount + makeFile moo-moo moo $mountdir + umount + + check { + moo moo-moo + foo.bar foobar + f/ + f/o/ + f/o/o content + foo/ + foo/bar foo-bar + } + } + + fstest add-dir {Add directory to archive} { + create { + foo.bar foobar + f/ + f/o/ + f/o/o content + foo/ + foo/bar foo-bar + } + mount + file mkdir $mountdir/foo/first + file mkdir $mountdir/foo/first/second + umount + + check { + foo.bar foobar + f/ + f/o/ + f/o/o content + foo/ + foo/bar foo-bar + foo/first/ + foo/first/second/ + } + } + + fstest add-remove-file {Add file to archive and remove it} { + create { + foo.bar foobar + f/ + f/o/ + f/o/o content + foo/ + foo/bar foo-bar + } + mount + makeFile moo-moo moo $mountdir + file delete $mountdir/moo + umount + + check { + foo.bar foobar + f/ + f/o/ + f/o/o content + foo/ + foo/bar foo-bar + } + } + + fstest add-truncate-file {Add file to archive and truncate it} { + create { + foo.bar foobar + } + mount + makeFile moo-moo moo $mountdir + ftruncate $mountdir/moo 3 + umount + + check { + foo.bar foobar + moo moo + } + } + + fstest add-truncate-remove-file {Add file to archive, truncate and remove it} { + create { + foo.bar foobar + } + mount + makeFile moo-moo moo $mountdir + ftruncate $mountdir/moo 3 + file delete $mountdir/moo + umount + + check { + foo.bar foobar + } + } + + fstest add-overwrite-file {Add file to archive and overwrite it} { + create { + foo.bar foobar + } + mount + makeFile moo-moo moo $mountdir + makeFile Gerasim moo $mountdir + umount + + check { + foo.bar foobar + moo Gerasim + } + } + + fstest remove-file {Remove file from archive} { + create { + filename.ext blah-blah + filename2.ext blah-blah + } + mount + file delete $mountdir/filename.ext + umount + check { + filename2.ext blah-blah + } + } + + fstest remove-dir {Remove directory from archive} { + create { + filename.ext blah-blah + foo/ + foo/moo {} + bar/ + } + mount + file delete -force $mountdir/foo + umount + check { + filename.ext blah-blah + bar/ + } + } + + fstest remove-nonexistent-dir {Remove nonexistent directory from archive} { + create { + filename.ext blah-blah + bar/ + } + mount + if {![ catch { + exec rmdir $mountdir/foo + } err opts ]} { + error "rmdir error is expected!" + } + umount + check { + filename.ext blah-blah + bar/ + } + } + + fstest remove-last-file {Remove last file from archive} { + create { + filename.ext blah-blah + } + mount + file delete $mountdir/filename.ext + umount + if {[ file exist $fname ]} { + puts [ exec cat $fname ] + error "Archive should not exist!" + } + } + + fstest overwrite-file {Overwrite existing file} { + create { + filename.ext blah-blah + } + mount + + set f [ open $mountdir/filename.ext w ] + puts $f "Be-be-be!" + close $f + umount + check { + filename.ext "Be-be-be!\n" + } + } + + fstest truncate {Truncate existing file} { + create { + filename.ext blah-blah + } + mount + ftruncate $mountdir/filename.ext 5 + + set f [ open $mountdir/filename.ext r ] + set data [ read $f ] + close $f + + if {[ string compare $data "blah-" ] != 0} { + error "Invalid content: $data" + } + + umount + check { + filename.ext blah- + } + } + + fstest truncate-remove {Truncate existing file and delete it} { + create { + filename.ext blah-blah + other other + } + mount + ftruncate $mountdir/filename.ext 5 + file delete $mountdir/filename.ext + + umount + check { + other other + } + } + + fstest truncate-on-chunk-boundary {Truncate existing file on a chunk boundary} { + create [ list \ + filename.ext [ string repeat a 8192 ] + ] + mount + ftruncate $mountdir/filename.ext 4096 + umount + check [ list \ + filename.ext [ string repeat a 4096 ] \ + ] + } + + fstest truncate-to-zero {Truncate existing file (to zero size)} { + create { + filename.ext blah-blah + } + mount + ftruncate $mountdir/filename.ext 0 + umount + check { + filename.ext {} + } + } + + fstest truncate-after-end-same-chunk {Truncate existing file after the end (without new chunks creation)} { + create { + filename.ext blah-blah + } + mount + ftruncate $mountdir/filename.ext 11 + + set f [ open $mountdir/filename.ext r ] + set data [ read $f ] + close $f + + if {[ string compare $data "blah-blah\x00\x00" ] != 0} { + error "Invalid content: $data" + } + + umount + check { + filename.ext "blah-blah\x00\x00" + } + } + + fstest truncate-after-end-other-chunk {Truncate existing file after the end (with new chunk creation)} { + create { + filename.ext blah-blah + } + mount + ftruncate $mountdir/filename.ext 5009 + set content "blah-blah[ string repeat "\x00" 5000 ]" + + set f [ open $mountdir/filename.ext r ] + set data [ read $f ] + close $f + + if {[ string compare $data $content ] != 0} { + error "Invalid content: $data" + } + + umount + check [ list \ + filename.ext $content \ + ] + } + + fstest truncate-expand-read {Truncate file, expand it and read} { + create { + filename.ext blah-blah + } + mount + ftruncate $mountdir/filename.ext 4 + ftruncate $mountdir/filename.ext 9 + set content "blah[ string repeat "\x00" 5 ]" + + set f [ open $mountdir/filename.ext r ] + set data [ read $f ] + close $f + + if {[ string compare $data $content ] != 0} { + error "Invalid content: $data" + } + + umount + check [ list \ + filename.ext $content \ + ] + } + + fstest truncate-after-end-other-chunk-twice {Truncate existing file after the end (with new chunk creation, twice)} { + create { + filename.ext blah-blah + } + mount + ftruncate $mountdir/filename.ext 5 + ftruncate $mountdir/filename.ext 5005 + + set content "blah-[ string repeat "\x00" 5000 ]" + + set f [ open $mountdir/filename.ext r ] + set data [ read $f ] + close $f + + if {[ string compare $data $content ] != 0} { + error "Invalid content: $data" + } + + umount + check [ list \ + filename.ext $content \ + ] + } + + fstest sparse-file {Sparse files test} { + set fname $tmpdir/archive.zip + mount + + set count 8201 + + set f [ open $mountdir/filename.ext w ] + seek $f $count start + puts -nonewline $f blah + close $f + + set content "[ string repeat "\x00" $count ]blah" + + set f [ open $mountdir/filename.ext r ] + set data [ read $f ] + close $f + + if {[ string compare $content $data ] != 0} { + error "Invalid file content: $data" + } + + umount + check [ list \ + filename.ext $content \ + ] + } + + fstest sparse-file-2 {Sparse files test 2} { + create { + filename.ext start + } + mount + + set count 4096 + + set f [ open $mountdir/filename.ext r+ ] + seek $f [ expr {5+$count} ] start + puts -nonewline $f end + close $f + + set content "start[ string repeat "\x00" $count ]end" + + set f [ open $mountdir/filename.ext r ] + set data [ read $f ] + close $f + + if {[ string compare $content $data ] != 0} { + error "Invalid file content: $data" + } + + umount + check [ list \ + filename.ext $content \ + ] + } + + fstest sparse-truncate {Truncate sparse file} { + create { + filename.ext {} + } + mount + + set count 4078 + + set f [ open $mountdir/filename.ext r+ ] + seek $f 8208 start + puts -nonewline $f data + flush $f + ftruncate -fileid $f $count + close $f + + set content "[ string repeat "\x00" $count ]" + + set f [ open $mountdir/filename.ext r ] + set data [ read $f ] + close $f + + if {[ string compare $content $data ] != 0} { + error "Invalid file content: $data" + } + + umount + check [ list \ + filename.ext $content \ + ] + } + + fstest read-zero {Read zero-length file from archive} { + create { + filename.ext {} + } + mount + set f [ open $mountdir/filename.ext r ] + set data [ read $f ] + close $f + + if {[ string compare $data "" ] != 0} { + error "Invalid content: $data" + } + + umount + } + + fstest read-file {Read file from archive} { + create { + filename.ext blah-blah + } + mount + set f [ open $mountdir/filename.ext r ] + set data [ read $f ] + close $f + + if {[ string compare $data "blah-blah" ] != 0} { + error "Invalid content: $data" + } + + umount + } + + fstest read-file-two-handles {Read file from archive (two handles)} { + set content [ string repeat "abcdef" 3000 ] + set l [ string length $content ] + create [ list \ + filename.ext $content \ + ] + mount + set f1 [ open $mountdir/filename.ext r ] + set f2 [ open $mountdir/filename.ext r ] + set positions { + {0 6} + {10 1000} + {4097 322} + {27 15} + {1 1} + {3333 11} + } + for {set i 0} {$i < [ llength $positions ]} {incr i} { + lassign [ lindex $positions $i ] off size + seek $f1 $off + set data [ read $f1 $size ] + set expected [ string range $content $off [ expr {$off + $size - 1} ] ] + assert {$data eq $expected} + + lassign [ lindex $positions end-$i ] off size + seek $f2 $off + set data [ read $f2 $size ] + set expected [ string range $content $off [ expr {$off + $size - 1} ] ] + assert {$data eq $expected} + } + close $f1 + close $f2 + umount + } + + fstest read-file-random-access {Read file from archive (random access)} { + set content [ string repeat "abcdef" 3000 ] + set l [ string length $content ] + create [ list \ + filename.ext $content \ + ] + mount + set f [ open $mountdir/filename.ext r ] + foreach {off size} { + 0 6 + 10 1000 + 4097 322 + 27 15 + 1 1 + 3333 11 + } { + seek $f $off + set data [ read $f $size ] + set expected [ string range $content $off [ expr {$off + $size - 1} ] ] + assert {$data eq $expected} + } + close $f + umount + } + + fstest read-file-three-chunks {Read file from archive (3 chunks)} { + set content [ string repeat "a" [ expr {4096*3-100} ] ] + create [ list \ + filename.ext $content \ + ] + mount + set f [ open $mountdir/filename.ext r ] + set data [ read $f ] + close $f + + if {[ string compare $data $content ] != 0} { + error "Invalid content: '$data'" + } + + umount + } + + fstest read-on-end {Read on the end of file} { + set content [ string repeat "a" 100 ] + create [ list \ + filename.ext $content \ + ] + mount + set f [ open $mountdir/filename.ext r ] + fconfigure $f -encoding binary + seek $f 98 + set data [ read $f 4 ] + close $f + + if {[ string compare $data "aa" ] != 0} { + error "Invalid content: '$data'" + } + + umount + } + + fstest read-after-end {Read after the end of file} { + set content [ string repeat "b" 100 ] + create [ list \ + filename.ext $content \ + ] + mount + set f [ open $mountdir/filename.ext r ] + fconfigure $f -encoding binary + seek $f 102 + set data [ read $f 4 ] + close $f + + if {[ string compare $data "" ] != 0} { + error "Invalid content: '$data'" + } + + umount + } + + fstest read-bad-crc {Read file from archive (bad CRC)} { + set fname data/bad-crc.zip + + mount + set res [ catch { + set f [ open $mountdir/bash.txt r ] + set data [ read $f ] + close $f + } err opts ] + + umount + + if {$res == 0 || [ lrange [ dict get $opts -errorcode ] 0 1 ] ne "POSIX EIO"} { + error "Read error expected" + } + } + + fstest read-bad-crc-twice {Read file from archive (bad CRC, twice), changeset 80ad59679639} { + set fname data/bad-crc.zip + + mount + foreach step {1st 2nd} { + set res [ catch { + set f [ open $mountdir/bash.txt r ] + set data [ read $f ] + close $f + } err opts ] + if {$res == 0 || [ lrange [ dict get $opts -errorcode ] 0 1 ] ne "POSIX EIO"} { + error "Read error expected on step $step" + } + } + + umount + + } + + fstest find {Check that find is working on a filesystem} { + create { + foo.bar foobar + f/ + f/o/ + f/o/o content + foo/ + foo/bar foo-bar + } + set in { + foo.bar + f + f/o + f/o/o + foo + foo/bar + } + mount + set data {} + set l [ string length "$mountdir/" ] + foreach line [ split [ exec find $mountdir ] "\n" ] { + if {[ string length $line ] > $l} { + lappend data [ string range $line $l end ] + } + } + umount + + if {[ lsort $in ] != [ lsort $data ]} { + error "Invalid list of files returned: $data" + } + } + + fstest append {Append to file} { + create { + foo foo + } + mount + set f [ open $mountdir/foo a ] + puts -nonewline $f bar + close $f + + umount + check { + foo foobar + } + } + + fstest compare-source-and-mountpoint {Compare source files with mountpoint content} { + create [ list \ + foo.bar foobar \ + f/ \ + f/o/ \ + f/o/o content \ + foo/ \ + foo/bar foo-bar \ + bigFile [ string repeat "substances! " 9000 ] \ + zerofile {} \ + longName_longName_longName_longName_longName_longName_longName_longName_longName_longName_longName_longName_longName_longName_longName_longName_longName_longName_longName_longName_longName_longName_longName_longName_longName {} + ] + mount + exec diff -Nura $tmpdir/archiveSource $mountdir + umount + } + + fstest iconv-cp866 {Check that archive from 'other' OS correctly mounted} { + set fname data/cp866.zip + + mount -o modules=iconv,from_code=cp866 + + if {[ lsort [ glob -directory $mountdir -tails * ] ] ne + [ lsort "{\u0422\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0439 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442.txt} \u0414\u0430\u0442\u0430" ]} { + error "File names incorrectly decoded: [ glob -directory $mountdir * ]" + } + + umount + } + + fstest rename-file {Rename file} { + create { + foo.bar foobar + } + mount + file rename $mountdir/foo.bar $mountdir/john_doe + umount + + check { + john_doe foobar + } + } + + fstest rename-file-new-dir {Rename file (new directory creation)} { + create { + foo.bar foobar + squirrel belka + } + mount + file mkdir $mountdir/test + file rename $mountdir/foo.bar $mountdir/test/john_doe + set ls [ glob -directory $mountdir -tails * ] + if {![ ::struct::set equal {test squirrel} $ls ]} { + error "Invalid files in /: $ls" + } + set ls [ glob -directory $mountdir/test -tails * ] + if {![ ::struct::set equal {john_doe} $ls ]} { + error "Invalid files in /test: $ls" + } + umount + + check { + test/john_doe foobar + squirrel belka + } + } + + fstest rename-file-new-path {Rename file (change directory)} { + create { + foo.bar foobar + test/ + test/blah {blah-blah} + } + mount + file rename $mountdir/foo.bar $mountdir/test/john_doe + set ls [ glob -directory $mountdir -tails * ] + if {![ ::struct::set equal {test} $ls ]} { + error "Invalid files in /: $ls" + } + set ls [ glob -directory $mountdir/test -tails * ] + if {![ ::struct::set equal {blah john_doe} $ls ]} { + error "Invalid files in /test: $ls" + } + umount + + check { + test/john_doe foobar + test/blah {blah-blah} + } + } + + fstest rename-empty-dir {Rename empty directory} { + create { + foo/ + } + mount + file rename $mountdir/foo $mountdir/bar + umount + + check { + bar/ + } + } + + fstest rename-non-empty-dir {Rename non-empty directory} { + create { + foo/john/doe {Hi} + foo/bar {content} + foo/duck/tape/1 1 + } + mount + file rename $mountdir/foo $mountdir/bar + umount + + check { + bar/john/doe {Hi} + bar/bar {content} + bar/duck/tape/1 1 + } + } + + fstest rename-non-empty-dir-new-path {Rename non-empty directory (new dir)} { + create { + foo/bar {content} + foo/john/doe {Hi} + foo/duck/tape/ + } + mount + file mkdir $mountdir/bar + file rename $mountdir/foo $mountdir/bar/duck + umount + + check { + bar/duck/bar {content} + bar/duck/john/doe {Hi} + bar/duck/duck/tape/ + } + } + + fstest rename-file-to-existent {Renamed file to existent file} { + create { + kitten {Meow} + puppy {Woof} + } + mount + file rename -force $mountdir/kitten $mountdir/puppy + umount + + check { + puppy {Meow} + } + } + + fstest size-file {Check file size} { + create { + foobar blah-blah + } + mount + assert {[ file size $mountdir/foobar ] == 9} + makeFile doe john $mountdir + assert {[ file size $mountdir/john ] == 3} + umount + check { + foobar blah-blah + john doe + } + } + + fstest size-dir {Check directory size} { + create { + foobar/ + } + mount + assert {[ file size $mountdir/foobar ] == 0} + file mkdir $mountdir/john + assert {[ file size $mountdir/john ] == 0} + umount + check { + foobar/ + john/ + } + } + + finalize +} +namespace delete ::util::test + +# vim: set ft=tcl: diff -Nru fuse-zip-0.2.8/tests/blackbox/Makefile fuse-zip-0.2.12/tests/blackbox/Makefile --- fuse-zip-0.2.8/tests/blackbox/Makefile 1970-01-01 01:00:00.000000000 +0100 +++ fuse-zip-0.2.12/tests/blackbox/Makefile 2010-02-07 00:19:02.000000000 +0000 @@ -0,0 +1,7 @@ +all: + ./fuse-zip.test + +valgrind: + ./fuse-zip.test -valgrind + +.PHONY: all diff -Nru fuse-zip-0.2.8/tests/blackbox/README fuse-zip-0.2.12/tests/blackbox/README --- fuse-zip-0.2.8/tests/blackbox/README 1970-01-01 01:00:00.000000000 +0100 +++ fuse-zip-0.2.12/tests/blackbox/README 2010-02-07 00:19:02.000000000 +0000 @@ -0,0 +1,29 @@ +Test suite for black box testing of fuse-zip. + +You need the following programs and libraries: +Tcl >= 8.5 +tcllib >= 1.10 +BLT +Tclx +zip +diff +valgrind + +To run testsuite invoke command +$ ./fuse-zip.test +or +$ ./fuse-zip.test -valgrind +to check memory errors with valgrind. + +Additional options can be passed to valgrind using VALGRIND_ARGS environment +variable. For example, you can ask valgrind to generate suppressions (that can +be added to file valgrind.supp) by specifying the following variable value: +VALGRIND_ARGS="--gen-suppressions=all" + +To get list of all tests use -list switch. + +To run only interected tests instead of full test suite, pass test identifier +patterns (glob-like) to testsuite script. +For example: +$ ./fuse-zip.test 'truncate-*' 'rename-*' + diff -Nru fuse-zip-0.2.8/tests/blackbox/valgrind.supp fuse-zip-0.2.12/tests/blackbox/valgrind.supp --- fuse-zip-0.2.8/tests/blackbox/valgrind.supp 1970-01-01 01:00:00.000000000 +0100 +++ fuse-zip-0.2.12/tests/blackbox/valgrind.supp 2010-02-07 00:19:02.000000000 +0000 @@ -0,0 +1,51 @@ +# add-dir, truncate-to-zero +{ + Unitialized memory read in zlib's deflate (not a problem) + Memcheck:Cond + fun:deflate + obj:/usr/lib/libzip.so.1.0.0 + fun:zip_close + fun:_ZN11FuseZipDataD1Ev + fun:_ZL15fusezip_destroyPv + fun:fuse_fs_destroy + obj:/usr/lib/libfuse.so.2.8.1 + obj:/usr/lib/libfuse.so.2.8.1 + fun:fuse_session_destroy + fun:fuse_destroy + obj:/usr/lib/libfuse.so.2.8.1 + fun:main +} + +# truncate-on-chunk-boundary +{ + Unitialized memory read in zlib's inflateReset2 (not a problem) + Memcheck:Cond + fun:inflateReset2 + fun:inflateInit2_ + fun:zip_fopen_index + fun:_ZN9BigBufferC1EP3zipil + fun:_ZN8FileNode4openEv + fun:_ZL16fusezip_truncatePKcl + obj:/usr/lib/libfuse.so.2.8.1 + obj:/usr/lib/libfuse.so.2.8.1 + fun:fuse_session_loop + fun:main +} + +# read-file-three-chunks +{ + Unitialized memory read in zlib's inflateReset2 (not a problem) + Memcheck:Cond + fun:inflateReset2 + fun:inflateInit2_ + fun:zip_fopen_index + fun:_ZN9BigBufferC1EP3zipil + fun:_ZN8FileNode4openEv + fun:_ZL12fusezip_openPKcP14fuse_file_info + fun:fuse_fs_open + obj:/usr/lib/libfuse.so.2.8.1 + obj:/usr/lib/libfuse.so.2.8.1 + fun:fuse_session_loop + fun:main +} + diff -Nru fuse-zip-0.2.8/tests/Makefile fuse-zip-0.2.12/tests/Makefile --- fuse-zip-0.2.8/tests/Makefile 1970-01-01 01:00:00.000000000 +0100 +++ fuse-zip-0.2.12/tests/Makefile 2010-02-07 00:19:02.000000000 +0000 @@ -0,0 +1,12 @@ +SUITES=blackbox whitebox + +all: $(SUITES) + +valgrind: + make -C blackbox valgrind + +$(SUITES): + make -C $@ + +.PHONY: all $(SUITES) + diff -Nru fuse-zip-0.2.8/tests/whitebox/Makefile fuse-zip-0.2.12/tests/whitebox/Makefile --- fuse-zip-0.2.8/tests/whitebox/Makefile 1970-01-01 01:00:00.000000000 +0100 +++ fuse-zip-0.2.12/tests/whitebox/Makefile 2010-02-07 00:19:02.000000000 +0000 @@ -0,0 +1,2 @@ +all: + diff -Nru fuse-zip-0.2.8/TODO fuse-zip-0.2.12/TODO --- fuse-zip-0.2.8/TODO 1970-01-01 01:00:00.000000000 +0100 +++ fuse-zip-0.2.12/TODO 2010-02-07 00:19:02.000000000 +0000 @@ -0,0 +1,10 @@ +* whitebox test suite +* more unified exceptions and error cases handling +* replace vector with (s)list in BigBuffer +* rewrite files tree: keep only short path information to simplify rename(), use hash_map (unordered_map) instead of map +* log zip file errors to syslog +* truncate() without open-close file +* incapsulate call for zip_* into FileNode +* speed up position determination using bitwise operations +* iterate over chunks by iterator instead of indexing +* write memory cache to /tmp on fsync(). May be write compressed stream diff -Nru fuse-zip-0.2.8/types.h fuse-zip-0.2.12/types.h --- fuse-zip-0.2.8/types.h 2009-11-18 18:09:36.000000000 +0000 +++ fuse-zip-0.2.12/types.h 2010-02-07 00:19:02.000000000 +0000 @@ -1,6 +1,7 @@ //////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2008 by Alexander Galanin // -// gaa.nnov@mail.ru // +// Copyright (C) 2008-2010 by Alexander Galanin // +// al@galanin.nnov.ru // +// http://galanin.nnov.ru/~al // // // // This program is free software; you can redistribute it and/or modify // // it under the terms of the GNU Lesser General Public License as //