├── .gitignore ├── .travis.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake ├── DefaultCompilerFlags.cmake ├── DetectGcc.cmake └── FindPostgres.cmake ├── docker ├── Dockerfile ├── smb.conf └── tableau-samba.sh ├── docs └── install-fuse-tableaufs-on-osx.markdown └── src ├── CMakeLists.txt ├── tableaufs.c ├── tableaufs.h ├── tableaufs_version.h ├── workgroup.c └── workgroup.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | *.exe 25 | *.out 26 | *.app 27 | *.i*86 28 | *.x86_64 29 | *.hex 30 | 31 | # Debug files 32 | *.dSYM/ 33 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | 3 | compiler: 4 | - gcc 5 | - clang 6 | 7 | before_install: 8 | - if [ $TRAVIS_OS_NAME == linux ]; then sudo apt-get update && sudo apt-get install -y libpq-dev libfuse-dev fuse-utils libfuse2 ; fi 9 | - if [ $TRAVIS_OS_NAME == osx ]; then brew tap caskroom/cask; brew cask install osxfuse; sudo /bin/cp -RfX /usr/local/opt/osxfuse/Library/Filesystems/osxfusefs.fs /Library/Filesystems; sudo chmod +s /Library/Filesystems/osxfusefs.fs/Support/load_osxfusefs; sudo mknod /dev/fuse c 10 229; sudo chmod 666 /dev/fuse; fi 10 | 11 | script: cmake . && make 12 | 13 | branches: 14 | only: 15 | - master 16 | 17 | os: 18 | - linux 19 | - osx 20 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2012-2015, Tamas Foldi, Starschema 2 | # 3 | # Redistribution and use in source and binary forms, with or without 4 | # modification, are permitted provided that the following conditions 5 | # are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright 8 | # notice, this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 14 | # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 16 | # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 17 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 18 | # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 20 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | cmake_minimum_required(VERSION 2.6) 25 | project(fuse-tableaufs C) 26 | 27 | 28 | set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) 29 | include(DefaultCompilerFlags) 30 | include(FindPkgConfig) 31 | 32 | # Try to find Postgres 33 | find_package(Postgres 9.0 REQUIRED) 34 | 35 | # Load FUSE 36 | pkg_check_modules( FUSE REQUIRED fuse>=2.7.0) 37 | 38 | # Add the include directories 39 | include_directories( 40 | ${POSTGRES_INCLUDE_DIR} 41 | ${FUSE_INCLUDE_DIRS} ) 42 | 43 | 44 | # SUBDIRS(...) is deprecated 45 | # 46 | # ADD_SUBDIRECTORY processes the subdir at the time it is called, 47 | # whereas SUBDIRS pushes the dirs onto a list which is processed 48 | # at the end of the current cmakelists file - this is the old 49 | # behaviour and some vars are initialized 'out of order' -or at 50 | # least in unexpected order 51 | add_subdirectory(src) 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Foldi Tamas, Starschema Ltd, www.starschema.net 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of fuse-tableaufs nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FUSE-TableauFS: File System on Tableau Repository [![Build Status](https://travis-ci.org/tfoldi/fuse-tableaufs.svg?branch=master)](https://travis-ci.org/tfoldi/fuse-tableaufs) 2 | 3 | TableauFS is a FUSE based userspace file system driver built on top of Tableau's repository server. It allows to mount tableau servers with its datasources and workbooks directly to the file system. File information and contents are retrieved on-access without any local persistence or caching. By default the file system connects directly to the postgresql database using `readonly` credentials, however, read-write mode is also implemented using twlwgadmin user. 4 | 5 | ![working with files and directories on tableaufs](http://cdn.starschema.net/tableaufs.PNG) 6 | 7 | ## Use cases 8 | TableauFS helps Tableau Server administrators in several cases: 9 | 10 | - Save/copy workbooks or data sources to other, persistent file systems 11 | - Run workbook audit tools like [Workbook Audit Tool](http://databoss.starschema.net/how-to-use-twb-auditor-with-tableaufs-audit-tableau-server-files-directly/) or [TWIS](http://www.betterbi.biz/TWIS.html) 12 | - [Version control objects with any file based version control system (git, git-annex, subversion, mercurial, p4, etc.)](http://databoss.starschema.net/version-control-and-point-in-time-recovery-of-tableau-server-objects/) 13 | - Directly modify files in the repository without using any API 14 | - Migrate contents between servers 15 | - Grep strings in workbook definitions 16 | 17 | Your Tableau published objects will be visible as ordinary files, thus, the possibilities are unlimited. For more information please visit http://databoss.starschema.net/introducing-tableaufs-file-system-on-tableau-server-repository/ 18 | 19 | 20 | ## Installation 21 | 22 | ### System requirements 23 | At the moment only Linux, FreeBSD and OSX are supported. If you have to use files from Windows you can still run it form docker or mount thru WebDAV. If required, I can make a dokan or callbackfs based windows port, however, at the moment I am happy with this *nix only version. 24 | ### Dependencies 25 | To compile the application you need at `cmake` (=> 2.6), `libpq` (>= 9.0) and `fuse` (=>2.9). 26 | 27 | ### Build 28 | Just type `cmake . && make && make install`and you will have everything installed. 29 | 30 | ### Configuring tableau server database 31 | 32 | To exploit all features (include read-write mode) you need `tblwgadmin` or similar user with superuser privilege while for read only access `readonly` user is almost enough. 33 | ![Enable and grant select to readonly user](http://databoss.starschema.net/wp-content/uploads/2015/05/enable-and-grant-select-to-readonly-user.png) 34 | 35 | The steps here: 36 | 37 | 1. Enable readonly user with `tabadmin dbpass --username readonly ` 38 | 2. Check your pgsql admin password in `tabsvc.yml` file. The default location is C:\ProgramData\Tableau\Tableau Server\config but depending on your ProgramData folder, this can be different. lease note that ProgramData folder can be hidden. 39 | 3. Go to Tableau Server\9.0\pgsql\bin folder and issue `psql -h localhost -p 8060 -U tblwgadmin workgroup` command and paste the password from `tabsvc.yml` 40 | 4. Execute the `grant select on pg_largeobject to readonly; `statement 41 | 42 | to leverage the full read only experience. It will not harm your system (this is still read only) but unsupported. 43 | 44 | 45 | 46 | ## Usage 47 | To mount a tableau server type: 48 | 49 | # tableaufs -o pghost=tabsrver.local,pgport=8060,pguser=readonly,pgpass=readonly /mnt/tableau-dev 50 | 51 | where -o stands for file system options. After executing you will see the file system in the mount list: 52 | 53 | tableaufs on /mnt/tableau-dev type fuse.tableaufs (rw,nosuid,nodev,relatime,user_id=0,group_id=0) 54 | 55 | To unmount, simple `umount /mnt/tableau-dev` 56 | 57 | ## Directory structure & File operations 58 | 59 | TableauFS maps Tableau repository to the following directory structure: 60 | 61 | /Sitename/Projectname/Workbook 1.tbw[x] 62 | /Sitename/Projectname/Workbook 2.tbw[x] 63 | /Sitename/Projectname/Datasource 1.tds[x] 64 | 65 | Read operations are fully supported while write support is implemented but still highly experimental. For rw mode you need read-write access to workbooks, datasources and pg_largeobjects tables. 66 | Last modification time is read from last\_updated columns while file sizes are actual sizes of the pg\_largeobjects. 67 | 68 | ## Questions 69 | 70 | **Is this supported or allowed by Tableau or any 3rd party?** 71 | 72 | Tableau FS uses the supported `readonly` repository user by default which is supported and allowed by Tableau. The file system part is completely unsupported, however, we are trying to help you on best effort basis. 73 | 74 | Accessing your repository in read-write mode is not supported. 75 | 76 | **I need this on windows, can I?** 77 | 78 | You should ramp up a unix server, install a webdav server on it (like apache+mod_dav) and mount it. The Docker image with webdav + tableaufs is here: https://registry.hub.docker.com/u/tfoldi/fuse-tableaufs/ 79 | 80 | **Do you have a binary installer?** 81 | 82 | No, but you can find a Dockerfile in docker folder, that should help. 83 | 84 | **Can I move my files to a version control repo?** 85 | 86 | Sure you can, [here is how.](http://databoss.starschema.net/version-control-and-point-in-time-recovery-of-tableau-server-objects/) 87 | 88 | **I have strange error regarding fuse device when I execute TableauFS from docker** 89 | 90 | Add `--privileged` to your `docker exec` command line. 91 | 92 | ## Issues / Known bugs 93 | 94 | 95 | Please send a pull request or open an issue if you have any problems. 96 | 97 | ## License 98 | 99 | Copyright . 2015 Tamas Foldi ([@tfoldi](http://twitter.com/tfoldi)), [Starschema](http://www.starschema.net/) Ltd. 100 | 101 | Distributed under BSD License. 102 | -------------------------------------------------------------------------------- /cmake/DefaultCompilerFlags.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2008,2009,2010, CodeSloop Team 2 | # 3 | # Redistribution and use in source and binary forms, with or without 4 | # modification, are permitted provided that the following conditions 5 | # are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright 8 | # notice, this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 14 | # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 16 | # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 17 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 18 | # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 20 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | INCLUDE(DetectGcc) 25 | 26 | SET(CMAKE_C_FLAGS_DEBUG " ${GCC_C_FLAGS_DEBUG} ${COMMON_C_FLAGS} ") 27 | SET(CMAKE_C_FLAGS_RELEASE " ${GCC_C_FLAGS_RELEASE} ${COMMON_C_FLAGS} ") 28 | 29 | 30 | # default is debug configuration 31 | SET(CMAKE_C_FLAGS ${CMAKE_C_FLAGS_DEBUG}) 32 | 33 | # change config if release 34 | IF(CMAKE_BUILD_TYPE MATCHES "release") 35 | SET(CMAKE_C_FLAGS ${CMAKE_C_FLAGS_RELEASE}) 36 | ENDIF(CMAKE_BUILD_TYPE MATCHES "release") 37 | 38 | MESSAGE(STATUS "Build type: " ${CMAKE_BUILD_TYPE}) 39 | MESSAGE(STATUS "C FLAGS: " ${CMAKE_C_FLAGS}) 40 | 41 | # -- EOF -- 42 | -------------------------------------------------------------------------------- /cmake/DetectGcc.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2008,2009,2010, CodeSloop Team 2 | # 3 | # Redistribution and use in source and binary forms, with or without 4 | # modification, are permitted provided that the following conditions 5 | # are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright 8 | # notice, this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 14 | # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 16 | # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 17 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 18 | # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 20 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | SET(GCC_COMMON_WARNINGS " -Wall -Wwrite-strings -Wcast-qual -Wpointer-arith -Wconversion -Wcomment -Wcast-align -Wshadow -Wredundant-decls ") 25 | 26 | SET(GCC_C_FLAGS_RELEASE "") 27 | SET(GCC_C_FLAGS_DEBUG "") 28 | 29 | IF(WIN32) 30 | IF(MSYS) 31 | SET(GCC_C_FLAGS_RELEASE " -O3 ${GCC_COMMON_WARNINGS} ") 32 | SET(GCC_C_FLAGS_DEBUG " -DDEBUG -g3 ${GCC_COMMON_WARNINGS} ") 33 | ENDIF(MSYS) 34 | ELSE(WIN32) 35 | SET(GCC_C_FLAGS_RELEASE " -O3 ${GCC_COMMON_WARNINGS} ") 36 | SET(GCC_C_FLAGS_DEBUG " -DDEBUG -g3 ${GCC_COMMON_WARNINGS} ") 37 | ENDIF(WIN32) 38 | 39 | # -- EOF -- 40 | -------------------------------------------------------------------------------- /cmake/FindPostgres.cmake: -------------------------------------------------------------------------------- 1 | # Find PostgreSQL 2 | # ~~~~~~~~~~~~~~~ 3 | # Copyright (c) 2007, Martin Dobias 4 | # 5 | # Modified by tfoldi@starschema.net (check for 64 bit lo functions) 6 | # 7 | # Redistribution and use is allowed according to the terms of the BSD license. 8 | # For details see the accompanying COPYING-CMAKE-SCRIPTS file. 9 | # 10 | # CMake module to search for PostgreSQL library 11 | # 12 | # pg_config is searched for in POSTGRES_CONFIG dir, 13 | # default /usr/bin 14 | # 15 | # If it's found it sets POSTGRES_FOUND to TRUE 16 | # and following variables are set: 17 | # POSTGRES_INCLUDE_DIR 18 | # POSTGRES_LIBRARY 19 | INCLUDE (CheckLibraryExists) 20 | 21 | IF(WIN32) 22 | IF (NOT POSTGRES_INCLUDE_DIR) 23 | FIND_PATH(POSTGRES_INCLUDE_DIR libpq-fe.h 24 | /usr/local/include 25 | /usr/include 26 | c:/msys/local/include 27 | "$ENV{LIB_DIR}/include/postgresql" 28 | "$ENV{LIB_DIR}/include" 29 | ) 30 | ENDIF (NOT POSTGRES_INCLUDE_DIR) 31 | 32 | IF (NOT POSTGRES_LIBRARY) 33 | FIND_LIBRARY(POSTGRES_LIBRARY NAMES pq libpq libpqdll PATHS 34 | /usr/local/lib 35 | /usr/lib 36 | c:/msys/local/lib 37 | "$ENV{LIB_DIR}/lib" 38 | ) 39 | ENDIF (NOT POSTGRES_LIBRARY) 40 | 41 | ELSE(WIN32) 42 | IF(UNIX) 43 | 44 | SET(POSTGRES_CONFIG_PREFER_PATH "$ENV{POSTGRES_HOME}/bin" CACHE STRING "preferred path to PG (pg_config)") 45 | FIND_PROGRAM(POSTGRES_CONFIG pg_config 46 | ${POSTGRES_CONFIG_PREFER_PATH} 47 | /usr/local/pgsql/bin/ 48 | /usr/local/bin/ 49 | /usr/bin/ 50 | ) 51 | # MESSAGE("DBG POSTGRES_CONFIG ${POSTGRES_CONFIG}") 52 | 53 | IF (POSTGRES_CONFIG) 54 | # set INCLUDE_DIR 55 | EXEC_PROGRAM(${POSTGRES_CONFIG} 56 | ARGS --includedir 57 | OUTPUT_VARIABLE PG_TMP) 58 | SET(POSTGRES_INCLUDE_DIR ${PG_TMP} CACHE STRING INTERNAL) 59 | 60 | # set LIBRARY_DIR 61 | EXEC_PROGRAM(${POSTGRES_CONFIG} 62 | ARGS --libdir 63 | OUTPUT_VARIABLE PG_TMP) 64 | IF (APPLE) 65 | SET(POSTGRES_LIBRARY ${PG_TMP}/libpq.dylib CACHE STRING INTERNAL) 66 | ELSE (APPLE) 67 | SET(POSTGRES_LIBRARY ${PG_TMP}/libpq.so CACHE STRING INTERNAL) 68 | ENDIF (APPLE) 69 | ENDIF(POSTGRES_CONFIG) 70 | 71 | ENDIF(UNIX) 72 | ENDIF(WIN32) 73 | 74 | IF (POSTGRES_INCLUDE_DIR AND POSTGRES_LIBRARY) 75 | SET(POSTGRES_FOUND TRUE) 76 | IF(EXISTS "${POSTGRES_INCLUDE_DIR}/pg_config.h") 77 | SET(HAVE_PGCONFIG TRUE) 78 | ELSE(EXISTS "${POSTGRES_INCLUDE_DIR}/pg_config.h") 79 | SET(HAVE_PGCONFIG FALSE) 80 | ENDIF(EXISTS "${POSTGRES_INCLUDE_DIR}/pg_config.h") 81 | ENDIF (POSTGRES_INCLUDE_DIR AND POSTGRES_LIBRARY) 82 | 83 | 84 | IF (POSTGRES_FOUND) 85 | 86 | # Check if we have 64 bit lo functions 87 | SET(CMAKE_EXTRA_INCLUDE_FILES "${POSTGRES_INCLUDE_DIR}/libpq-fe.h") 88 | CHECK_LIBRARY_EXISTS( ${POSTGRES_LIBRARY} lo_lseek64 "" HAVE_LO_LSEEK64 ) 89 | CHECK_LIBRARY_EXISTS( ${POSTGRES_LIBRARY} lo_truncate64 "" HAVE_LO_TRUNCATE64 ) 90 | 91 | 92 | IF ( HAVE_LO_LSEEK64 ) 93 | SET( POSTGRES_CFLAGS " -DHAVE_LO_LSEEK64" ) 94 | ENDIF ( HAVE_LO_LSEEK64 ) 95 | 96 | IF ( HAVE_LO_TRUNCATE64 ) 97 | SET( POSTGRES_CFLAGS "${POSTGRES_CFLAGS} -DHAVE_LO_TRUNCATE64" ) 98 | ENDIF ( HAVE_LO_TRUNCATE64 ) 99 | 100 | IF (NOT POSTGRES_FIND_QUIETLY) 101 | MESSAGE(STATUS "Found PostgreSQL: ${POSTGRES_LIBRARY}") 102 | ENDIF (NOT POSTGRES_FIND_QUIETLY) 103 | 104 | ELSE (POSTGRES_FOUND) 105 | 106 | #SET (POSTGRES_INCLUDE_DIR "") 107 | #SET (POSTGRES_LIBRARY "") 108 | 109 | IF (POSTGRES_FIND_REQUIRED) 110 | MESSAGE(FATAL_ERROR "Could not find PostgreSQL") 111 | ELSE (POSTGRES_FIND_REQUIRED) 112 | MESSAGE(STATUS "Could not find PostgreSQL") 113 | ENDIF (POSTGRES_FIND_REQUIRED) 114 | 115 | ENDIF (POSTGRES_FOUND) 116 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos 2 | MAINTAINER Tamas Foldi 3 | LABEL Description="Tableau File System (fuse-tableaufs) bundled with samba" Vendor="Starschema" Version="1.0" 4 | RUN yum -y update && yum -y install cmake make gcc postgresql-devel fuse-devel git vim-enhanced samba && mkdir -p /mnt/tableau 5 | RUN cd /usr/src && git clone https://github.com/tfoldi/fuse-tableaufs.git && \ 6 | cd /usr/src/fuse-tableaufs && cmake . && make && make install && \ 7 | cp docker/smb.conf /etc/samba && \ 8 | install -m 755 docker/tableau-samba.sh /usr/bin 9 | EXPOSE 138/udp 139 445 445/udp 10 | -------------------------------------------------------------------------------- /docker/smb.conf: -------------------------------------------------------------------------------- 1 | [global] 2 | workgroup = TABLEAUFS 3 | server string = Samba Server Version %v 4 | log file = /var/log/samba/log.%m 5 | max log size = 50 6 | security = user 7 | passdb backend = tdbsam 8 | map to guest = bad user 9 | guest account = nobody 10 | load printers = no 11 | [tableaufs] 12 | comment = Tableau FS mount 13 | path = /mnt/tableau 14 | public = yes 15 | guest ok = yes 16 | writable = yes 17 | printable = no 18 | write list = +root 19 | 20 | -------------------------------------------------------------------------------- /docker/tableau-samba.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # just like in the 80s, no systemsd, no security 4 | # if you have systemsd don't use this file at all 5 | tableaufs -o $1 /mnt/tableau && \ 6 | smbd --foreground 7 | -------------------------------------------------------------------------------- /docs/install-fuse-tableaufs-on-osx.markdown: -------------------------------------------------------------------------------- 1 | # Installing FUSE-TableauFS on OSX 2 | 3 | This guide tries to help you with installing FUSE-TableaFS on your OSX 4 | machine. 5 | 6 | For brewitys sake in the following guide, we'll use the acronym TFS instead of writing 7 | FUSE-TableauFS each and every time. 8 | 9 | ## Pre-requisites 10 | 11 | You'll need to install a bunch of libraries and command-line utilities 12 | to compile TFS: 13 | 14 | - XCode Developer Tools (with Command Line Support) 15 | - cmake (=> 2.6) 16 | - libpq (>= 9.0) 17 | - fuse (=> 2.9) 18 | 19 | We recommend installing these pre-requisites using 20 | [HomeBrew](http://brew.sh/). 21 | 22 | 23 | #### XCode Developer Tools 24 | 25 | XCode Developer Tools gives us the much needed compilers, and tooling 26 | necessary to compile C source code and is necessary for using HomeBrew. 27 | 28 | You can download the latest XCode from the App Store or from 29 | [Apple Developer Download Page](https://developer.apple.com/xcode/downloads/). 30 | 31 | A detailed [guide to install the XCode Command Line development tools for Yosemite] 32 | (https://railsapps.github.io/xcode-command-line-tools.html). Please note 33 | that this guide uses GCC as the example, but the gcc executable is just 34 | a wrapper for CLang handling the GCC command line switches properly. 35 | 36 | After installation check if you're successful: 37 | 38 | ``` 39 | $ clang --version 40 | Apple LLVM version 6.0 (clang-600.0.51) (based on LLVM 3.5svn) 41 | Target: x86_64-apple-darwin13.4.0 42 | Thread model: posix 43 | ``` 44 | 45 | 46 | #### CMake 47 | 48 | Installing CMake is fairly straightforward 49 | 50 | ``` 51 | brew install cmake 52 | ``` 53 | 54 | After installation check if you're successful: 55 | 56 | ``` 57 | $ cmake --version 58 | cmake version 3.0.2 59 | 60 | CMake suite maintained and supported by Kitware (kitware.com/cmake). 61 | ``` 62 | 63 | #### libpq 64 | 65 | If you dont already have postgresql installed, the easiest way to 66 | install the headers necessary is to install postgres using brew: 67 | 68 | ``` 69 | $ brew install postgresql 70 | ``` 71 | 72 | #### FUSE 73 | 74 | [FUSE for OSX](http://osxfuse.github.io) is a separate project from 75 | FUSE. You can download the latest release from the 76 | [osxfuse sourceforge files](http://sourceforge.net/projects/osxfuse/files/) 77 | or the [osxfuse github releases page](https://github.com/osxfuse/osxfuse/releases) 78 | or install it using HomeBrew: 79 | 80 | ``` 81 | $ brew install osxfuse 82 | ``` 83 | 84 | We recommend installing a recent version (we have tested using 3.0.3 85 | released on 15. May 2015). 86 | 87 | 88 | ## Compiling the source code 89 | 90 | As TFS is in early stages of its lifecycle consider it a moving target, so we recommend 91 | cloning the source repository instead of downloading a source archive. 92 | 93 | `$ git clone https://github.com/tfoldi/fuse-tableaufs.git` 94 | 95 | Then create a separate directory for building it (out-of-source builds 96 | are the recommended way of working with CMake): 97 | 98 | ``` 99 | $ mkdir fuse-tableaufs-build 100 | $ cd fuse-tableaufs-build 101 | ``` 102 | 103 | Then use cmake to generate the makefiles (replace `../fuse-tableaufs` with the relative 104 | path of the source folder from the build folder): 105 | 106 | ``` 107 | $ cmake . ../fuse-tableaufs 108 | $ make clean tableaufs 109 | ``` 110 | 111 | After a short compilation, the tableaufs executable is available in the 112 | src folder. 113 | 114 | 115 | ## Installing 116 | 117 | 118 | `$ sudo make install` 119 | 120 | this will install the `tableaufs` command into `/usr/local/bin` and 121 | symlink it as `/sbin/mount_tableaufs` for using with automount. 122 | 123 | 124 | 125 | ## Automounting 126 | 127 | Work in progress... 128 | 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2012-2015, Tamas Foldi, Starschema 2 | # 3 | # Redistribution and use in source and binary forms, with or without 4 | # modification, are permitted provided that the following conditions 5 | # are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright 8 | # notice, this list of conditions and the following disclaimer. 9 | # 2. Redistributions in binary form must reproduce the above copyright 10 | # notice, this list of conditions and the following disclaimer in the 11 | # documentation and/or other materials provided with the distribution. 12 | # 13 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 14 | # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 16 | # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 17 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 18 | # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 20 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | 25 | # FUSE_CFLAGS_OTHER may contain more then one option so we replace 26 | # the bad joining semicolons 27 | string(REPLACE ";" " " FUSE_CFLAGS_OTHER_SPLIT "${FUSE_CFLAGS_OTHER}") 28 | message(">> Setting FUSE_CFLAGS_OTHER to ${FUSE_CFLAGS_OTHER_SPLIT}") 29 | set( TFS_COMPILE_FLAGS "${FUSE_CFLAGS_OTHER_SPLIT} ${POSTGRES_CFLAGS}") 30 | 31 | # Add the executable 32 | add_executable( tableaufs 33 | tableaufs.c 34 | workgroup.c 35 | ) 36 | 37 | # Set the compile flags on a pre-target basis 38 | set_target_properties(tableaufs PROPERTIES COMPILE_FLAGS ${TFS_COMPILE_FLAGS}) 39 | 40 | # Link it with all the good stuff 41 | target_link_libraries( tableaufs 42 | ${POSTGRES_LIBRARY} 43 | ${FUSE_LDFLAGS} 44 | ) 45 | 46 | install( 47 | TARGETS tableaufs 48 | RUNTIME 49 | DESTINATION bin ) 50 | 51 | 52 | 53 | 54 | 55 | # Symlink the tableaufs executable as mount_tableaufs for osx 56 | if(APPLE) 57 | # The installed executable location 58 | set(TFS_EXEC_INSTALL_PATH "${CMAKE_INSTALL_PREFIX}/bin/tableaufs") 59 | set(TFS_MOUNT_EXEC_INSTALL_PATH "/sbin/mount_tableaufs") 60 | install(CODE " 61 | EXECUTE_PROCESS( 62 | COMMAND ln -sf ${TFS_EXEC_INSTALL_PATH} ${TFS_MOUNT_EXEC_INSTALL_PATH} 63 | ) 64 | ") 65 | endif() 66 | -------------------------------------------------------------------------------- /src/tableaufs.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Tamas Foldi, Starschema 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 13 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 14 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 15 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 16 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 17 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 18 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 19 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 20 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 21 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 | */ 23 | 24 | 25 | // fuse.h recommends setting the API version to 26 for new applications 26 | #define FUSE_USE_VERSION 26 27 | 28 | #include "tableaufs.h" 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include "workgroup.h" 37 | 38 | 39 | #define TFS_WG_PARSE_PATH( path, node ) \ 40 | { \ 41 | int __ret = TFS_WG_parse_path(path, node);\ 42 | if (__ret < 0){ \ 43 | return __ret; \ 44 | }; \ 45 | } while (0) 46 | 47 | static int tableau_getattr(const char *path, struct stat *stbuf) 48 | { 49 | int res = 0; 50 | tfs_wg_node_t node; 51 | 52 | TFS_WG_PARSE_PATH(path, &node); 53 | 54 | memcpy(stbuf, &(node.st), sizeof(struct stat)); 55 | 56 | return res; 57 | } 58 | 59 | static int tableau_readdir(const char *path, void *buf, fuse_fill_dir_t filler, 60 | off_t offset, struct fuse_file_info *fi) 61 | { 62 | tfs_wg_node_t node; 63 | 64 | TFS_WG_PARSE_PATH(path, &node); 65 | 66 | if ( node.level == TFS_WG_FILE ) 67 | return -ENOTDIR; 68 | 69 | filler(buf, ".", NULL, 0); 70 | filler(buf, "..", NULL, 0); 71 | 72 | TFS_WG_readdir(&node, buf, filler); 73 | 74 | return 0; 75 | } 76 | 77 | static int tableau_open(const char *path, struct fuse_file_info *fi) 78 | { 79 | tfs_wg_node_t node; 80 | int ret; 81 | 82 | TFS_WG_PARSE_PATH(path, &node); 83 | 84 | ret = TFS_WG_open(&node, fi->flags, &(fi->fh) ); 85 | fi->direct_io = 1; // during read we can return smaller buffer than 86 | // requested 87 | 88 | return ret; 89 | } 90 | 91 | static int tableau_read(const char *path, char *buf, size_t size, off_t offset, 92 | struct fuse_file_info *fi) 93 | { 94 | return TFS_WG_IO_operation(TFS_WG_READ, fi->fh, NULL, buf, size, offset); 95 | } 96 | 97 | static int tableau_write(const char *path, const char *buf, size_t size, off_t offset, 98 | struct fuse_file_info *fi) 99 | { 100 | return TFS_WG_IO_operation(TFS_WG_WRITE, fi->fh, buf, NULL, size, offset); 101 | } 102 | 103 | static int tableau_truncate(const char *path, off_t offset) 104 | { 105 | tfs_wg_node_t node; 106 | int ret; 107 | 108 | TFS_WG_PARSE_PATH(path, &node); 109 | if (node.level != TFS_WG_FILE ) 110 | ret = -EISDIR; 111 | else 112 | ret = TFS_WG_IO_operation(TFS_WG_TRUNCATE, node.loid, NULL, NULL, 0, offset); 113 | 114 | return ret; 115 | } 116 | 117 | // A descriptor for all the possible FUSE operations on a tableau endpoint 118 | static struct fuse_operations tableau_oper = { 119 | .getattr = tableau_getattr, 120 | .readdir = tableau_readdir, 121 | .open = tableau_open, 122 | .read = tableau_read, 123 | .write = tableau_write, 124 | .truncate = tableau_truncate, 125 | }; 126 | 127 | 128 | // A shortcut macro for easy-peasy parameter description 129 | #define TABLEAUFS_OPT(t, p) { t, offsetof(struct tableau_cmdargs, p), 1 } 130 | 131 | 132 | static struct tableau_cmdargs tableau_cmdargs; 133 | static struct fuse_opt tableaufs_opts[] = 134 | { 135 | TABLEAUFS_OPT("pghost=%s", pghost), 136 | TABLEAUFS_OPT("pgport=%s", pgport), 137 | TABLEAUFS_OPT("pguser=%s", pguser), 138 | TABLEAUFS_OPT("pgpass=%s", pgpass), 139 | 140 | // No more options for you Sir 141 | FUSE_OPT_END 142 | }; 143 | 144 | 145 | // Just some verbose information. 146 | static void print_verbose_information(int argc, char *argv[]) 147 | { 148 | printf("TableauFS v%s using FUSE API Version %d\n", TABLEAUFS_VERSION, fuse_version()); 149 | } 150 | 151 | 152 | int main(int argc, char *argv[]) 153 | { 154 | // print some information 155 | print_verbose_information(argc, argv); 156 | 157 | // Parse the command line 158 | struct fuse_args args = FUSE_ARGS_INIT(argc, argv); 159 | if (fuse_opt_parse(&args, &tableau_cmdargs, tableaufs_opts, NULL) == -1) 160 | return -1; 161 | 162 | // Validate the options 163 | if (tableau_cmdargs.pguser == NULL || 164 | tableau_cmdargs.pghost == NULL || 165 | tableau_cmdargs.pgport == NULL || 166 | tableau_cmdargs.pgpass == NULL) { 167 | fprintf(stderr, "Error: You should specify all of the following mount options:\n"); 168 | fprintf(stderr, "\tpghost pgport pguser pgpass\n"); 169 | return -1; 170 | } 171 | 172 | // Connect to PG 173 | printf("Connecting to %s@%s:%s\n", tableau_cmdargs.pguser, 174 | tableau_cmdargs.pghost, tableau_cmdargs.pgport ); 175 | 176 | TFS_WG_connect_db( tableau_cmdargs.pghost, tableau_cmdargs.pgport, 177 | tableau_cmdargs.pguser, tableau_cmdargs.pgpass); 178 | 179 | // Do the FUSE dance 180 | return fuse_main(args.argc, args.argv, &tableau_oper, NULL); 181 | } 182 | -------------------------------------------------------------------------------- /src/tableaufs.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Tamas Foldi, Starschema 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 13 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 14 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 15 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 16 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 17 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 18 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 19 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 20 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 21 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 | */ 23 | #pragma once 24 | 25 | #include "tableaufs_version.h" 26 | 27 | /** A structure with the Postgres connection parameters */ 28 | struct tableau_cmdargs { 29 | const char *pghost; 30 | const char *pgport; 31 | const char *pguser; 32 | const char *pgpass; 33 | }; 34 | 35 | 36 | /** Get the connection parameters for the current session */ 37 | struct tableau_cmdargs tableaufs_get_cmdargs(); 38 | 39 | -------------------------------------------------------------------------------- /src/tableaufs_version.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Tamas Foldi, Starschema 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 13 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 14 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 15 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 16 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 17 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 18 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 19 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 20 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 21 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 | */ 23 | #pragma once 24 | 25 | 26 | /* Versioning 27 | * ---------- 28 | * 29 | * see http://semver.org/ for more details. 30 | */ 31 | #define TABLEAUFS_VERSION_MAJOR "0" 32 | #define TABLEAUFS_VERSION_MINOR "7" 33 | #define TABLEAUFS_VERSION_PATCH "1" 34 | 35 | #define TABLEAUFS_VERSION TABLEAUFS_VERSION_MAJOR "." TABLEAUFS_VERSION_MINOR "." TABLEAUFS_VERSION_PATCH 36 | -------------------------------------------------------------------------------- /src/workgroup.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | Copyright (c) 2015, Tamas Foldi, Starschema 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions 7 | are met: 8 | 1. Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 14 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 16 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 17 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 18 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 20 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include "workgroup.h" 33 | #include "tableaufs.h" 34 | #include "libpq-fe.h" 35 | #include "libpq/libpq-fs.h" 36 | 37 | #define BUFSIZE 1024 38 | #define TFS_WG_BLOCKSIZE 8196 39 | #define _NAME_MAX "255" 40 | 41 | #define TFS_WG_MTIME \ 42 | ", extract(epoch from coalesce(c.updated_at,'2000-01-01')) ctime " 43 | 44 | #define TFS_WG_LIST_SITES \ 45 | "select c.name" TFS_WG_MTIME "from sites c where 1 = 1 " 46 | 47 | #define TFS_WG_LIST_PROJECTS \ 48 | "select c.name" TFS_WG_MTIME "from projects c inner join" \ 49 | " sites p on (p.id = c.site_id) where p.name = $1" 50 | 51 | #define TFS_WG_LIST_FILE( entity, ext ) \ 52 | "select c.name || '." #ext "' || case when substring(data from 1 for 2) = " \ 53 | "'PK' then 'x' else '' end filename " TFS_WG_MTIME ", content," \ 54 | "(select sum(length(data)) from pg_largeobject where pg_largeobject.loid = " \ 55 | "repository_data.content) size from " #entity " c inner join repository_data " \ 56 | " on (repository_data.tracking_id = coalesce(data_id,reduced_data_id))" \ 57 | "inner join projects on (c.project_id = projects.id) inner join sites on " \ 58 | "(sites.id = projects.site_id) inner join pg_largeobject on " \ 59 | "(repository_data.content = pg_largeobject.loid) where pg_largeobject.pageno = 0" \ 60 | " and sites.name = $1 and projects.name = $2 " 61 | 62 | #define TFS_WG_LIST_WORKBOOKS TFS_WG_LIST_FILE( workbooks, twb ) 63 | 64 | #define TFS_WG_LIST_DATASOURCES TFS_WG_LIST_FILE( datasources, tds ) 65 | 66 | #define TFS_WG_NAMES_WITHOUT_SLASH(ext) \ 67 | "replace(c.name,'/','_')||'." #ext "x', replace(c.name,'/','_')||'." #ext "' " 68 | 69 | 70 | /** The global connection object */ 71 | static PGconn* global_conn; 72 | 73 | /** Mutex for transaction LO access */ 74 | static pthread_mutex_t tfs_wg_transaction_block_mutex = PTHREAD_MUTEX_INITIALIZER; 75 | 76 | 77 | 78 | /** 79 | * Connection data for postgres. 80 | * 81 | * This gets filled on the call to TFS_WG_connect_db. 82 | */ 83 | static struct tableau_cmdargs pg_connection_data; 84 | 85 | /** Helper function to wrap connecting to a database using the connection data */ 86 | static PGconn* connect_to_pg( struct tableau_cmdargs conn_data) 87 | { 88 | PGconn* new_conn = PQsetdbLogin( 89 | conn_data.pghost, 90 | conn_data.pgport, 91 | NULL, NULL, "workgroup", 92 | conn_data.pguser, 93 | conn_data.pgpass ); 94 | 95 | /* check to see that the backend connection was successfully made */ 96 | if (PQstatus(new_conn) == CONNECTION_BAD) 97 | { 98 | fprintf(stderr, "Connection to database '%s' failed.\n", conn_data.pghost); 99 | fprintf(stderr, "%s", PQerrorMessage(new_conn)); 100 | PQfinish(new_conn); 101 | return NULL; 102 | } 103 | 104 | return new_conn; 105 | } 106 | 107 | /** 108 | * Helper function to get the current connection to the database 109 | * 110 | * TODO: should this reconnect be wrapped in a mutex? 111 | */ 112 | static PGconn* get_pg_connection() 113 | { 114 | // List all cases, figure out which need reconnection 115 | switch( PQstatus(global_conn) ) 116 | { 117 | case CONNECTION_BAD: 118 | fprintf(stderr, "CONNECTION_BAD encountered: '%s'. Trying to reconnect.\n", PQerrorMessage(global_conn)); 119 | // overwrite the existing global (EEEEEK) connection 120 | global_conn = connect_to_pg( pg_connection_data ); 121 | return global_conn; 122 | 123 | 124 | case CONNECTION_NEEDED: 125 | // Postgres 9 Docs is silent about this enum value. What does this state mean? 126 | // TODO: is it safe to return conn here? 127 | return global_conn; 128 | 129 | 130 | case CONNECTION_SETENV: 131 | case CONNECTION_AUTH_OK: 132 | case CONNECTION_SSL_STARTUP: 133 | case CONNECTION_MADE: 134 | case CONNECTION_AWAITING_RESPONSE: 135 | case CONNECTION_STARTED: 136 | case CONNECTION_OK: 137 | default: 138 | return global_conn; 139 | } 140 | } 141 | 142 | 143 | int TFS_WG_IO_operation(tfs_wg_operations_t op, const uint64_t loid, 144 | const char * src, char * dst, const size_t size, const off_t offset) 145 | { 146 | PGresult *res; 147 | int fd, ret = 0; 148 | int mode; 149 | 150 | // get the connection via a reconnect-capable backer 151 | PGconn* conn = get_pg_connection(); 152 | 153 | 154 | if ( op == TFS_WG_READ ) 155 | mode = INV_READ; 156 | else 157 | mode = INV_WRITE; 158 | 159 | // LO operations only supported within transactions 160 | // On our FS one read is one transaction 161 | // While libpq is thread safe, still, we cannot have parallel 162 | // transactions from multiple threads on the same connection 163 | pthread_mutex_lock(&tfs_wg_transaction_block_mutex); 164 | 165 | res = PQexec(conn, "BEGIN"); 166 | PQclear(res); 167 | 168 | fd = lo_open(conn, (Oid)loid, mode); 169 | 170 | fprintf(stderr, "TFS_WG_open: reading from fd %d (l:%lu:o:%tu)\n", 171 | fd, size, offset); 172 | 173 | #ifdef HAVE_LO_LSEEK64 174 | if ( lo_lseek64(conn, fd, offset, SEEK_SET) < 0 ) { 175 | #else 176 | if ( lo_lseek(conn, fd, (int)offset, SEEK_SET) < 0 ) { 177 | #endif // HAVE_LO_LSEEK64 178 | ret = -EINVAL; 179 | } else { 180 | switch (op) { 181 | case TFS_WG_READ: 182 | ret = lo_read(conn, fd, dst, size); 183 | break; 184 | case TFS_WG_WRITE: 185 | ret = lo_write(conn, fd, src, size); 186 | break; 187 | case TFS_WG_TRUNCATE: 188 | #ifdef HAVE_LO_TRUNCATE64 189 | ret = lo_truncate64(conn, fd, offset); 190 | #else 191 | ret = lo_truncate(conn, fd,(size_t) offset); 192 | #endif 193 | break; 194 | default: 195 | ret = -EINVAL; 196 | break; 197 | } 198 | } 199 | 200 | res = PQexec(conn, "END"); 201 | pthread_mutex_unlock(&tfs_wg_transaction_block_mutex); 202 | 203 | PQclear(res); 204 | 205 | return ret; 206 | } 207 | 208 | 209 | int TFS_WG_open(const tfs_wg_node_t * node, int mode, uint64_t * fh) 210 | { 211 | 212 | if (node->level != TFS_WG_FILE ) 213 | return -EISDIR; 214 | else 215 | *fh = node->loid; 216 | 217 | return 0; 218 | } 219 | 220 | int TFS_WG_readdir(const tfs_wg_node_t * node, void * buffer, 221 | tfs_wg_add_dir_t filler) 222 | { 223 | PGresult *res; 224 | int i, ret; 225 | size_t j, len; 226 | char * name; 227 | const char *paramValues[2] = { node->site, node->project }; 228 | 229 | // get the connection via a reconnect-capable backer 230 | PGconn* conn = get_pg_connection(); 231 | 232 | 233 | switch(node->level) 234 | { 235 | case TFS_WG_ROOT: 236 | res = PQexec(conn, TFS_WG_LIST_SITES); 237 | break; 238 | 239 | case TFS_WG_SITE: 240 | res = PQexecParams(conn, TFS_WG_LIST_PROJECTS, 1, NULL, paramValues, NULL, NULL, 0); 241 | break; 242 | 243 | 244 | case TFS_WG_PROJECT: 245 | res = PQexecParams(conn, 246 | TFS_WG_LIST_WORKBOOKS " union all " TFS_WG_LIST_DATASOURCES, 247 | 2, NULL, paramValues, NULL, NULL, 0); 248 | break; 249 | 250 | default: 251 | // make sure res is initialized, so the code is clean and we dont get a warning 252 | res = NULL; 253 | fprintf(stderr, "Unknown node level found: %u\n", node->level); 254 | break; 255 | 256 | } 257 | 258 | if (PQresultStatus(res) != PGRES_TUPLES_OK) 259 | { 260 | fprintf(stderr, "SELECT entries failed: %s", PQerrorMessage(conn)); 261 | // TODO: error handling 262 | ret = -EIO; 263 | } else if (PQntuples(res) == 0 ) { 264 | ret = -ENOENT; 265 | } else { 266 | // return a zero as the universal OK sign 267 | ret = 0; 268 | for (i = 0; i < PQntuples(res); i++) { 269 | name = PQgetvalue(res, i, TFS_WG_QUERY_NAME); 270 | len = strlen( name ); 271 | 272 | for ( j = 0 ; j < len ; j++ ) 273 | if ( name[j] == '/' ) 274 | name[j] = '_'; 275 | 276 | filler(buffer, name, NULL, 0); 277 | } 278 | } 279 | 280 | PQclear(res); 281 | 282 | return ret; 283 | } 284 | 285 | int TFS_WG_stat_file(tfs_wg_node_t * node) 286 | { 287 | const char *paramValues[3] = { node->site, node->project, node->file }; 288 | PGresult * res; 289 | int ret; 290 | 291 | // get the connection via a reconnect-capable backer 292 | PGconn* conn = get_pg_connection(); 293 | 294 | node->st.st_blksize = TFS_WG_BLOCKSIZE; 295 | 296 | // basic stat stuff: file type, nlinks, size of dirs 297 | if ( node->level < TFS_WG_FILE) { 298 | node->st.st_mode = S_IFDIR | 0555; // read only 299 | node->st.st_nlink = 2; 300 | node->st.st_size = TFS_WG_BLOCKSIZE; 301 | node->st.st_blocks = 1; 302 | } else if (node->level == TFS_WG_FILE) { 303 | node->st.st_mode = S_IFREG | 0444; // read only 304 | node->st.st_nlink = 1; 305 | } 306 | 307 | if (node->level == TFS_WG_ROOT) { 308 | time(&(node->st.st_mtime)); 309 | return 0; 310 | } else if (node->level == TFS_WG_SITE) { 311 | 312 | res = PQexecParams(conn, TFS_WG_LIST_SITES " and c.name = $1", 1, NULL, 313 | paramValues, NULL, NULL, 0); 314 | 315 | } else if (node->level == TFS_WG_PROJECT) { 316 | 317 | res = PQexecParams(conn, TFS_WG_LIST_PROJECTS " and c.name = $2", 318 | 2, NULL, paramValues, NULL, NULL, 0); 319 | 320 | } else if (node->level == TFS_WG_FILE) { 321 | 322 | res = PQexecParams(conn, 323 | TFS_WG_LIST_WORKBOOKS " and $3 IN (" TFS_WG_NAMES_WITHOUT_SLASH(twb) ") " 324 | "union all " 325 | TFS_WG_LIST_DATASOURCES " and $3 IN (" TFS_WG_NAMES_WITHOUT_SLASH(tds) ") ", 326 | 3, NULL, paramValues, NULL, NULL, 0); 327 | } else { 328 | // res defaults to NULL 329 | res = NULL; 330 | } 331 | 332 | if (PQresultStatus(res) != PGRES_TUPLES_OK) 333 | { 334 | fprintf(stderr, "SELECT entries failed: %s/%s", PQresultErrorMessage(res), PQerrorMessage(conn)); 335 | // TODO: error handling 336 | ret = -EINVAL; 337 | } else if (PQntuples(res) == 0 ) { 338 | ret = -ENOENT; 339 | } else { 340 | node->st.st_mtime = atoll( PQgetvalue(res, 0, TFS_WG_QUERY_MTIME) ); 341 | 342 | if ( node->level == TFS_WG_FILE ) { 343 | node->loid = (uint64_t)atoll( PQgetvalue(res, 0, TFS_WG_QUERY_CONTENT) ); 344 | node->st.st_size = atoll( PQgetvalue(res, 0, TFS_WG_QUERY_SIZE) ); 345 | if ( node->st.st_size > 0 ) 346 | node->st.st_blocks = (int) node->st.st_size / TFS_WG_BLOCKSIZE + 1; 347 | } 348 | 349 | ret = 0; 350 | } 351 | 352 | PQclear(res); 353 | return ret; 354 | } 355 | 356 | int TFS_WG_parse_path(const char * path, tfs_wg_node_t * node) 357 | { 358 | int ret; 359 | 360 | if ( strlen(path) > PATH_MAX ) 361 | return -EINVAL; 362 | else if ( strlen(path) == 1 && path[0] == '/' ) { 363 | node->level = TFS_WG_ROOT; 364 | return TFS_WG_stat_file(node); 365 | } 366 | 367 | memset(node, 0, sizeof(tfs_wg_node_t)); 368 | ret = sscanf(path, "/%" _NAME_MAX "[^/]/%" _NAME_MAX "[^/]/%255[^/]s", 369 | node->site, node->project, node->file ); 370 | 371 | /* sscanf returned with error */ 372 | if (ret == EOF ) { 373 | return errno; // TODO: this so thread unsafe 374 | } else if ( strchr(node->file, '/' ) != NULL ) { 375 | /* file name has / char in it */ 376 | return -EINVAL; 377 | } else { 378 | 379 | fprintf(stderr, "TFS_WG_parse_path: site: %s proj: %s file: %s\n", 380 | node->site, node->project, node->file); 381 | 382 | // cast so the signed conversion warning goes away 383 | node->level = (tfs_wg_level_t)ret; 384 | 385 | // get stat from node 386 | ret = TFS_WG_stat_file(node); 387 | 388 | return ret; 389 | } 390 | } 391 | 392 | int TFS_WG_connect_db(const char * pghost, const char * pgport, 393 | const char * login, const char * pwd) 394 | { 395 | // save the connection data to the global (eeeeek) state. 396 | struct tableau_cmdargs conn_data = {pghost, pgport, login, pwd}; 397 | /*struct tableau_cmdargs conn_data = {(char*)pghost, (char*)pgport, (char*)login, (char*)pwd};*/ 398 | pg_connection_data = conn_data; 399 | 400 | // Set up the connection 401 | global_conn = connect_to_pg( conn_data ); 402 | 403 | // return based on whether we have the connection 404 | if (global_conn == NULL) return -1; 405 | return 0; 406 | } 407 | 408 | -------------------------------------------------------------------------------- /src/workgroup.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Tamas Foldi, Starschema 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 13 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 14 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 15 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 16 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 17 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 18 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 19 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 20 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 21 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 | */ 23 | 24 | #ifndef tableaufs_workgroup_h 25 | #define tableaufs_workgroup_h 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | typedef enum 32 | { 33 | TFS_WG_READ = 0, 34 | TFS_WG_WRITE = 1, 35 | TFS_WG_TRUNCATE = 2 36 | } tfs_wg_operations_t; 37 | 38 | typedef enum 39 | { 40 | TFS_WG_ROOT = 0, 41 | TFS_WG_SITE = 1, 42 | TFS_WG_PROJECT = 2, 43 | TFS_WG_FILE = 3 44 | } tfs_wg_level_t; 45 | 46 | typedef struct tfs_wg_node_t { 47 | tfs_wg_level_t level; // level inside the mount point 48 | char site[NAME_MAX+1]; // site name 49 | char project[NAME_MAX+1]; // project name 50 | char file[NAME_MAX+1]; // Workbook/Datasource name 51 | uint64_t loid; // repo id, if file 52 | struct stat st; // file stat info 53 | } tfs_wg_node_t; 54 | 55 | typedef enum { 56 | TFS_WG_QUERY_NAME = 0, 57 | TFS_WG_QUERY_MTIME = 1, 58 | TFS_WG_QUERY_CONTENT = 2, 59 | TFS_WG_QUERY_SIZE = 3 60 | } tfs_wg_list_query_cols_t; 61 | 62 | typedef int(* tfs_wg_add_dir_t )(void *buf, const char *name, 63 | const struct stat *stbuf, off_t off); 64 | 65 | extern int TFS_WG_IO_operation(tfs_wg_operations_t op, const uint64_t loid, 66 | const char * src, char * dst, const size_t size, const off_t offset); 67 | 68 | extern int TFS_WG_modife(const uint64_t fd, char * buf, const size_t size, 69 | const off_t offset, tfs_wg_operations_t op); 70 | 71 | extern int TFS_WG_open(const tfs_wg_node_t * node, int mode, uint64_t * fh); 72 | 73 | extern int TFS_WG_readdir(const tfs_wg_node_t * node, void * buffer, 74 | tfs_wg_add_dir_t filler); 75 | 76 | extern int TFS_WG_connect_db(const char * pghost, const char * pgport, 77 | const char * login, const char * pwd); 78 | 79 | extern int TFS_WG_parse_path(const char * path, tfs_wg_node_t * node); 80 | 81 | #endif /* tableaufs_workgroup_h */ 82 | --------------------------------------------------------------------------------