├── context.c ├── PACKAGE_ICON.PNG ├── PACKAGE_ICON_256.PNG ├── SynoBuildConf ├── depends ├── build └── install ├── context.h ├── scripts ├── postupgrade ├── preinst ├── preuninst ├── preupgrade ├── postuninst ├── postinst └── start-stop-status ├── worm.service ├── link.h ├── loop.h ├── wormrotate ├── filter.h ├── INFO.sh ├── Makefile.am ├── utils.h ├── config.h ├── attribute.h ├── directory.h ├── utils ├── makefile ├── setretention.c └── getretention.c ├── Dockerfile ├── shared.h ├── configure.ac ├── README.md ├── TestWithAutoCommit.conf ├── TestWithoutAutoCommit.conf ├── worm.conf ├── file.h ├── retention.h ├── LICENSE.md ├── logger.h ├── config.c ├── filter.c ├── software.spec ├── .gitattributes ├── start.pl ├── utils.c ├── link.c ├── loop.c ├── ParseAudit.pl ├── directory.c ├── attribute.c ├── main.c ├── logger.c ├── .gitignore ├── shared.c ├── file.c ├── retention.c ├── test.pl ├── test.bats └── Create WORM.sql /context.c: -------------------------------------------------------------------------------- 1 | #include "context.h" 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /PACKAGE_ICON.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfgs/WORM-FS/HEAD/PACKAGE_ICON.PNG -------------------------------------------------------------------------------- /PACKAGE_ICON_256.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfgs/WORM-FS/HEAD/PACKAGE_ICON_256.PNG -------------------------------------------------------------------------------- /SynoBuildConf/depends: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2000-2016 Synology Inc. All rights reserved. 2 | [BuildDependent] 3 | libini 4 | liblog 5 | 6 | [default] 7 | all="6.0" 8 | -------------------------------------------------------------------------------- /context.h: -------------------------------------------------------------------------------- 1 | #ifndef _Context_ 2 | #define _Context_ 3 | 4 | #define NAFILE "NA" 5 | #define _VERSION "0001" 6 | 7 | 8 | 9 | 10 | #endif 11 | 12 | -------------------------------------------------------------------------------- /scripts/postupgrade: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright (C) 2000-2016 Synology Inc. All rights reserved. 3 | 4 | ### This script will be executed ONLY at package upgraded. 5 | ### Actions after package upgraded. 6 | ### ex. restore user settings. 7 | 8 | exit 0 9 | -------------------------------------------------------------------------------- /scripts/preinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright (C) 2000-2016 Synology Inc. All rights reserved. 3 | 4 | ### This script will be execute when package installed and upgraded. 5 | ### Actions before package installed. 6 | ### ex. check environment. 7 | 8 | exit 0 9 | -------------------------------------------------------------------------------- /scripts/preuninst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright (C) 2000-2016 Synology Inc. All rights reserved. 3 | 4 | ### This script will be executed when package uninstalled and upgraded. 5 | ### Actions before package uninstalled 6 | ### ex. backup package data. 7 | 8 | exit 0 9 | -------------------------------------------------------------------------------- /scripts/preupgrade: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright (C) 2000-2016 Synology Inc. All rights reserved. 3 | 4 | ### This script will execute ONLY when package upgraded. 5 | ### Actions before package upgraded. 6 | ### ex. backup user settings for package upgrade. 7 | 8 | exit 0 9 | -------------------------------------------------------------------------------- /worm.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=WORM service 3 | #Requires=iscsi.service 4 | #After=iscsi.service 5 | 6 | [Service] 7 | Type=forking 8 | ExecStart=/usr/local/bin/worm -o allow_other,default_permissions 9 | ExecStop=fusermount -u /mnt/WORM 10 | 11 | [Install] 12 | WantedBy=multi-user.target 13 | -------------------------------------------------------------------------------- /link.h: -------------------------------------------------------------------------------- 1 | #ifndef _Link_ 2 | #define _Link_ 3 | 4 | #include 5 | 6 | int WORM_readlink(const char *path, char *link, size_t size); 7 | int WORM_unlink(const char *path); 8 | int WORM_symlink(const char *path, const char *link); 9 | int WORM_link(const char *path, const char *newpath); 10 | 11 | 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /loop.h: -------------------------------------------------------------------------------- 1 | #ifndef _Loop_ 2 | #define _Loop_ 3 | 4 | typedef struct 5 | { 6 | char name[1024]; 7 | const char* fileName; 8 | const char* mountPoint; 9 | } Loop; 10 | 11 | Loop* loop_create(const char* fileName,const char* mountPoint); 12 | int loop_mount(Loop* loop); 13 | int loop_umount(Loop* loop); 14 | void loop_free(Loop* loop); 15 | 16 | 17 | #endif 18 | 19 | -------------------------------------------------------------------------------- /scripts/postuninst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright (C) 2000-2016 Synology Inc. All rights reserved. 3 | 4 | ### This script will be executed when package uninstalled and upgraded. 5 | ### Actions after package uninstalled. 6 | ### ex. remove garbage files. 7 | 8 | rm -f /usr/local/bin/worm 9 | rm -f /etc/worm.conf 10 | rm -f /etc/logrotate.d/wormrotate 11 | 12 | 13 | exit 0 14 | -------------------------------------------------------------------------------- /wormrotate: -------------------------------------------------------------------------------- 1 | /var/log/worm.log { 2 | rotate 10 3 | missingok 4 | notifempty 5 | daily 6 | sharedscripts 7 | postrotate 8 | pkill -SIGUSR1 worm 9 | endscript 10 | } 11 | 12 | /var/log/worm_audit.log { 13 | rotate 10 14 | missingok 15 | notifempty 16 | daily 17 | sharedscripts 18 | postrotate 19 | pkill -SIGUSR2 worm 20 | endscript 21 | } 22 | 23 | -------------------------------------------------------------------------------- /filter.h: -------------------------------------------------------------------------------- 1 | #ifndef _Filter_ 2 | #define _Filter_ 3 | 4 | #include "regex.h" 5 | 6 | typedef struct 7 | { 8 | regex_t regex; 9 | unsigned short value; 10 | } Filter; 11 | 12 | typedef struct 13 | { 14 | Filter* items; 15 | int count; 16 | } Filters; 17 | 18 | 19 | Filters* filters_load(); 20 | unsigned short filters_getRetention(Filters* filters,const char* path,int* filterIndex); 21 | int filters_free(Filters* filters); 22 | 23 | #endif 24 | 25 | -------------------------------------------------------------------------------- /INFO.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source /pkgscripts/include/pkg_util.sh 4 | 5 | package="WORM" 6 | version="1.0" 7 | displayname="WORM" 8 | maintainer="Marc GUICHARD" 9 | arch="$(pkg_get_unified_platform)" 10 | description="WORM-FS is WORM (Write Once Read Many) compliant file system. Once a file is created, you cannot delete or update it, until the retention period has expired." 11 | maintainer_url="https://github.com/dfgs/WORM-FS" 12 | [ "$(caller)" != "0 NULL" ] && return 0 13 | pkg_dump_info 14 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | AUTOMAKE_OPTIONS = foreign 2 | wormconfdir=/etc 3 | wormconf_DATA = worm.conf 4 | wormrotatedir=/etc/logrotate.d 5 | wormrotate_DATA = wormrotate 6 | bin_PROGRAMS = worm 7 | worm_SOURCES = main.c attribute.c directory.c file.c link.c logger.c retention.c shared.c utils.c loop.c context.c filter.c config.c attribute.h directory.h file.h link.h logger.h retention.h shared.h utils.h loop.h context.h filter.h config.h worm.conf worm.service wormrotate 8 | install-service: 9 | cp worm.service /usr/lib/systemd/system/ 10 | -------------------------------------------------------------------------------- /utils.h: -------------------------------------------------------------------------------- 1 | #ifndef _Utils_ 2 | #define _Utils_ 3 | 4 | #include 5 | 6 | void convertPath(char *DestPath, const char *originalPath); 7 | void setRealOwnerID(const char* funcName,const char *path); 8 | int getReadOnlyMode(const char* funcName,const char *path,int mode); 9 | int fileExists(const char *path); 10 | int directoryExists(const char *path); 11 | int createDirectory(const char* funcName,const char *path); 12 | void convertTime(time_t Time,char *Buffer,int bufferSize); 13 | int isReadOnly(const char *path); 14 | #endif 15 | -------------------------------------------------------------------------------- /scripts/postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright (C) 2000-2016 Synology Inc. All rights reserved. 3 | 4 | ### This script will be executed when package installed and upgraded. 5 | ### Actions after package installed. 6 | ### ex. create database, create symbolic link... 7 | 8 | ln -sf $SYNOPKG_PKGDEST/usr/local/bin/worm /usr/local/bin/ 9 | ln -sf $SYNOPKG_PKGDEST/etc/worm.conf /etc 10 | ln -sf $SYNOPKG_PKGDEST/etc/logrotate.d/wormrotate /etc/logrotate.d 11 | sed -i "s~/mnt/WORM~$sharedPath~g" $SYNOPKG_PKGDEST/etc/worm.conf 12 | mkdir -p $sharedPath 13 | exit 0 14 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | #ifndef _Config_ 2 | #define _Config_ 3 | 4 | #include 5 | #include "filter.h" 6 | 7 | typedef struct 8 | { 9 | IniFile *iniFile; 10 | unsigned char ID; 11 | char *repositoryPath; 12 | int repositoryType; 13 | char *repositoryFile; 14 | char *mountPath; 15 | unsigned short defaultRetention; 16 | int auditMode; 17 | int lockDelay; 18 | int autoLock; 19 | Filters* filters; 20 | } Config; 21 | 22 | extern Config config; 23 | 24 | int config_init(); 25 | int config_loadFilters(); 26 | int config_free(); 27 | 28 | 29 | 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /attribute.h: -------------------------------------------------------------------------------- 1 | #ifndef _Attribute_ 2 | #define _Attribute_ 3 | 4 | #include 5 | 6 | 7 | int WORM_getattr(const char *path, struct stat *statbuf); 8 | int WORM_fgetattr(const char *path, struct stat *statbuf, struct fuse_file_info *fi); 9 | 10 | int WORM_getxattr(const char *path, const char *name, char *value, size_t size); 11 | int WORM_listxattr(const char *path, char *list, size_t size); 12 | 13 | int WORM_setxattr(const char *path, const char *name, const char *value, size_t size, int flags); 14 | int WORM_removexattr(const char *path, const char *name); 15 | #endif 16 | -------------------------------------------------------------------------------- /SynoBuildConf/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (C) 2000-2016 Synology Inc. All rights reserved. 3 | 4 | case ${MakeClean} in 5 | [Yy][Ee][Ss]) 6 | make clean 7 | ;; 8 | esac 9 | 10 | echo "Building WORM with flags ${MAKE_FLAGS}" 11 | aclocal 12 | automake --add-missing 13 | autoconf 14 | env CC="${CC}" CXX="${CXX}" LD="${LD}" AR=${AR} STRIP=${STRIP} RANLIB=${RANLIB} NM=${NM} \ 15 | CFLAGS="${CFLAGS} -Os -I/usr/local/include" CXXFLAGS="${CXXFLAGS}" LDFLAGS="${LDFLAGS} -L/usr/local/lib" \ 16 | ./configure ${ConfigOpt} --prefix=/usr/local 17 | 18 | make ${MAKE_FLAGS} 19 | 20 | 21 | -------------------------------------------------------------------------------- /directory.h: -------------------------------------------------------------------------------- 1 | #ifndef _Directory_ 2 | #define _Directory_ 3 | 4 | #include 5 | #include 6 | 7 | int WORM_mknod(const char *path, mode_t mode, dev_t dev); 8 | int WORM_readdir(const char *path, void *buf, fuse_fill_dir_t filler,off_t offset, struct fuse_file_info *fi); 9 | int WORM_mkdir(const char *path, mode_t mode); 10 | int WORM_rmdir(const char *path); 11 | int WORM_opendir(const char *path, struct fuse_file_info *fi); 12 | int WORM_releasedir(const char *path, struct fuse_file_info *fi); 13 | int WORM_fsyncdir(const char *path, int datasync, struct fuse_file_info *fi); 14 | 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /scripts/start-stop-status: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright (C) 2000-2016 Synology Inc. All rights reserved. 3 | 4 | case $1 in 5 | start) 6 | ### Start this package. 7 | /usr/local/bin/worm -o allow_other,default_permissions >> /var/log/worm.log 2>&1 8 | exit $? 9 | ;; 10 | stop) 11 | ### Stop this package. 12 | fusermount -u /volume1/WORM >> /var/log/worm.log 2>&1 13 | exit $? 14 | ;; 15 | status) 16 | ### Check package alive. 17 | ps cax | grep worm > /dev/null 18 | if [ $? -eq 0 ]; then 19 | exit 0 20 | else 21 | exit 3 22 | fi 23 | ;; 24 | killall) 25 | ;; 26 | log) 27 | echo /var/log/worm.log 28 | exit 0 29 | ;; 30 | esac 31 | 32 | -------------------------------------------------------------------------------- /utils/makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | LD=ld 3 | CFLAGS= 4 | LDFLAGS= 5 | 6 | 7 | all:getretention setretention 8 | 9 | 10 | getretention:getretention.o 11 | $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) 12 | 13 | setretention:setretention.o 14 | $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) 15 | 16 | clean : 17 | rm *.o getretention setretention 18 | 19 | install:$(EXEC) 20 | cp getretention setretention /usr/local/bin 21 | 22 | uninstall: 23 | rm /usr/local/bin/getretention /usr/local/bin/setretention 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos:latest 2 | MAINTAINER Marc GUICHARD "Marc.Guichard@IPC.com" 3 | 4 | ENV REFRESHED_AT 2016-30-11 5 | RUN yum -y -q upgrade 6 | RUN yum -y groupinstall 'Development Tools' 7 | RUN yum -y install fuse 8 | RUN yum -y install fuse-devel 9 | RUN yum -y install samba 10 | 11 | ENV REFRESHED_AT 2016-01-12 12 | #ADD worm-1.0.tar.gz /root 13 | #WORKDIR /root/worm-1.0 14 | #RUN ./configure 15 | #RUN make 16 | #RUN make install 17 | 18 | RUN mkdir /mnt/WORM 19 | COPY WORM /usr/local/bin 20 | COPY WORM.conf /etc 21 | 22 | VOLUME ["/opt/audit"] 23 | 24 | COPY start.pl /root 25 | EXPOSE 139 445 26 | CMD ["--user","worm:worm:1000","--share","Archive:/Archive/:worm"] 27 | ENTRYPOINT ["/root/start.pl"] 28 | -------------------------------------------------------------------------------- /shared.h: -------------------------------------------------------------------------------- 1 | #include "logger.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | //int WORM_mknod(const char *path, mode_t mode, dev_t dev); 14 | int WORM_rename(const char *path, const char *newpath); 15 | int WORM_chown(const char *path, uid_t uid, gid_t gid); 16 | int WORM_utime(const char *path, struct utimbuf *ubuf); 17 | int WORM_statfs(const char *path, struct statvfs *statv); 18 | int WORM_fsync(const char *path, int datasync, struct fuse_file_info *fi); 19 | int WORM_access(const char *path, int mask); 20 | int WORM_chmod(const char *path, mode_t mode); 21 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | AC_INIT([worm], [1.0], [dfgs83@gmail.com]) 2 | CFLAGS="$CFLAGS -D_FILE_OFFSET_BITS=64" 3 | 4 | AC_CHECK_LIB(fuse,fuse_main, ,[AC_MSG_ERROR([Couldn't find libfuse...] )]) 5 | AC_CHECK_LIB(rt,mq_open, ,[AC_MSG_ERROR([Couldn't find librt...] )]) 6 | AC_CHECK_LIB(ini,ini_open, ,[AC_MSG_ERROR([Couldn't find libini...] )]) 7 | AC_CHECK_LIB(log,log_open, ,[AC_MSG_ERROR([Couldn't find liblog...] )]) 8 | 9 | AC_CHECK_HEADER(fuse.h,, [AC_MSG_ERROR([Couldn't find fuse.h...] )]) 10 | AC_CHECK_HEADER(mqueue.h,, [AC_MSG_ERROR([Couldn't find mqueue.h...] )]) 11 | AC_CHECK_HEADER(ini.h,, [AC_MSG_ERROR([Couldn't find ini.h...] )]) 12 | AC_CHECK_HEADER(log.h,, [AC_MSG_ERROR([Couldn't find log.h...] )]) 13 | 14 | AM_INIT_AUTOMAKE 15 | AC_PROG_CC 16 | AC_CONFIG_FILES([Makefile]) 17 | AC_OUTPUT 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WORM-FS 2 | 3 | WORM-FS is WORM (Write Once Read Many) compliant file system. Once a file is created, you cannot delete or update it, until the retention period has expired. 4 | 5 | You can set up retention periods at file level, using rules or manually. For instance, you can choose to set retention period of 1 year for png files, retention period of 6 month for wav files etc... If no filter applies to one file/folder, the parent folder retentions is used. For this reason you can set hierarchical structures, for instance one root folder with 5 years retention, another one with 2 years retention etc... 6 | 7 | 8 | Please note that retention period is defined in days. Most of time, this file system is exposed to consumers through network shares (SAMBA, FTP WebDav etc...). 9 | 10 | For more information about architecture, installation and usage, please consult wiki pages. 11 | -------------------------------------------------------------------------------- /TestWithAutoCommit.conf: -------------------------------------------------------------------------------- 1 | # 2 | # WORM Configuration File 3 | # 4 | 5 | [General] 6 | ID=1 # unique server ID 7 | DefaultRetention=1 # default file retention in days 8 | MountPath=/mnt/WORM # directory mounted as WORM 9 | RepositoryType=0 # repository type, 0=directory, 1=file 10 | RepositoryPath=/tmp # mounted repository path 11 | RepositoryFile=/root/vault.vlt # if RepositoryType=1, vault file location 12 | Database=10.0.0.1 # database host 13 | LockDelay=1 # file lock delay in seconds 14 | AutoLock=1 # auto lock feature 15 | 16 | [Audit] 17 | AuditMode=0 # audit modes, 0=none, 1=log file, 2=posix message queue, 3=persistent message queue 18 | 19 | # user define retentions bases on filter rules 20 | # use regex expression to apply filter 21 | # syntax is regexfilter retention-value-in-days 22 | # if no rules are present, default retention is applied 23 | 24 | [Retention] 25 | \.rt0$=0 26 | \.rt1$=1 27 | 28 | 29 | -------------------------------------------------------------------------------- /TestWithoutAutoCommit.conf: -------------------------------------------------------------------------------- 1 | # 2 | # WORM Configuration File 3 | # 4 | 5 | [General] 6 | ID=1 # unique server ID 7 | DefaultRetention=1 # default file retention in days 8 | MountPath=/mnt/WORM # directory mounted as WORM 9 | RepositoryType=0 # repository type, 0=directory, 1=file 10 | RepositoryPath=/tmp # mounted repository path 11 | RepositoryFile=/root/vault.vlt # if RepositoryType=1, vault file location 12 | Database=10.0.0.1 # database host 13 | LockDelay=1 # file lock delay in seconds 14 | AutoLock=0 # auto lock feature 15 | 16 | [Audit] 17 | AuditMode=0 # audit modes, 0=none, 1=log file, 2=posix message queue, 3=persistent message queue 18 | 19 | # user define retentions bases on filter rules 20 | # use regex expression to apply filter 21 | # syntax is regexfilter retention-value-in-days 22 | # if no rules are present, default retention is applied 23 | 24 | [Retention] 25 | \.rt0$=0 26 | \.rt1$=1 27 | 28 | 29 | -------------------------------------------------------------------------------- /worm.conf: -------------------------------------------------------------------------------- 1 | # 2 | # WORM Configuration File 3 | # 4 | 5 | [General] 6 | ID=1 # unique server ID 7 | DefaultRetention=1 # default file retention in days 8 | MountPath=/mnt/WORM # directory mounted as WORM 9 | RepositoryType=0 # repository type, 0=directory, 1=file 10 | RepositoryPath=/tmp # mounted repository path 11 | RepositoryFile=/root/vault.vlt # if RepositoryType=1, vault file location 12 | Database=10.0.0.1 # database host 13 | LockDelay=300 # file lock delay in seconds 14 | AutoLock=0 # auto lock feature 15 | 16 | [Audit] 17 | AuditMode=1 # audit modes, 0=none, 1=log file, 2=posix message queue, 3=persistent message queue 18 | 19 | # user defined retentions based on filter rules 20 | # use regex expression to apply filter 21 | # syntax is regexfilter retention-value-in-days 22 | # if no rules are present, default retention is applied 23 | 24 | [Retention] 25 | \.WAV$=1 26 | \.txt$=2 27 | 28 | 29 | -------------------------------------------------------------------------------- /file.h: -------------------------------------------------------------------------------- 1 | #ifndef _File_ 2 | #define _File_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | 21 | int WORM_open(const char *path, struct fuse_file_info *fi); 22 | int WORM_read(const char *path, char *buf, size_t size, off_t offset,struct fuse_file_info *fi); 23 | int WORM_truncate(const char *path, off_t newsize); 24 | int WORM_write(const char *path, const char *buf, size_t size, off_t offset,struct fuse_file_info *fi); 25 | int WORM_flush(const char *path, struct fuse_file_info *fi); 26 | int WORM_release(const char *path, struct fuse_file_info *fi); 27 | int WORM_create(const char *path, mode_t mode, struct fuse_file_info *fi); 28 | int WORM_ftruncate(const char *path, off_t offset, struct fuse_file_info *fi); 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /utils/setretention.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | int main(int argc, char **argv) 16 | { 17 | unsigned short retention; 18 | int status; 19 | char *path; 20 | 21 | if (argc<3) 22 | { 23 | printf("Bad arguments, expected syntax: setretention \n"); 24 | return 0; 25 | } 26 | 27 | status=sscanf(argv[2],"%hu",&retention); 28 | if (status==EOF) 29 | { 30 | printf("Bad arguments, expected syntax: setretention \n"); 31 | return 0; 32 | } 33 | 34 | path=argv[1]; 35 | status = lsetxattr(path, "user.Retention", &retention, sizeof(retention),0); 36 | if (status<0) 37 | { 38 | printf("Cannot set retention: %s\n",strerror(errno)); 39 | } 40 | else 41 | { 42 | printf("Retention set\n"); 43 | } 44 | 45 | 46 | return 0; 47 | } 48 | 49 | -------------------------------------------------------------------------------- /retention.h: -------------------------------------------------------------------------------- 1 | #ifndef _Retention_ 2 | #define _Retention_ 3 | 4 | 5 | #include 6 | #include 7 | #include "utils.h" 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | unsigned short getParentRetention(const char* funcName,const char *path); 16 | unsigned short getRetention(const char* funcName,const char *path); 17 | 18 | time_t calcExpirationDate(const char* funcName,unsigned short retention); 19 | time_t getExpirationDate(const char* funcName,const char* path); 20 | time_t getLockDate(const char* funcName,const char* path); 21 | 22 | void setRetention(const char* funcName,const char *path,const char *convertedPath); 23 | void setExpirationDate(const char* funcName,const char* path,const char *convertedPath); 24 | void setExpirationDateExplicit(const char* funcName,const char* path,const char *convertedPath,time_t expirationDate); 25 | void setLockDate(const char* funcName,const char* path,const char *convertedPath); 26 | 27 | int isExpired(const char* funcName,const char *path); 28 | //void setRetentionAndExpiration(const char *path,const char *convertedPath); 29 | 30 | 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Marc GUICHARD 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /utils/getretention.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | int main(int argc, char **argv) 17 | { 18 | unsigned short retention; 19 | int status; 20 | time_t expiration; 21 | char *path; 22 | time_t now; 23 | int64_t value; 24 | char* timeString; 25 | 26 | if (argc<2) 27 | { 28 | printf("Bad arguments, expected syntax: getretention \n"); 29 | return 0; 30 | } 31 | 32 | 33 | path=argv[1]; 34 | status = lgetxattr(path, "user.Retention", &retention, sizeof(retention)); 35 | if (status<0) 36 | { 37 | printf("Cannot get retention: %s\n",strerror(errno)); 38 | } 39 | else 40 | { 41 | printf("Retention is %hu day(s)\n",retention); 42 | } 43 | 44 | status = lgetxattr(path, "user.ExpirationDate", &value, sizeof(value)); 45 | if (status<0) 46 | { 47 | printf("Cannot get expiration date: %s\n",strerror(errno)); 48 | } 49 | else 50 | { 51 | 52 | //printf("Value is : %" PRId64,value); 53 | 54 | expiration=value; // be sure to read 64 bits 55 | timeString=ctime(&expiration); 56 | if (timeString==NULL) printf("Expiration date is (NULL)\n"); 57 | else printf("Expiration date is %s\n",timeString); 58 | 59 | now=time(NULL); 60 | 61 | if (now>expiration) 62 | { 63 | printf("Media is expired\n"); 64 | } 65 | else 66 | { 67 | printf("Media is not expired\n"); 68 | } 69 | } 70 | 71 | 72 | return 0; 73 | } 74 | 75 | -------------------------------------------------------------------------------- /logger.h: -------------------------------------------------------------------------------- 1 | #ifndef _Logger_ 2 | #define _Logger_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | //extern int MaxLinesInLogFiles; 9 | 10 | extern const char* DEBUG; 11 | extern const char* INFO; 12 | extern const char* WARN; 13 | extern const char* ERROR; 14 | 15 | extern const char* SUCCESS; 16 | extern const char* FAILURE; 17 | extern const char* OK; 18 | extern const char* NOK; 19 | extern const char* NOTEXPIRED; 20 | 21 | extern const char* CREATE; 22 | extern const char* DELETE; 23 | extern const char* UPDATE; 24 | 25 | extern const char* cFILE; 26 | extern const char* DIRECTORY; 27 | extern const char* EXPIRATION; 28 | extern const char* LOCK; 29 | extern const char* RETENTION; 30 | extern const char* OWNER; 31 | extern const char* MODE; 32 | extern const char* TIME; 33 | extern const char* LOCATION; 34 | extern const char* ATTRIBUTE; 35 | 36 | typedef struct 37 | { 38 | LogFile* logFile; 39 | LogFile* auditFile; 40 | mqd_t auditQueue; 41 | } Logger; 42 | 43 | 44 | extern void (*writeAudit)(const char*,const char*,const char* ,const char* ,const char*); 45 | 46 | 47 | int logger_init(void); 48 | void logger_free(void); 49 | 50 | void logger_enter(const char *funcName,const char* path); 51 | void logger_log(const char* funcName,const char* path,const char* errorLevel,const char *format, ...); 52 | int logger_errno(const char* funcName,const char* path,const char* message); 53 | 54 | //void writeAudit(const char *action,const char *entity,const char* result,const char* path,const char *value); 55 | void logger_auditSuccess(const char *action,const char *entity,const char* path,const char *format, ...); 56 | void logger_auditFailure(const char *action,const char *entity,const char* path,const char *format, ...); 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /SynoBuildConf/install: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (C) 2000-2016 Synology Inc. All rights reserved. 3 | 4 | ### Use PKG_DIR as working directory. 5 | PKG_DIR=/tmp/_test_spk 6 | rm -rf $PKG_DIR 7 | mkdir -p $PKG_DIR 8 | 9 | ### get spk packing functions 10 | source /pkgscripts/include/pkg_util.sh 11 | 12 | create_package_tgz() { 13 | local firewere_version= 14 | local package_tgz_dir=/tmp/_package_tgz 15 | local binary_dir=$package_tgz_dir/usr/local/bin 16 | 17 | ### clear destination directory 18 | rm -rf $package_tgz_dir && mkdir -p $package_tgz_dir 19 | 20 | ### install needed file into PKG_DIR 21 | #mkdir -p $binary_dir 22 | #cp -av worm $binary_dir 23 | make install DESTDIR="$package_tgz_dir" 24 | 25 | ### create package.tgz $1: source_dir $2: dest_dir 26 | pkg_make_package $package_tgz_dir "${PKG_DIR}" 27 | } 28 | 29 | create_spk(){ 30 | local scripts_dir=$PKG_DIR/scripts 31 | local wizards_dir=$PKG_DIR/WIZARD_UIFILES 32 | 33 | ### Copy package center scripts to PKG_DIR 34 | mkdir -p $scripts_dir 35 | cp -av scripts/* $scripts_dir 36 | 37 | ### Copy package center wizards to PKG_DIR 38 | mkdir -p $wizards_dir 39 | cp -av WIZARD_UIFILES/* $wizards_dir 40 | 41 | ### Copy package icon 42 | cp -av PACKAGE_ICON*.PNG $PKG_DIR 43 | 44 | ### Generate INFO file 45 | ./INFO.sh > INFO 46 | cp INFO $PKG_DIR/INFO 47 | 48 | ### Create the final spk. 49 | # pkg_make_spk 50 | # Please put the result spk into /image/packages 51 | # spk name functions: pkg_get_spk_name pkg_get_spk_unified_name pkg_get_spk_family_name 52 | mkdir -p /image/packages 53 | pkg_make_spk ${PKG_DIR} "/image/packages" $(pkg_get_spk_family_name) 54 | } 55 | 56 | main() { 57 | create_package_tgz 58 | create_spk 59 | } 60 | 61 | main "$@" 62 | -------------------------------------------------------------------------------- /config.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "config.h" 4 | #include "ini.h" 5 | 6 | Config config = {.ID=0,.auditMode=0,.lockDelay=300,.autoLock=300}; 7 | 8 | 9 | int config_init() 10 | { 11 | 12 | config.iniFile = ini_open("/etc/worm.conf"); 13 | if (config.iniFile==NULL) 14 | { 15 | fprintf(stderr,"Failed to open configuration file\n"); 16 | return -1; 17 | } 18 | 19 | config.ID = ini_getInt(config.iniFile, "General","ID", 1); 20 | config.mountPath=ini_getString(config.iniFile,"General","MountPath","/mnt/WORM"); 21 | config.repositoryType=ini_getInt(config.iniFile,"General","RepositoryType", 0); 22 | config.repositoryPath=ini_getString(config.iniFile,"General","RepositoryPath","/tmp"); 23 | config.repositoryFile=ini_getString(config.iniFile,"General","RepositoryFile","NA"); 24 | config.defaultRetention = ini_getInt(config.iniFile, "General","DefaultRetention", 0); 25 | config.lockDelay = ini_getInt(config.iniFile, "General","LockDelay", 300); 26 | config.autoLock = ini_getInt(config.iniFile, "General","AutoLock", 0); 27 | config.auditMode=ini_getInt(config.iniFile,"Audit","AuditMode",0); 28 | 29 | if (config.repositoryType==1) 30 | { 31 | strcpy(config.repositoryPath,"/tmp/mnt.XXXXXX"); 32 | config.repositoryPath=mkdtemp(config.repositoryPath); 33 | if (config.repositoryPath==NULL) return -1; 34 | } 35 | 36 | 37 | return 0; 38 | } 39 | int config_loadFilters() 40 | { 41 | config.filters=filters_load(config.iniFile); 42 | if (config.filters==NULL) return -1; 43 | return 0; 44 | } 45 | 46 | int config_free() 47 | { 48 | if ((config.repositoryType==1) && (config.repositoryPath!=NULL)) 49 | { 50 | rmdir(config.repositoryPath); 51 | } 52 | 53 | if (config.iniFile!=NULL) ini_free(config.iniFile); 54 | if (config.filters!=NULL) filters_free(config.filters); 55 | } -------------------------------------------------------------------------------- /filter.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "filter.h" 5 | #include "logger.h" 6 | #include "context.h" 7 | 8 | Filters* filters_load(IniFile* iniFile) 9 | { 10 | Filters* filters; 11 | 12 | char* pattern; 13 | int value; 14 | int result; 15 | Section* section; 16 | int index; 17 | keyValuePair keyValuePair; 18 | 19 | filters=malloc(sizeof(Filter)); 20 | 21 | logger_enter(__func__,NAFILE); 22 | 23 | section=ini_getSection(iniFile,"Retention"); 24 | filters->items=malloc(sizeof(Filter)*section->count); 25 | 26 | filters->count=0; 27 | logger_log(__func__, NAFILE,INFO,"Parsing WORM filter rules"); 28 | for(index=0;indexcount;index++) 29 | { 30 | keyValuePair=section->items[index]; 31 | pattern=keyValuePair.key; 32 | value=ini_getUnsignedShort(iniFile,"Retention", pattern, 0); 33 | 34 | // need to extract rule from key 35 | 36 | logger_log(__func__, NAFILE,INFO,"Compiling filter rule: %s %i",pattern,value); 37 | filters->items[filters->count].value=value; 38 | result=regcomp(&filters->items[filters->count].regex,pattern,REG_ICASE|REG_NOSUB|REG_NEWLINE|REG_EXTENDED); 39 | if (result!=0) 40 | { 41 | logger_log(__func__, NAFILE,ERROR,"Invalid filter regex pattern %s",pattern); 42 | } else filters->count++; 43 | } 44 | logger_log(__func__, NAFILE,INFO,"Filters count=%i",filters->count); 45 | 46 | return filters; 47 | 48 | } 49 | 50 | unsigned short filters_getRetention(Filters* filters,const char* path,int* filterIndex) 51 | { 52 | int index; 53 | int match; 54 | logger_enter(__func__,NAFILE); 55 | 56 | logger_log(__func__, path,DEBUG, "Try to match rule patterns"); 57 | for(index=0;indexcount;index++) 58 | { 59 | match=regexec(&filters->items[index].regex, path, 0, NULL, 0); 60 | if (match==0) 61 | { 62 | *filterIndex=index; 63 | return filters->items[index].value; 64 | } 65 | } 66 | return 65535; 67 | } 68 | 69 | int filters_free(Filters* filters) 70 | { 71 | int index; 72 | 73 | for(index=0;indexcount;index++) 74 | { 75 | regfree(&filters->items[index].regex); 76 | }//*/ 77 | 78 | free(filters->items); 79 | free(filters); 80 | } 81 | 82 | 83 | -------------------------------------------------------------------------------- /software.spec: -------------------------------------------------------------------------------- 1 | Name: worm 2 | Version: 1.0 3 | Release: 1 4 | Summary: WORM compliant file system 5 | PreReq: fuse-libs 6 | 7 | 8 | Group: Applications/Internet 9 | License: MIT 10 | URL: https://github.com/dfgs/WORM-FS 11 | 12 | 13 | %define WORM_SOURCE_FILE worm 14 | %define WORM_SOURCE_DIR %{WORM_SOURCE_FILE} 15 | %define WORM_VERSION 1.0 16 | %define WORM_FULLPATH %{name}-%{version}/%{WORM_SOURCE_DIR}-%{WORM_VERSION} 17 | 18 | %define GETRETENTION_SOURCE_FILE getretention 19 | %define GETRETENTION_SOURCE_DIR %{GETRETENTION_SOURCE_FILE} 20 | %define GETRETENTION_VERSION 1.0 21 | %define GETRETENTION_FULLPATH %{name}-%{version}/%{GETRETENTION_SOURCE_DIR}-%{GETRETENTION_VERSION} 22 | 23 | 24 | Source0: %{WORM_SOURCE_FILE}-%{WORM_VERSION}.tar.gz 25 | Source1: %{GETRETENTION_SOURCE_FILE}-%{GETRETENTION_VERSION}.tar.gz 26 | 27 | 28 | %description 29 | WORM-FS is WORM (Write Once Read Many) compliant file system. Once a file is created, you cannot delete or update it, until the retention period has expired. 30 | 31 | 32 | %prep 33 | %setup -c -a 0 -a 1 34 | 35 | %build 36 | cd %{_builddir}/%{WORM_FULLPATH} 37 | ./configure 38 | make 39 | 40 | cd %{_builddir}/%{GETRETENTION_FULLPATH} 41 | ./configure 42 | make 43 | 44 | %install 45 | mkdir -p $RPM_BUILD_ROOT/usr/local/bin 46 | mkdir -p $RPM_BUILD_ROOT/usr/lib/systemd/system 47 | mkdir -p $RPM_BUILD_ROOT/etc 48 | mkdir -p $RPM_BUILD_ROOT/etc/logrotate.d 49 | 50 | install %{WORM_SOURCE_DIR}-%{WORM_VERSION}/%{WORM_SOURCE_FILE} $RPM_BUILD_ROOT/usr/local/bin 51 | install %{GETRETENTION_SOURCE_DIR}-%{GETRETENTION_VERSION}/%{GETRETENTION_SOURCE_FILE} $RPM_BUILD_ROOT/usr/local/bin 52 | install %{WORM_SOURCE_DIR}-%{WORM_VERSION}/worm.service $RPM_BUILD_ROOT/usr/lib/systemd/system 53 | install %{WORM_SOURCE_DIR}-%{WORM_VERSION}/worm.conf $RPM_BUILD_ROOT/etc 54 | install %{WORM_SOURCE_DIR}-%{WORM_VERSION}/wormrotate $RPM_BUILD_ROOT/etc/logrotate.d/worm 55 | 56 | 57 | %files 58 | %defattr(-,root,root) 59 | #%doc README 60 | %attr(644, root, root) %config(noreplace) /etc/worm.conf 61 | %attr(644, root, root) %config(noreplace) /etc/logrotate.d/worm 62 | %attr(644, root, root) %config(noreplace) /usr/lib/systemd/system/worm.service 63 | /usr/local/bin 64 | 65 | %clean 66 | rm -rf $RPM_BUILD_ROOT 67 | 68 | %changelog 69 | * Fri Jan 20 2017 Marc GUICHARD - 1.0-1 70 | - Initial build 71 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /start.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | use strict; 3 | use warnings; 4 | use Getopt::Long; 5 | 6 | my @users; 7 | my @shares; 8 | my $file; 9 | my $folder; 10 | 11 | $SIG{TERM} = sub { die "Received TERM signal, exiting..." }; 12 | 13 | GetOptions ("user=s" => \@users, # string 14 | "share=s" => \@shares) # flag 15 | or die("Error in command line arguments\n"); 16 | 17 | print("Creating users...\n"); 18 | foreach my $user (@users) { 19 | if ( $user =~ /^([a-z_][a-z0-9_-]*[\$]?):([^:]+):(\d+)/) 20 | { 21 | print "Login:$1 Password:$2 ID:$3\n"; 22 | system("groupadd -g $3 $1"); 23 | system("useradd -s /sbin/nologin $1 -M -u $3 -g $3"); 24 | system("echo $2 | passwd $1 --stdin"); 25 | system("printf \"$2\n$2\" | smbpasswd -a -s $1"); 26 | } 27 | else 28 | { 29 | warn "Invalid user definition: $user"; 30 | } 31 | } 32 | 33 | print("Creating shares...\n"); 34 | 35 | rename("/etc/samba/smb.conf","/etc/samba/smb.old") or die "Could not backup samba configuration file !"; 36 | open($file, '>', "/etc/samba/smb.conf") or die "Could not open samba configuration file !"; 37 | 38 | print $file "[global]\n"; 39 | print $file "workgroup = WORKGROUP\n"; 40 | print $file "server string = WORM Server\n"; 41 | print $file "\n"; 42 | print $file "# log files split per-machine:\n"; 43 | print $file "log file = /var/log/samba/log.%m\n"; 44 | print $file "# maximum size of 50KB per log file, then rotate:\n"; 45 | print $file "max log size = 50\n"; 46 | print $file "\n"; 47 | print $file "#security\n"; 48 | print $file "security = user\n"; 49 | print $file "passdb backend = tdbsam\n"; 50 | print $file "\n"; 51 | print $file "#printers\n"; 52 | print $file "load printers = no\n"; 53 | print $file "passdb backend = tdbsam\n"; 54 | print $file "map to guest = Bad User\n"; 55 | print $file "unix password sync = yes\n"; 56 | print $file "\n"; 57 | print $file "#shares\n"; 58 | 59 | foreach my $share (@shares) { 60 | if ( $share =~ /^([A-Za-z0-9_-]+):((?:\/[^\/]+)*\/):([a-z_][a-z0-9_-]*[\$]?)/) 61 | { 62 | $folder="/mnt/WORM".$2; 63 | 64 | print "Share:$1 Path:$2 User:$3\n"; 65 | print $file "[$1]\n"; 66 | print $file "path = $folder\n"; 67 | print $file "comment = WORM share\n"; 68 | print $file "read only = no\n"; 69 | print $file "browseable = yes\n"; 70 | print $file "guest ok = no\n"; 71 | print $file "valid users = $3\n"; 72 | print $file "admin users = Bad User\n"; 73 | print $file "\n"; 74 | 75 | #if (!-d $folder) { 76 | # print("Shared folder doesn't exists creating...\n"); 77 | # mkdir($folder) or warn "Failed to create folder $folder"; 78 | # system("chown $3:$3 $folder"); 79 | #} 80 | 81 | } 82 | else 83 | { 84 | warn "Invalid share definition: $share"; 85 | } 86 | 87 | 88 | } 89 | 90 | close $file; 91 | 92 | print("Launching WORM daemon...\n"); 93 | system("/usr/local/bin/WORM -o allow_other,default_permissions"); 94 | 95 | print("Launching SAMBA daemon...\n"); 96 | system("smbd"); 97 | 98 | print("Waiting for TERM signal..."); 99 | sleep; 100 | -------------------------------------------------------------------------------- /utils.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "utils.h" 13 | #include "logger.h" 14 | #include "retention.h" 15 | #include "context.h" 16 | #include "config.h" 17 | 18 | void convertTime(time_t time,char *buffer,int bufferSize) 19 | { 20 | // struct stat info; 21 | struct tm * timeinfo; 22 | 23 | timeinfo = localtime(&time); 24 | strftime(buffer, bufferSize, "%Y-%m-%d %H:%M:%S", timeinfo); 25 | } 26 | 27 | void convertPath(char *destPath, const char *originalPath) 28 | { 29 | //logEnter("convertPath"); 30 | strcpy(destPath, config.repositoryPath); 31 | strncat(destPath, originalPath, PATH_MAX); // ridiculously long paths will break here 32 | 33 | //writeLog("Convert path",originalPath,DEBUG, "convertPath: rootdir = %s, original path = %s, converted path = %s",repositoryPath, originalPath, destPath); 34 | } 35 | 36 | void setRealOwnerID(const char* funcName,const char *path) 37 | { 38 | int returnStatus; 39 | 40 | struct fuse_context *context; 41 | logger_enter(__func__,path); 42 | 43 | context=fuse_get_context(); 44 | logger_log(funcName,path,DEBUG,"Try to update fs entry owner from pid=%i to uid=%i/gid=%i",context->pid, context->uid,context->gid); 45 | returnStatus=chown(path,context->uid,context->gid); 46 | if (returnStatus<0) returnStatus = logger_errno(__func__, path,"Failed to update fs entry owner"); 47 | 48 | } 49 | 50 | int getReadOnlyMode(const char* funcName,const char *path,int mode) 51 | { 52 | if (S_ISDIR(mode)!=0) return mode; 53 | 54 | if (isExpired(funcName,path) == 0) 55 | { 56 | //writeLog(DEBUG,"Change mode from %i to %i (readonly)",Mode,Mode & 65389); 57 | return mode & 65389; 58 | } 59 | else return mode; 60 | } 61 | int isReadOnly(const char *path) 62 | { 63 | int returnStatus = 0; 64 | struct stat statbuf; 65 | 66 | returnStatus = lstat(path, &statbuf); 67 | if (returnStatus != 0) 68 | { 69 | returnStatus = logger_errno(__func__, path,"Failed to get read only status"); 70 | return 0; 71 | } 72 | return (statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))==0; 73 | 74 | } 75 | int fileExists(const char *path) 76 | { 77 | FILE* file; 78 | 79 | file = fopen(path, "r"); 80 | if (file != NULL) 81 | { 82 | fclose(file); 83 | return 1; 84 | } 85 | return 0; 86 | } 87 | int directoryExists(const char *path) 88 | { 89 | DIR* dir = opendir(path); 90 | if (dir) 91 | { 92 | closedir(dir); 93 | return 1; 94 | } 95 | else return 0; 96 | 97 | } 98 | int createDirectory(const char* funcName,const char *path) 99 | { 100 | int returnStatus; 101 | 102 | returnStatus = mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); 103 | if (returnStatus != 0) returnStatus = logger_errno(funcName, path,"Failed to create directory"); 104 | return returnStatus; 105 | } 106 | -------------------------------------------------------------------------------- /link.c: -------------------------------------------------------------------------------- 1 | #include "link.h" 2 | #include "logger.h" 3 | #include "utils.h" 4 | #include "retention.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | int WORM_readlink(const char *path, char *link, size_t size) 13 | { 14 | int returnStatus = 0; 15 | char convertedPath[PATH_MAX]; 16 | 17 | logger_enter(__func__,path); 18 | convertPath(convertedPath, path); 19 | 20 | logger_log(__func__, path,INFO, "Try to read link"); 21 | returnStatus = readlink(convertedPath, link, size - 1); 22 | if (returnStatus < 0) 23 | { 24 | returnStatus = logger_errno(__func__, path,"Failed to read link"); 25 | } 26 | else 27 | { 28 | link[returnStatus] = '\0'; // this part is required because readlink doesn't return ending char. Fuse requires it. 29 | returnStatus = 0; 30 | } 31 | 32 | 33 | return returnStatus; 34 | } 35 | 36 | /** Remove a file */ 37 | int WORM_unlink(const char *path) 38 | { 39 | int returnStatus = 0; 40 | char convertedPath[PATH_MAX]; 41 | 42 | logger_enter(__func__,path); 43 | convertPath(convertedPath, path); 44 | 45 | logger_log(__func__,path,INFO,"Checking expiration"); 46 | if (isExpired(__func__,convertedPath) == 0) 47 | { 48 | logger_log(__func__,path,WARN, "Media is not expired"); 49 | logger_auditFailure(DELETE,cFILE,path,NOTEXPIRED); 50 | return -EACCES; 51 | } 52 | 53 | logger_log(__func__, path,INFO, "Try to unlink target"); 54 | returnStatus = unlink(convertedPath); 55 | if (returnStatus != 0) 56 | { 57 | returnStatus = logger_errno(__func__, path,"Failed to unlink target"); 58 | logger_auditFailure(DELETE,cFILE,path,NOK); 59 | } 60 | else 61 | { 62 | logger_auditSuccess(DELETE,cFILE,path,OK); 63 | } 64 | 65 | return returnStatus; 66 | } 67 | 68 | // The parameters here are a little bit confusing, but do correspond 69 | // to the symlink() system call. The 'path' is where the link points, 70 | // while the 'link' is the link itself. So we need to leave the path 71 | // unaltered, but insert the link into the mounted directory. 72 | int WORM_symlink(const char *path, const char *link) 73 | { 74 | int returnStatus = 0; 75 | char linkPath[PATH_MAX]; 76 | 77 | logger_enter(__func__,path); 78 | convertPath(linkPath, link); 79 | 80 | logger_log(__func__, path,INFO, "Try to create link"); 81 | returnStatus = symlink(path, linkPath); 82 | if (returnStatus != 0) returnStatus = logger_errno(__func__, link,"Failed to create link"); 83 | 84 | return returnStatus; 85 | } 86 | 87 | int WORM_link(const char *path, const char *newpath) 88 | { 89 | int returnStatus = 0; 90 | char convertedPath[PATH_MAX], newConvertedPath[PATH_MAX]; 91 | 92 | logger_enter(__func__,path); 93 | convertPath(convertedPath, path); 94 | convertPath(newConvertedPath, newpath); 95 | 96 | logger_log(__func__, path,INFO, "Try to create link"); 97 | returnStatus = link(convertedPath, newConvertedPath); 98 | if (returnStatus != 0) returnStatus = logger_errno(__func__, path,"Failed to create link"); 99 | 100 | 101 | return returnStatus; 102 | } 103 | -------------------------------------------------------------------------------- /loop.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE // mandatory in order to compile successfully 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "loop.h" 12 | #include "logger.h" 13 | #include "context.h" 14 | 15 | static int getFreeLoop(char* buffer,int size) 16 | { 17 | FILE *fp; 18 | int length; 19 | 20 | logger_enter(__func__,NAFILE); 21 | 22 | memset(buffer,0,size); 23 | 24 | logger_log(__func__,NAFILE,DEBUG,"Getting free loop device"); 25 | fp = popen("losetup -f", "r"); 26 | if (fp == NULL) 27 | { 28 | logger_errno(__func__,NAFILE,"Failed to get free loop device"); 29 | return -1; 30 | } 31 | if (fgets(buffer, size-1, fp)==NULL) 32 | { 33 | logger_errno(__func__,NAFILE,"Failed to get free loop device"); 34 | fclose(fp); 35 | return -1; 36 | } 37 | pclose(fp); 38 | 39 | length=strlen(buffer); 40 | if (length>0) buffer[length-1]='\0'; 41 | 42 | logger_log(__func__,NAFILE,DEBUG,"Found free loop device: %s",buffer); 43 | return 0; 44 | } 45 | 46 | static int loop_bind(Loop* loop) 47 | { 48 | char command[1024]; 49 | 50 | logger_enter(__func__,NAFILE); 51 | 52 | strcpy(command,"losetup "); 53 | strcat(command,loop->name); 54 | strcat(command," "); 55 | strcat(command,loop->fileName); 56 | 57 | logger_log(__func__,NAFILE,DEBUG,"%s",command); 58 | 59 | return system(command); 60 | } 61 | static int loop_unbind(Loop* loop) 62 | { 63 | char command[1024]; 64 | 65 | logger_enter(__func__,NAFILE); 66 | 67 | strcpy(command,"losetup -d "); 68 | strcat(command,loop->name); 69 | 70 | return system(command); 71 | } 72 | 73 | Loop* loop_create(const char* fileName,const char* mountPoint) 74 | { 75 | Loop* loop; 76 | 77 | logger_enter(__func__,NAFILE); 78 | 79 | 80 | loop=malloc(sizeof(Loop)); 81 | loop->fileName=fileName; 82 | loop->mountPoint=mountPoint; 83 | 84 | logger_log(__func__,NAFILE,DEBUG,"Creating loop device"); 85 | if (getFreeLoop(loop->name,1024)!=0) 86 | { 87 | logger_log(__func__,NAFILE,ERROR,"Failed to create loop device"); 88 | free(loop); 89 | return NULL; 90 | } 91 | if (loop_bind(loop)!=0) 92 | { 93 | logger_log(__func__,NAFILE,ERROR,"Failed to bind loop device"); 94 | free(loop); 95 | return NULL; 96 | } 97 | 98 | return loop; 99 | } 100 | 101 | 102 | int loop_mount(Loop* loop) 103 | { 104 | logger_enter(__func__,NAFILE); 105 | 106 | 107 | /*logger_log(__func__,NAFILE,DEBUG,"Unshare filesystem namespace"); 108 | if (unshare(CLONE_NEWNS | CLONE_FS) != 0) 109 | { 110 | logger_errno(__func__,NAFILE,"Failed to unshare filesystem namespace"); 111 | return -1; 112 | }//*/ 113 | 114 | logger_log(__func__,NAFILE,DEBUG,"Mounting loop device"); 115 | /*if (mount("none", "/", NULL, MS_REC|MS_PRIVATE, NULL) != 0) 116 | { 117 | logger_ernno(__func__,NAFILE,ERROR,"Failed to mount loop device"); 118 | return -1; 119 | }*/ 120 | if (mount(loop->name, loop->mountPoint, "ext4",0,NULL)!= 0) 121 | { 122 | logger_errno(__func__,NAFILE,"Failed to mount loop device"); 123 | return -1; 124 | } 125 | 126 | return 0; 127 | 128 | } 129 | int loop_umount(Loop* loop) 130 | { 131 | logger_enter(__func__,NAFILE); 132 | return umount(loop->mountPoint); 133 | } 134 | 135 | void loop_free(Loop* loop) 136 | { 137 | logger_enter(__func__,NAFILE); 138 | loop_unbind(loop); 139 | free(loop); 140 | } 141 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /ParseAudit.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | use DBI; 6 | use POSIX qw(strftime); 7 | #use Proc::Daemon; 8 | use Config::IniFiles; 9 | 10 | my $dbh=undef; 11 | my $command=undef; 12 | my $sql=undef; 13 | my $fh=undef; 14 | my $host=undef; 15 | my $waitSeconds=10; 16 | my $quit = 0; 17 | 18 | 19 | 20 | 21 | sub Wait 22 | { 23 | my $i = 0; 24 | print "Waiting for $waitSeconds seconds\n"; 25 | while(($i<$waitSeconds) and (!$quit)) 26 | { 27 | sleep 1; 28 | $i++; 29 | }; 30 | } 31 | 32 | sub SendAuditSignal() 33 | { 34 | my $error=1; 35 | 36 | while(($error) and (!$quit)) 37 | { 38 | print "Send USR1 signal to WORM process\n"; 39 | $error=`pkill --signal USR1 WORM 2>&1`; 40 | if ($error) 41 | { 42 | print "Cannot send USR1 signal: $error"; 43 | &Wait; 44 | } 45 | }; 46 | } 47 | 48 | sub ConnectDatabase() 49 | { 50 | $dbh=0; 51 | 52 | while((!$dbh) and (!$quit)) 53 | { 54 | print "Trying to connect database ($host)\n"; 55 | $dbh = DBI->connect("dbi:mysql:WORM;host=$host",'audit','audit1708$'); 56 | if (!$dbh) 57 | { 58 | warn "Failed to connect database: $DBI::errstr\n"; 59 | &Wait; 60 | } 61 | }; 62 | 63 | } 64 | 65 | sub CloseDatabase() 66 | { 67 | if ($dbh) 68 | { 69 | print "Close database\n"; 70 | $dbh->disconnect; 71 | &Wait; 72 | } 73 | } 74 | 75 | # 0=OK, 1=File Error, 2=SQL Error 76 | sub ProcessFile() 77 | { 78 | my $file=$_[0]; 79 | my $fullFileName=undef; 80 | my @parts=undef; 81 | my $error=0; 82 | 83 | 84 | print "Trying to open file $file\n"; 85 | $fullFileName = "/var/log/$file"; 86 | 87 | if (!(open($fh, '<:encoding(UTF-8)', $fullFileName))) 88 | { 89 | warn "Could not open file: $!"; 90 | $error= 1; 91 | } 92 | 93 | while ((my $row = <$fh>) and ($error==0) and (!$quit)) 94 | { 95 | chomp $row; 96 | my @parts=split('\|',$row); 97 | my $datestring = strftime "%Y-%m-%d %H:%M:%S", localtime($parts[0]); 98 | 99 | my $id=$parts[1]; 100 | my $operation=$parts[2]; 101 | my $entity=$parts[3]; 102 | my $result=$parts[4]; 103 | my $target=$parts[5]; 104 | my $value=$parts[6]; 105 | my $uid=$parts[7]; 106 | my $gid=$parts[8]; 107 | 108 | #print "$row\n"; 109 | 110 | $sql="CALL WriteAudit( '$datestring',$id,'$operation', '$entity','$result', '$target','$value',$uid,$gid);"; 111 | print "Insert audit in database: $sql\n"; 112 | 113 | if (!($command=$dbh->prepare($sql))) 114 | { 115 | $error=2; 116 | warn "Failed to prepare stored procedure: $!"; 117 | } 118 | elsif (!($command->execute())) 119 | { 120 | $error=2; 121 | warn "Failed to execute stored procedure: $!"; 122 | } 123 | else 124 | { 125 | $command->finish(); 126 | } 127 | 128 | } 129 | 130 | #print "Close file\n"; 131 | if ($error!=1) 132 | { 133 | close($fh); 134 | } 135 | 136 | #print "Trying to Remove audit file\n" 137 | if ($error==0) 138 | { 139 | unlink $fullFileName or warn "Failed to remove file: $!"; 140 | } 141 | 142 | return $error; 143 | } 144 | sub ParseFiles() 145 | { 146 | my @files=undef; 147 | my $file=undef; 148 | my $error=undef; 149 | 150 | print "Trying to parse all audit files\n"; 151 | opendir(DIR, "/var/log/") or die "Cannot open directory /var/log/\n"; 152 | @files = grep(/WORM_Audit-[0-9]+\.log$/,readdir(DIR)) or warn "Cannot enumerate files\n"; 153 | closedir(DIR); 154 | 155 | foreach $file (@files) 156 | { 157 | $error=&ProcessFile($file); 158 | if (($error==2) or ($quit==1)) 159 | { 160 | last; 161 | } 162 | } 163 | 164 | 165 | } 166 | 167 | 168 | #Proc::Daemon::Init; 169 | 170 | 171 | $SIG{TERM} = sub { print "Catch TERM signal\n"; $quit = 1; }; 172 | 173 | my $cfg = Config::IniFiles->new( -file => "/etc/WORM.conf" ,-handle_trailing_comment =>1); 174 | 175 | 176 | $host=$cfg->val( 'General', 'Database' ); 177 | 178 | while(!$quit) 179 | { 180 | &SendAuditSignal; 181 | &ConnectDatabase; 182 | &ParseFiles; 183 | &CloseDatabase; 184 | } 185 | 186 | -------------------------------------------------------------------------------- /directory.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "directory.h" 6 | #include "logger.h" 7 | #include "retention.h" 8 | #include "utils.h" 9 | #include "context.h" 10 | #include "config.h" 11 | 12 | 13 | int WORM_mkdir(const char *path, mode_t mode) 14 | { 15 | int returnStatus = 0; 16 | char convertedPath[PATH_MAX]; 17 | 18 | logger_enter(__func__,path); 19 | 20 | convertPath(convertedPath, path); 21 | 22 | logger_log(__func__, path,INFO, "Try to create directory"); 23 | returnStatus = mkdir(convertedPath, mode); 24 | 25 | if (returnStatus != 0) 26 | { 27 | returnStatus = logger_errno(__func__, path,"Failed to create directory"); 28 | logger_auditFailure(CREATE,DIRECTORY,path,NOK); 29 | } 30 | else 31 | { 32 | logger_auditSuccess(CREATE,DIRECTORY,path,OK); 33 | logger_log(__func__, path,INFO, "Setting retention and owner"); 34 | setRealOwnerID(__func__, convertedPath); 35 | setRetention(__func__,path,convertedPath); 36 | if (config.autoLock!=0) 37 | { 38 | logger_log(__func__, path,INFO, "Auto lock is on, setting expiration date"); 39 | setExpirationDate(__func__,path,convertedPath); 40 | } 41 | } 42 | 43 | return returnStatus; 44 | } 45 | 46 | int WORM_rmdir(const char *path) 47 | { 48 | int returnStatus = 0; 49 | char convertedPath[PATH_MAX]; 50 | 51 | logger_enter(__func__,path); 52 | 53 | convertPath(convertedPath, path); 54 | 55 | logger_log(__func__,path,INFO,"Checking expiration"); 56 | if (isExpired(__func__,convertedPath)==0) 57 | { 58 | logger_log(__func__,path,WARN, "Media is not expired"); 59 | logger_auditFailure(DELETE,DIRECTORY,path,NOTEXPIRED); 60 | return -EACCES; 61 | } 62 | 63 | logger_log(__func__, path,INFO, "Try to remove directory"); 64 | returnStatus = rmdir(convertedPath); 65 | if (returnStatus < 0) 66 | { 67 | returnStatus = logger_errno(__func__, path,"Failed to remove directory"); 68 | logger_auditFailure(DELETE,DIRECTORY,path,NOK); 69 | } 70 | else 71 | { 72 | logger_auditSuccess(DELETE,DIRECTORY,path,OK); 73 | } 74 | 75 | return returnStatus; 76 | } 77 | 78 | 79 | int WORM_readdir(const char *path, void *buf, fuse_fill_dir_t filler,off_t offset, struct fuse_file_info *fi) 80 | { 81 | int returnStatus = 0; 82 | DIR *directory; 83 | struct dirent *directoryEntry; 84 | char convertedPath[PATH_MAX]; 85 | 86 | 87 | logger_enter(__func__,path); 88 | convertPath(convertedPath, path); 89 | 90 | logger_log(__func__, path,INFO, "Try to read directory content"); 91 | directory = opendir(convertedPath); 92 | if (directory==NULL) 93 | { 94 | returnStatus = logger_errno(__func__, path,"Failed to read directory content"); 95 | return returnStatus; 96 | } 97 | 98 | logger_log(__func__, path,INFO, "Try to fill filler buffer"); 99 | while ((directoryEntry = readdir(directory)) != NULL) 100 | { 101 | if (filler(buf, directoryEntry->d_name, NULL, 0) != 0) 102 | { 103 | logger_log(__func__, path,ERROR, "Failed to fill filler buffer"); 104 | returnStatus = logger_errno(__func__, path,"Failed to fill filler buffer"); 105 | closedir(directory); 106 | return -ENOMEM; 107 | } 108 | } 109 | 110 | logger_log(__func__, path,INFO, "Try to close directory content"); 111 | returnStatus=closedir(directory); 112 | if (returnStatus != 0 ) returnStatus = logger_errno(__func__, path,"Failed to close directory content"); 113 | 114 | return returnStatus; 115 | } 116 | 117 | 118 | int WORM_opendir(const char *path, struct fuse_file_info *fi) 119 | { 120 | int returnStatus = 0; 121 | char convertedPath[PATH_MAX]; 122 | 123 | logger_enter(__func__,path); 124 | convertPath(convertedPath, path); 125 | 126 | logger_log(__func__, path,INFO, "Try to open directory"); 127 | fi->fh = (intptr_t)opendir(convertedPath); 128 | if (fi->fh == 0) returnStatus = logger_errno(__func__, path,"Failed to open directory"); 129 | 130 | return returnStatus; 131 | } 132 | 133 | int WORM_releasedir(const char *path, struct fuse_file_info *fi) 134 | { 135 | int returnStatus = 0; 136 | 137 | logger_enter(__func__,path); 138 | 139 | logger_log(__func__, path,INFO, "Try to close directory from file_info"); 140 | returnStatus=closedir((DIR *) (uintptr_t) fi->fh); 141 | if (returnStatus < 0) returnStatus = logger_errno(__func__, path,"Failed to close directory from file info"); 142 | 143 | return returnStatus; 144 | } 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /attribute.c: -------------------------------------------------------------------------------- 1 | #include "attribute.h" 2 | #include "logger.h" 3 | #include "utils.h" 4 | #include "retention.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | int WORM_getattr(const char *path, struct stat *statbuf) 13 | { 14 | int returnStatus = 0; 15 | char convertedPath[PATH_MAX]; 16 | 17 | logger_enter(__func__,path); 18 | 19 | convertPath(convertedPath, path); 20 | 21 | logger_log(__func__, path,INFO, "Try to get attributes"); 22 | returnStatus = lstat(convertedPath, statbuf); 23 | if (returnStatus != 0) returnStatus = logger_errno(__func__, path,"Failed to get attributes"); 24 | else statbuf->st_mode=getReadOnlyMode(__func__, convertedPath,statbuf->st_mode); 25 | 26 | return returnStatus; 27 | } 28 | int WORM_fgetattr(const char *path, struct stat *statbuf, struct fuse_file_info *fi) 29 | { 30 | int returnStatus = 0; 31 | char convertedPath[PATH_MAX]; 32 | 33 | logger_enter(__func__,path); 34 | 35 | convertPath(convertedPath, path); 36 | 37 | logger_log(__func__, path,INFO, "Try to get file attributes"); 38 | returnStatus = fstat(fi->fh, statbuf); 39 | 40 | if (returnStatus != 0) returnStatus = logger_errno(__func__, path,"Failed to get file attributes"); 41 | else statbuf->st_mode=getReadOnlyMode(__func__, convertedPath,statbuf->st_mode); 42 | 43 | return returnStatus; 44 | } 45 | 46 | 47 | 48 | int WORM_getxattr(const char *path, const char *name, char *value, size_t size) 49 | { 50 | int returnStatus = 0; 51 | char convertedPath[PATH_MAX]; 52 | 53 | logger_enter(__func__,path); 54 | convertPath(convertedPath, path); 55 | 56 | logger_log(__func__, path,INFO, "Try to get extended attribute %s",name); 57 | returnStatus = lgetxattr(convertedPath, name, value, size); 58 | 59 | if (returnStatus < 0) returnStatus = logger_errno(__func__, path,"Failed to get extended attribute"); 60 | 61 | return returnStatus; 62 | } 63 | 64 | 65 | int WORM_listxattr(const char *path, char *list, size_t size) 66 | { 67 | int returnStatus = 0; 68 | char convertedPath[PATH_MAX]; 69 | 70 | logger_enter(__func__,path); 71 | convertPath(convertedPath, path); 72 | 73 | logger_log(__func__, path,INFO, "Try to get extended attributes list"); 74 | returnStatus = llistxattr(convertedPath, list, size); 75 | 76 | if (returnStatus < 0) returnStatus = logger_errno(__func__, path,"Failed to get extended attributes list"); 77 | 78 | return returnStatus; 79 | } 80 | int WORM_setxattr(const char *path, const char *name, const char *value, size_t size, int flags) 81 | { 82 | int returnStatus = 0; 83 | char convertedPath[PATH_MAX]; 84 | 85 | logger_enter(__func__,path); 86 | convertPath(convertedPath, path); 87 | 88 | logger_log(__func__,path,INFO,"Checking expiration"); 89 | if (isExpired(__func__,convertedPath)==0) 90 | { 91 | logger_log(__func__,path,WARN, "Media is not expired"); 92 | logger_auditFailure(UPDATE,ATTRIBUTE,path,NOTEXPIRED,"%s",name); 93 | return -EACCES; 94 | } 95 | 96 | logger_log(__func__,path,INFO,"Try to set extended attribute %s",name); 97 | returnStatus = lsetxattr(convertedPath, name, value, size, flags); 98 | if (returnStatus < 0) 99 | { 100 | returnStatus = logger_errno(__func__, path,"Failed to set extended attributes"); 101 | logger_auditFailure(UPDATE,ATTRIBUTE,path,NOK,"%s",name); 102 | } 103 | else 104 | { 105 | logger_auditSuccess(UPDATE,ATTRIBUTE,path,OK,"%s",name); 106 | } 107 | return returnStatus; 108 | } 109 | 110 | int WORM_removexattr(const char *path, const char *name) 111 | { 112 | int returnStatus = 0; 113 | char convertedPath[PATH_MAX]; 114 | 115 | logger_enter(__func__,path); 116 | convertPath(convertedPath, path); 117 | 118 | logger_log(__func__,path,INFO,"Checking expiration"); 119 | if (isExpired(__func__,convertedPath)==0) 120 | { 121 | logger_log(__func__,path,WARN, "Media is not expired"); 122 | logger_auditFailure(DELETE,ATTRIBUTE,path,NOTEXPIRED,"%s",name); 123 | return -EACCES; 124 | } 125 | 126 | logger_log(__func__,path,INFO, "Try to remove extended attribute %s",name); 127 | returnStatus = lremovexattr(convertedPath, name); 128 | if (returnStatus < 0) 129 | { 130 | returnStatus = logger_errno(__func__, path,"Failed to remove extended attribute"); 131 | logger_auditFailure(DELETE,ATTRIBUTE,path,NOK,"%s",name); 132 | } 133 | else 134 | { 135 | logger_auditSuccess(DELETE,ATTRIBUTE,path,OK,"%s",name); 136 | } 137 | return returnStatus; 138 | } 139 | 140 | 141 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #define FUSE_USE_VERSION 29 2 | 3 | #ifndef HAVE_SETXATTR 4 | #define HAVE_SETXATTR 1 5 | #endif 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include "logger.h" 26 | #include "retention.h" 27 | #include "directory.h" 28 | #include "utils.h" 29 | #include "attribute.h" 30 | #include "link.h" 31 | #include "shared.h" 32 | #include "file.h" 33 | #include "loop.h" 34 | #include "context.h" 35 | #include "config.h" 36 | 37 | Loop* loopDevice; 38 | 39 | 40 | static void freeResources() 41 | { 42 | logger_log(__func__, NAFILE,INFO,"Releasing resources"); 43 | 44 | if (loopDevice!=NULL) 45 | { 46 | loop_umount(loopDevice); 47 | loop_free(loopDevice); 48 | } 49 | 50 | config_free(); 51 | logger_free(); 52 | 53 | } 54 | 55 | static void *WORM_init(struct fuse_conn_info *conn) 56 | { 57 | logger_enter(__func__,NAFILE); 58 | 59 | logger_log(__func__,NAFILE,DEBUG,"ID is %i",config.ID); 60 | logger_log(__func__,NAFILE,DEBUG,"Mount path is %s",config.mountPath); 61 | logger_log(__func__,NAFILE,DEBUG,"Repository type is %i",config.repositoryType); 62 | logger_log(__func__,NAFILE,DEBUG,"Repository path is %s",config.repositoryPath); 63 | logger_log(__func__,NAFILE,DEBUG,"Repository file is %s",config.repositoryFile); 64 | logger_log(__func__,NAFILE,DEBUG,"DefaultRetention is %i",config.defaultRetention); 65 | logger_log(__func__,NAFILE,DEBUG,"Audit mode is %i",config.auditMode); 66 | logger_log(__func__,NAFILE,DEBUG,"LockDelay is %i",config.lockDelay); 67 | logger_log(__func__,NAFILE,DEBUG,"AutoLock is %i",config.autoLock); 68 | 69 | } 70 | 71 | 72 | 73 | static void WORM_destroy(void *userdata) 74 | { 75 | logger_enter(__func__,NAFILE); 76 | freeResources(); 77 | } 78 | 79 | 80 | 81 | static struct fuse_operations WORM_oper = 82 | { 83 | .readlink = WORM_readlink, 84 | .getdir = NULL,// no .getdir -- that's deprecated 85 | //.mknod = WORM_mknod, 86 | .mkdir = WORM_mkdir, 87 | .unlink = WORM_unlink, 88 | .rmdir = WORM_rmdir, 89 | .symlink = WORM_symlink, 90 | .rename = WORM_rename, 91 | .link = WORM_link, 92 | .chmod = WORM_chmod, 93 | .chown = WORM_chown, 94 | .truncate = WORM_truncate, 95 | .utime = WORM_utime, 96 | .open = WORM_open, 97 | .read = WORM_read, 98 | .write = WORM_write, 99 | .statfs = WORM_statfs, 100 | //.flush = WORM_flush, 101 | .release = WORM_release, 102 | .fsync = WORM_fsync, 103 | .getattr = WORM_getattr, 104 | .getxattr = WORM_getxattr, 105 | .listxattr = WORM_listxattr, 106 | .setxattr = WORM_setxattr, 107 | .removexattr = WORM_removexattr, 108 | .opendir = WORM_opendir, 109 | .readdir = WORM_readdir, 110 | .releasedir = WORM_releasedir, 111 | //.fsyncdir = WORM_fsyncdir, 112 | .init = WORM_init, 113 | .destroy = WORM_destroy, 114 | .access = WORM_access, 115 | .create = WORM_create, 116 | .ftruncate = WORM_ftruncate, 117 | .fgetattr = WORM_fgetattr 118 | }; 119 | 120 | 121 | 122 | 123 | int main(int argc, char *argv[]) 124 | { 125 | char* newargs[128]; 126 | int result; 127 | 128 | 129 | if (argc==128) 130 | { 131 | fprintf(stderr,"Too many arguments\n"); 132 | return -1; 133 | } 134 | 135 | if ((argc ==2) && (strcmp(argv[1],"-h")==0)) 136 | { 137 | printf("Current VERSION: %s\n",_VERSION); 138 | printf("Usage: WORM options\n"); 139 | return 0; 140 | } 141 | 142 | if (config_init()!=0) 143 | { 144 | fprintf(stderr,"Cannot initialize configuration\n"); 145 | freeResources(); 146 | return -1; 147 | } 148 | if (logger_init()!=0) 149 | { 150 | fprintf(stderr,"Cannot initialize logger\n"); 151 | freeResources(); 152 | return -1; 153 | } 154 | if (config_loadFilters()!=0) 155 | { 156 | fprintf(stderr,"Cannot initialize filters\n"); 157 | freeResources(); 158 | return -1; 159 | } 160 | 161 | if (config.repositoryType==1) 162 | { 163 | 164 | loopDevice=loop_create(config.repositoryFile,config.repositoryPath); 165 | if (loopDevice==NULL) 166 | { 167 | fprintf(stderr,"Cannot initialize loop device\n"); 168 | freeResources(); 169 | return -1; 170 | } 171 | if (loop_mount(loopDevice)!=0) 172 | { 173 | fprintf(stderr,"Cannot mount loop device\n"); 174 | freeResources(); 175 | return -1; 176 | } 177 | } 178 | 179 | 180 | memcpy(newargs, argv, argc*sizeof(char*)); 181 | newargs[argc]=config.mountPath; 182 | 183 | //printf("Calling fuse main...\n"); 184 | result = fuse_main(argc+1, newargs, &WORM_oper, NULL); 185 | 186 | if (result!=0) fprintf(stderr,"fuse main returned an error %i\n",result); 187 | 188 | return result; 189 | } 190 | -------------------------------------------------------------------------------- /logger.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include "logger.h" 19 | #include "retention.h" 20 | #include "context.h" 21 | #include "config.h" 22 | 23 | const char* DEBUG="DEBUG"; 24 | const char* INFO="INFO"; 25 | const char* WARN="WARN"; 26 | const char* ERROR="ERROR"; 27 | 28 | const char* SUCCESS="SUCCESS"; 29 | const char* FAILURE="FAILURE"; 30 | const char* OK="OK"; 31 | const char* NOK="NOK"; 32 | const char* NOTEXPIRED="NOTEXPIRED"; 33 | 34 | const char* CREATE="CREATE"; 35 | const char* DELETE="DELETE"; 36 | const char* UPDATE="UPDATE"; 37 | 38 | const char* cFILE="FILE"; 39 | const char* DIRECTORY="DIRECTORY"; 40 | const char* EXPIRATION="EXPIRATION"; 41 | const char* LOCK="LOCK"; 42 | const char* RETENTION="RETENTION"; 43 | const char* OWNER="OWNER"; 44 | const char* MODE="MODE"; 45 | const char* TIME="TIME"; 46 | const char* LOCATION="LOCATION"; 47 | const char* ATTRIBUTE="ATTRIBUTE"; 48 | 49 | static Logger logger= {.auditQueue=(mqd_t)-1}; 50 | 51 | 52 | void (*writeAudit)(const char*,const char*,const char* ,const char* ,const char*); 53 | 54 | 55 | void logger_enter(const char* funcName,const char* path) 56 | { 57 | logger_log(funcName,path,DEBUG,"Entering..." ); 58 | } 59 | 60 | void logger_log(const char* funcName,const char* path,const char* errorLevel,const char *format, ...) 61 | { 62 | char buffer[1024]; 63 | va_list ap; 64 | 65 | va_start(ap, format); 66 | vsnprintf(buffer,1024, format, ap); 67 | va_end(ap); 68 | 69 | log_write(logger.logFile,"%i|%s|%s|%s|%s",config.ID,errorLevel,funcName,path,buffer); 70 | } 71 | 72 | int logger_errno(const char* funcName,const char* path,const char* message) 73 | { 74 | logger_log(funcName,path, ERROR,"%s: %s", message, strerror(errno)); 75 | return -errno; 76 | } 77 | 78 | 79 | 80 | 81 | void writeAuditToNull(const char *action,const char *entity,const char* result,const char* path,const char *value) 82 | { 83 | 84 | } 85 | 86 | void writeAuditToQueue(const char *action,const char *entity,const char* result,const char* path,const char *value) 87 | { 88 | log_write(logger.logFile,"TEST QUEUE"); 89 | if (logger.auditQueue == (mqd_t) -1) 90 | { 91 | log_write(logger.logFile,"QUEUE invalid"); 92 | } 93 | mq_send(logger.auditQueue, "TEST", 5, 1); 94 | } 95 | 96 | void writeAuditToFile(const char *action,const char *entity,const char* result,const char* path,const char *value) 97 | { 98 | struct fuse_context *fuseContext; 99 | 100 | fuseContext=fuse_get_context(); 101 | 102 | log_write(logger.auditFile,"%i|%s|%s|%s|%s|%s|%i|%i",config.ID,action,entity,result,path,value,fuseContext->uid,fuseContext->gid); 103 | } 104 | 105 | 106 | void logger_auditSuccess(const char *action,const char *entity,const char* path,const char *format, ...) 107 | { 108 | va_list ap; 109 | char message[PATH_MAX]; 110 | 111 | va_start(ap, format); 112 | vsnprintf(message,PATH_MAX,format,ap); 113 | va_end(ap); 114 | 115 | writeAudit(action,entity,SUCCESS,path,message); 116 | } 117 | void logger_auditFailure(const char *action,const char *entity,const char* path,const char *format, ...) 118 | { 119 | va_list ap; 120 | char message[PATH_MAX]; 121 | 122 | va_start(ap, format); 123 | vsnprintf(message,PATH_MAX,format,ap); 124 | va_end(ap); 125 | 126 | writeAudit(action,entity,FAILURE,path,message); 127 | } 128 | 129 | int logger_init() 130 | { 131 | printf("Initializing logs...\n"); 132 | logger.logFile=log_open("/var/log/worm.log",SIGUSR1); 133 | if (logger.logFile==NULL) 134 | { 135 | fprintf(stderr,"Failed to initialize log file\n"); 136 | return -1; 137 | } 138 | 139 | printf("Initializing callbacks...\n"); 140 | switch(config.auditMode) 141 | { 142 | case 1: 143 | logger.auditFile=log_open("/var/log/worm_audit.log",SIGUSR2); 144 | if (logger.auditFile==NULL) 145 | { 146 | fprintf(stderr,"Failed to initialize audit file"); 147 | return -1; 148 | } 149 | writeAudit=&writeAuditToFile; 150 | break; 151 | case 2: 152 | logger.auditQueue = mq_open("/worm_audit", O_WRONLY | O_CREAT | O_NONBLOCK, 0600, NULL); 153 | if (logger.auditQueue == (mqd_t) -1) 154 | { 155 | fprintf(stderr,"Failed to initialize audit queue"); 156 | return -1; 157 | } 158 | writeAudit=&writeAuditToQueue; 159 | break; 160 | default: 161 | writeAudit=&writeAuditToNull; 162 | break; 163 | } 164 | 165 | return 0; 166 | 167 | } 168 | 169 | void logger_free() 170 | { 171 | if (logger.logFile!=NULL) log_close(logger.logFile); 172 | if (logger.auditFile!=NULL) log_close(logger.auditFile); 173 | if (logger.auditQueue!=(mqd_t)-1) 174 | { 175 | mq_close(logger.auditQueue); 176 | mq_unlink ("/worm_audit"); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /shared.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "shared.h" 13 | #include "logger.h" 14 | #include "utils.h" 15 | #include "retention.h" 16 | #include "context.h" 17 | #include "config.h" 18 | 19 | 20 | 21 | 22 | // both path and newpath are fs-relative 23 | int WORM_rename(const char *path, const char *newpath) 24 | { 25 | int returnStatus = 0; 26 | char convertedPath[PATH_MAX]; 27 | char newConvertedPath[PATH_MAX]; 28 | 29 | logger_enter(__func__,path); 30 | convertPath(convertedPath, path); 31 | convertPath(newConvertedPath, newpath); 32 | 33 | logger_log(__func__,path,INFO,"Checking expiration"); 34 | if (isExpired(__func__,convertedPath)==0) 35 | { 36 | logger_log(__func__,path,WARN, "Media is not expired"); 37 | logger_auditFailure(UPDATE,LOCATION,path,NOTEXPIRED); 38 | return -EACCES; 39 | } 40 | 41 | logger_log(__func__,path,INFO,"Try to rename"); 42 | returnStatus = rename(convertedPath, newConvertedPath); 43 | if (returnStatus != 0) 44 | { 45 | returnStatus = logger_errno(__func__, path,"Failed to rename"); 46 | logger_auditFailure(UPDATE,LOCATION,path,NOK); 47 | } 48 | else 49 | { 50 | logger_auditSuccess(UPDATE,LOCATION,path,newpath); 51 | } 52 | 53 | return returnStatus; 54 | } 55 | 56 | 57 | int WORM_chown(const char *path, uid_t uid, gid_t gid) 58 | { 59 | int returnStatus = 0; 60 | char convertedPath[PATH_MAX]; 61 | 62 | logger_enter(__func__,path); 63 | convertPath(convertedPath, path); 64 | 65 | logger_log(__func__,path,INFO,"Checking expiration"); 66 | if (isExpired(__func__,convertedPath)==0) 67 | { 68 | logger_log(__func__,path,WARN, "Media is not expired"); 69 | logger_auditFailure(UPDATE,OWNER,path,NOTEXPIRED); 70 | return -EACCES; 71 | } 72 | 73 | logger_log(__func__,path,INFO,"Try to change owner"); 74 | returnStatus = chown(convertedPath, uid, gid); 75 | if (returnStatus != 0) 76 | { 77 | returnStatus = logger_errno(__func__, path,"Failed to change ower"); 78 | logger_auditFailure(UPDATE,OWNER,path,NOK); 79 | } 80 | else 81 | { 82 | logger_auditSuccess(UPDATE,OWNER,path,OK); 83 | } 84 | 85 | return returnStatus; 86 | } 87 | 88 | /** Change the permission bits of a file */ 89 | int WORM_chmod(const char *path, mode_t mode) 90 | { 91 | int returnStatus = 0; 92 | char convertedPath[PATH_MAX]; 93 | 94 | logger_enter(__func__,path); 95 | convertPath(convertedPath, path); 96 | 97 | logger_log(__func__,path,INFO,"Checking expiration"); 98 | if (isExpired(__func__,convertedPath)==0) 99 | { 100 | logger_log(__func__,path,WARN, "Media is not expired"); 101 | logger_auditFailure(UPDATE,MODE,path,NOTEXPIRED,"%i",mode); 102 | return -EACCES; 103 | } 104 | 105 | logger_log(__func__,path,INFO,"Try to change access mode"); 106 | returnStatus = chmod(convertedPath, mode); 107 | if (returnStatus != 0) 108 | { 109 | returnStatus = logger_errno(__func__, path,"Failed to change access mode"); 110 | logger_auditFailure(UPDATE,MODE,path,NOK,"%i",mode); 111 | } 112 | else 113 | { 114 | logger_auditSuccess(UPDATE,MODE,path,OK,"%i",mode); 115 | } 116 | return returnStatus; 117 | } 118 | 119 | 120 | int WORM_utime(const char *path, struct utimbuf *ubuf) 121 | { 122 | int returnStatus = 0; 123 | char convertedPath[PATH_MAX]; 124 | char accessTime[20]; 125 | time_t expirationDate; 126 | 127 | logger_enter(__func__,path); 128 | convertPath(convertedPath, path); 129 | 130 | 131 | if ((isReadOnly(convertedPath)>0) && (config.autoLock==0)) 132 | { 133 | convertTime(ubuf->actime,accessTime,20); 134 | expirationDate=getExpirationDate(__func__,convertedPath); 135 | if (ubuf->actime > expirationDate) 136 | { 137 | logger_log(__func__,path,INFO,"File is read only setting expiration date %s on file",accessTime); 138 | setExpirationDateExplicit(__func__,path,convertedPath,ubuf->actime); 139 | return 0; 140 | } 141 | else 142 | { 143 | logger_log(__func__,path,WARN,"Cannot set lower expiration date %s on file",accessTime); 144 | logger_auditFailure(UPDATE,TIME,path,NOTEXPIRED); 145 | return -EACCES; 146 | } 147 | } 148 | 149 | if (isExpired(__func__,convertedPath)==0) 150 | { 151 | logger_log(__func__,path,WARN,"Media is not expired"); 152 | logger_auditFailure(UPDATE,TIME,path,NOTEXPIRED); 153 | return -EACCES; 154 | } 155 | 156 | 157 | 158 | logger_log(__func__,path,INFO,"Try to change time value"); 159 | returnStatus = utime(convertedPath, ubuf); 160 | if (returnStatus != 0) 161 | { 162 | returnStatus = logger_errno(__func__, path,"Failed to change time value"); 163 | logger_auditFailure(UPDATE,TIME,path,NOK); 164 | } 165 | else 166 | { 167 | logger_auditSuccess(UPDATE,TIME,path,OK); 168 | } 169 | 170 | return returnStatus; 171 | } 172 | 173 | int WORM_statfs(const char *path, struct statvfs *statv) 174 | { 175 | int returnStatus = 0; 176 | char convertedPath[PATH_MAX]; 177 | 178 | logger_enter(__func__,path); 179 | convertPath(convertedPath, path); 180 | 181 | logger_log(__func__,path,INFO,"Try to get file stats"); 182 | returnStatus = statvfs(convertedPath, statv); 183 | if (returnStatus != 0) returnStatus = logger_errno(__func__, path,"Failed to get file stats"); 184 | 185 | 186 | return returnStatus; 187 | } 188 | 189 | 190 | int WORM_fsync(const char *path, int datasync, struct fuse_file_info *fi) 191 | { 192 | int returnStatus = 0; 193 | 194 | logger_enter(__func__,path); 195 | 196 | logger_log(__func__,path,INFO,"Try to fsync from file_info"); 197 | if (datasync) returnStatus = fdatasync(fi->fh); 198 | else returnStatus = fsync(fi->fh); 199 | 200 | if (returnStatus != 0) returnStatus = logger_errno(__func__, path,"Failed to fsync from file_info"); 201 | 202 | return returnStatus; 203 | } 204 | 205 | 206 | int WORM_access(const char *path, int mask) 207 | { 208 | int returnStatus = 0; 209 | char convertedPath[PATH_MAX]; 210 | 211 | logger_enter(__func__,path); 212 | convertPath(convertedPath, path); 213 | 214 | logger_log(__func__,path,INFO,"Try to get access"); 215 | returnStatus = access(convertedPath, mask); 216 | if (returnStatus != 0) 217 | { 218 | returnStatus = logger_errno(__func__, path,"Failed to get access"); 219 | } 220 | else returnStatus=getReadOnlyMode(__func__, convertedPath,returnStatus); 221 | 222 | return returnStatus; 223 | } 224 | 225 | -------------------------------------------------------------------------------- /file.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include "file.h" 17 | #include "logger.h" 18 | #include "utils.h" 19 | #include "retention.h" 20 | #include "context.h" 21 | #include "config.h" 22 | 23 | int WORM_open(const char *path, struct fuse_file_info *fi) 24 | { 25 | int returnStatus = 0; 26 | char convertedPath[PATH_MAX]; 27 | int writeAccess; 28 | int create; 29 | 30 | logger_enter(__func__,path); 31 | convertPath(convertedPath, path); 32 | 33 | writeAccess=(fi->flags & (O_WRONLY | O_RDWR | O_CREAT | O_TRUNC)); 34 | //writeLog(__func__,path,DEBUG,"Open flags are %i",fi->flags); 35 | 36 | create=(fi->flags & O_CREAT ); 37 | 38 | logger_log(__func__,path,INFO,"Checking expiration"); 39 | if ( (writeAccess!=0) && (isExpired(__func__,convertedPath)==0)) 40 | { 41 | logger_log(__func__,path,WARN, "Media is not expired"); 42 | logger_auditFailure(UPDATE,cFILE,path,NOTEXPIRED); 43 | return -EACCES; 44 | } 45 | 46 | 47 | logger_log(__func__, path,INFO, "Try to open file"); 48 | fi->fh = open(convertedPath, fi->flags); 49 | if (fi->fh == 0) 50 | { 51 | returnStatus = logger_errno(__func__, path,"Failed to open file"); 52 | logger_auditFailure(UPDATE,cFILE,path,NOK); 53 | } 54 | else 55 | { 56 | if (create!=0) 57 | { 58 | logger_log(__func__, path,INFO, "Settng retention"); 59 | setRetention(__func__,path,convertedPath); 60 | logger_auditSuccess(UPDATE,cFILE,path,OK); 61 | } 62 | } 63 | 64 | return returnStatus; 65 | } 66 | 67 | int WORM_release(const char *path, struct fuse_file_info *fi) 68 | { 69 | int returnStatus = 0; 70 | char convertedPath[PATH_MAX]; 71 | 72 | logger_enter(__func__,path); 73 | convertPath(convertedPath, path); 74 | 75 | logger_log(__func__, path,INFO, "Try to close file"); 76 | returnStatus = close(fi->fh); 77 | if (returnStatus != 0) returnStatus = logger_errno(__func__, path,"Failed to close file"); 78 | 79 | 80 | return returnStatus; 81 | } 82 | 83 | 84 | int WORM_read(const char *path, char *buf, size_t size, off_t offset,struct fuse_file_info *fi) 85 | { 86 | int returnStatus = 0; 87 | 88 | logger_enter(__func__,path); 89 | 90 | logger_log(__func__, path,INFO, "Try to read file from file_info"); 91 | returnStatus = pread(fi->fh, buf, size, offset); 92 | if (returnStatus < 0) returnStatus = logger_errno(__func__, path,"Failed to read file from file_info"); 93 | 94 | return returnStatus; 95 | } 96 | 97 | 98 | /** Change the size of a file */ 99 | int WORM_truncate(const char *path, off_t newsize) 100 | { 101 | int returnStatus = 0; 102 | char convertedPath[PATH_MAX]; 103 | 104 | logger_enter(__func__,path); 105 | convertPath(convertedPath, path); 106 | 107 | logger_log(__func__,path,INFO,"Checking expiration"); 108 | if (isExpired(__func__,convertedPath)==0) 109 | { 110 | logger_log(__func__,path,WARN, "Media is not expired"); 111 | logger_auditFailure(UPDATE,cFILE,path,NOTEXPIRED); 112 | return -EACCES; 113 | } 114 | 115 | logger_log(__func__, path,INFO, "Try to truncate file"); 116 | returnStatus = truncate(convertedPath, newsize); 117 | if (returnStatus != 0) 118 | { 119 | returnStatus = logger_errno(__func__, path,"Failed to truncate file"); 120 | logger_auditFailure(UPDATE,cFILE,path,NOK); 121 | } 122 | else 123 | { 124 | logger_auditSuccess(UPDATE,cFILE,path,OK); 125 | } 126 | 127 | return returnStatus; 128 | } 129 | 130 | int WORM_ftruncate(const char *path, off_t offset, struct fuse_file_info *fi) 131 | { 132 | int returnStatus = 0; 133 | char convertedPath[PATH_MAX]; 134 | 135 | logger_enter(__func__,path); 136 | convertPath(convertedPath, path); 137 | 138 | logger_log(__func__,path,INFO,"Checking expiration"); 139 | if (isExpired(__func__,convertedPath)==0) 140 | { 141 | logger_log(__func__,path,WARN, "Media is not expired"); 142 | logger_auditFailure(UPDATE,cFILE,path,NOTEXPIRED); 143 | return -EACCES; 144 | } 145 | 146 | logger_log(__func__, path,INFO, "Try to truncate file from file_info"); 147 | returnStatus = ftruncate(fi->fh, offset); 148 | if (returnStatus != 0) 149 | { 150 | returnStatus = logger_errno(__func__, path,"Failed to truncate file from file_info"); 151 | logger_auditFailure(UPDATE,cFILE,path,NOK); 152 | } 153 | else 154 | { 155 | logger_auditSuccess(UPDATE,cFILE,path,OK); 156 | } 157 | 158 | 159 | return returnStatus; 160 | } 161 | 162 | int WORM_write(const char *path, const char *buf, size_t size, off_t offset,struct fuse_file_info *fi) 163 | { 164 | int returnStatus = 0; 165 | 166 | logger_enter(__func__,path); 167 | 168 | logger_log(__func__, path,INFO, "Try to write file from file_info"); 169 | returnStatus = pwrite(fi->fh, buf, size, offset); 170 | if (returnStatus < 0) 171 | { 172 | returnStatus = logger_errno(__func__, path,"Failed to write file from file_info"); 173 | logger_auditFailure(UPDATE,cFILE,path,NOK); 174 | } 175 | 176 | return returnStatus; 177 | } 178 | 179 | 180 | /*int WORM_flush(const char *path, struct fuse_file_info *fi) 181 | { 182 | int returnStatus = 0; 183 | 184 | writeLog("flush path=%s, fi=0x%08x", path, fi); 185 | returnStatus=fflush((FILE*)fi->fh); 186 | if (returnStatus < 0) 187 | { 188 | returnStatus = WriteError("fflush"); 189 | } 190 | 191 | return returnStatus; 192 | }*/ 193 | 194 | 195 | 196 | int WORM_create(const char *path, mode_t mode, struct fuse_file_info *fi) 197 | { 198 | int returnStatus = 0; 199 | char convertedPath[PATH_MAX]; 200 | 201 | logger_enter(__func__,path); 202 | convertPath(convertedPath, path); 203 | 204 | logger_log(__func__, path,INFO, "Try to create file"); 205 | fi->fh = creat(convertedPath, mode); 206 | if (fi->fh == 0) 207 | { 208 | returnStatus = logger_errno(__func__, path,"Failed to create file"); 209 | logger_auditFailure(CREATE,cFILE,path,NOK); 210 | } 211 | else 212 | { 213 | logger_auditSuccess(CREATE,cFILE,path,OK); 214 | logger_log(__func__, path,INFO, "Settng retention and owner"); 215 | setRealOwnerID(__func__, convertedPath); 216 | setRetention(__func__,path,convertedPath); 217 | if (config.autoLock!=0) 218 | { 219 | logger_log(__func__, path,INFO, "Auto lock is on, setting expiration date"); 220 | setExpirationDate(__func__,path,convertedPath); 221 | } 222 | } 223 | 224 | return returnStatus; 225 | } 226 | 227 | 228 | 229 | 230 | -------------------------------------------------------------------------------- /retention.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include "retention.h" 20 | #include "logger.h" 21 | #include "utils.h" 22 | #include "context.h" 23 | #include "filter.h" 24 | #include "config.h" 25 | 26 | 27 | 28 | unsigned short getParentRetention(const char* funcName,const char *path) 29 | { 30 | char pathCopy[PATH_MAX]; 31 | char* parentPath; 32 | 33 | logger_enter(__func__,path); 34 | strcpy(pathCopy,path); 35 | 36 | logger_log(funcName, path,DEBUG,"Try to get parent directory"); 37 | parentPath=dirname(pathCopy); 38 | if (parentPath==NULL) 39 | { 40 | logger_log(funcName, path,ERROR,"Cannot find parent directory, will use default retention"); 41 | return config.defaultRetention; 42 | } 43 | 44 | return getRetention(funcName,parentPath); 45 | } 46 | 47 | unsigned short getRetention(const char* funcName,const char *path) 48 | { 49 | unsigned short retention; 50 | int status; 51 | 52 | logger_enter(__func__,path); 53 | 54 | logger_log(funcName, path,DEBUG,"Try to get extended attribute user.Retention"); 55 | status = lgetxattr(path, "user.Retention", &retention, sizeof(retention)); 56 | if (status<0) 57 | { 58 | logger_errno(funcName, path,"Failed to get extended attribute user.Retention"); 59 | return config.defaultRetention; 60 | } 61 | 62 | //writeLog(DEBUG,"Retention value is %i",retention); 63 | return retention; 64 | } 65 | 66 | void setRetention(const char* funcName,const char *path,const char *convertedPath) 67 | { 68 | int status; 69 | unsigned short retention; 70 | int filterIndex; 71 | 72 | logger_enter(__func__,path); 73 | 74 | retention = filters_getRetention(config.filters,convertedPath,&filterIndex); 75 | if (retention==65535) 76 | { 77 | logger_log(funcName, path,DEBUG,"Path doesn't match any retention filter, trying to apply parent folder's retention"); 78 | retention=getParentRetention(funcName,convertedPath); 79 | } 80 | else 81 | { 82 | logger_log(funcName, path,DEBUG,"Path matches retention filter %i, retention of %i day(s) will be applied",filterIndex,retention); 83 | } 84 | 85 | logger_log(funcName, path,DEBUG,"Try to set extended attribute user.Retention"); 86 | status = lsetxattr(convertedPath, "user.Retention", &retention, sizeof(retention),0); 87 | if (status<0) 88 | { 89 | logger_errno(funcName, path,"Failed to set extended attribute user.Retention"); 90 | logger_auditFailure(UPDATE,RETENTION,path,"%i",retention); 91 | } 92 | else 93 | { 94 | logger_auditSuccess(UPDATE,RETENTION,path,"%i",retention); 95 | } 96 | 97 | 98 | } 99 | 100 | time_t calcExpirationDate(const char* funcName,unsigned short retention) 101 | { 102 | time_t now; 103 | time_t expiration; 104 | 105 | //logEnter(__func__,path); 106 | 107 | now=time(NULL); 108 | 109 | expiration=now+24*3600*retention; 110 | 111 | return expiration; 112 | 113 | } 114 | 115 | time_t getExpirationDate(const char* funcName,const char *path) 116 | { 117 | time_t expiration; 118 | int status; 119 | int64_t value; 120 | //long long tmp; 121 | 122 | logger_enter(__func__,path); 123 | 124 | logger_log(funcName, path,DEBUG,"Try to get extended attribute user.ExpirationDate"); 125 | status = lgetxattr(path, "user.ExpirationDate", &value, sizeof(value)); 126 | if (status<0) return 0; 127 | /*{ 128 | returnStatus = writeErrorNumber(funcName, path); 129 | return 0; 130 | }*/ 131 | expiration=value; //be sure to read 64 bits 132 | 133 | //writeLog("Expiration date/time is %s",ctime(&expiration)); 134 | return expiration; 135 | } 136 | time_t getLockDate(const char* funcName,const char *path) 137 | { 138 | time_t lock; 139 | int status; 140 | int64_t value; 141 | //long long tmp; 142 | 143 | logger_enter(__func__,path); 144 | 145 | logger_log(funcName, path,DEBUG,"Try to get extended attribute user.LockDate"); 146 | status = lgetxattr(path, "user.LockDate", &value, sizeof(value)); 147 | if (status<0) return 0; 148 | /*{ 149 | returnStatus = writeErrorNumber(funcName, path); 150 | return 0; 151 | }*/ 152 | lock=value; //be sure to read 64 bits 153 | 154 | //writeLog("Expiration date/time is %s",ctime(&expiration)); 155 | return lock; 156 | } 157 | 158 | void setExpirationDate(const char* funcName,const char *path,const char *convertedPath) 159 | { 160 | int status; 161 | //long long tmp; 162 | unsigned short retention; 163 | time_t expirationDate; 164 | 165 | //logEnter(__func__,path); 166 | 167 | retention=getRetention(funcName,convertedPath); 168 | expirationDate=calcExpirationDate(funcName,retention); 169 | 170 | setExpirationDateExplicit(funcName,path,convertedPath,expirationDate); 171 | 172 | } 173 | void setExpirationDateExplicit(const char* funcName,const char *path,const char *convertedPath,time_t expirationDate) 174 | { 175 | int status; 176 | //long long tmp; 177 | //unsigned short retention; 178 | char buffer[20]; 179 | int64_t value; 180 | 181 | logger_enter(__func__,path); 182 | 183 | value=expirationDate; // be sure to write 64 bits 184 | 185 | setLockDate(funcName,path,convertedPath); 186 | 187 | convertTime(expirationDate,buffer,20); 188 | logger_log(funcName, path,DEBUG,"Try to set extended attribute user.ExpirationDate"); 189 | status = lsetxattr(convertedPath, "user.ExpirationDate", &value,sizeof(value),0); 190 | if (status<0) 191 | { 192 | logger_errno(funcName, path,"Failed to set extended attribute user.ExpirationDate"); 193 | logger_auditFailure(UPDATE,EXPIRATION,path,"%" PRId64,value); 194 | } 195 | else 196 | { 197 | logger_auditSuccess(UPDATE,EXPIRATION,path,"%" PRId64, value); 198 | } 199 | } 200 | 201 | void setLockDate(const char* funcName,const char *path,const char *convertedPath) 202 | { 203 | int status; 204 | //long long tmp; 205 | int64_t value; 206 | time_t LockDate; 207 | 208 | logger_enter(__func__,path); 209 | 210 | LockDate=time(NULL); 211 | 212 | value=LockDate; // be sure to write 64 bits 213 | 214 | logger_log(funcName, path,DEBUG,"Try to set extended attribute user.LockDate"); 215 | status = lsetxattr(convertedPath, "user.LockDate", &value,sizeof(value),0); 216 | if (status<0) 217 | { 218 | logger_errno(funcName, path,"Failed to set extended attribute user.LockDate"); 219 | logger_auditFailure(UPDATE,LOCK,path,"%" PRId64,value); 220 | } 221 | else 222 | { 223 | logger_auditSuccess(UPDATE,LOCK,path,"%" PRId64, value); 224 | } 225 | } 226 | 227 | int isExpired(const char* funcName,const char *path) 228 | { 229 | time_t expiration; 230 | time_t now; 231 | time_t lock; 232 | 233 | logger_enter(__func__,path); 234 | 235 | now=time(NULL); 236 | 237 | lock=getLockDate(funcName,path); 238 | if ((config.autoLock!=0) && (now) ,0); 62 | assert( "Close file" ,close($file),0); 63 | } 64 | 65 | assert("Get file attributes", defined(stat($path)),0); 66 | #assert("Check file access", defined($result= -W $path),0); does not call access :( 67 | assert("Get extended attribute",defined(getxattr($path, 'user.Retention')) ,0); 68 | assert("List extended attributes",defined(listxattr($path)) ,0); 69 | assert("Get file status info",defined(stat($path)) ,0); 70 | assert("Get file system info",defined(statvfs($path)) ,0); 71 | 72 | 73 | } 74 | 75 | sub testWriteOnly { 76 | my ($rootDir,$name,$shouldFail) = @_; 77 | my $path; 78 | my $file; 79 | 80 | $path="$rootDir/$name"; 81 | 82 | if ( -d $path) 83 | { 84 | assert( "Overwrite directory" ,mkdir($path),1); 85 | } 86 | else 87 | { 88 | assert( "Overwrite file", open($file,'>',$path) && close($file),$shouldFail ); 89 | assert( "Append file", open($file,'>>',$path) && (print $file 'test') && close($file),$shouldFail ); 90 | assert( "Truncate file", truncate($path,0),$shouldFail ); 91 | } 92 | 93 | assert( "Rename file" ,rename($path,"$rootDir/newName") && rename("$rootDir/newName",$path),$shouldFail); 94 | 95 | assert("Set extended attribute", setxattr($path,"user.Test",1234),$shouldFail); 96 | assert("Remove extended attribute", removexattr($path,"user.Retention"),$shouldFail); 97 | assert("Change access/modification time (decrease)", utime(0,0,$path),$shouldFail); 98 | assert("Change access/modification time (increase)", utime(time + 10 * 24 * 60 * 60 ,0,$path),$shouldFail & $autoLock); 99 | 100 | 101 | 102 | assert ("Change mod" , chmod("777",$path),$shouldFail); 103 | assert ("Change own" , chown($<,$(,$path),$shouldFail); 104 | 105 | if ( -d $path) 106 | { 107 | assert( "Remove directory" ,rmdir($path),$shouldFail); 108 | } 109 | else 110 | { 111 | assert( "Remove file" ,unlink($path),$shouldFail); 112 | } 113 | 114 | 115 | } 116 | 117 | 118 | sub testDirectoryWithoutLock { 119 | my $dir; 120 | my ($rootDir) = @_; 121 | 122 | assert( "Create directory" ,mkdir("$rootDir/Directory"),0); 123 | testReadOnly($rootDir,'Directory'); 124 | testWriteOnly($rootDir,'Directory',0); 125 | } 126 | 127 | sub testDirectoryWithLock { 128 | my $dir; 129 | my ($rootDir) = @_; 130 | 131 | assert( "Create directory" ,mkdir("$rootDir/Directory"),0); 132 | if ($autoLock) 133 | { 134 | sleep(2); 135 | } 136 | else 137 | { 138 | lock("$rootDir/Directory",1); 139 | } 140 | 141 | testReadOnly($rootDir,'Directory'); 142 | testWriteOnly($rootDir,'Directory',1); 143 | } 144 | 145 | sub testFileWithoutLock { 146 | my $file; 147 | my ($rootDir,$ret) = @_; 148 | 149 | assert( "Create file", open($file,'>',"$rootDir/File.rt$ret") && (print $file 'test') && close($file),0 ); 150 | testReadOnly($rootDir,"File.rt$ret"); 151 | testWriteOnly($rootDir,"File.rt$ret",0); 152 | } 153 | 154 | sub testFileWithLock { 155 | my $file; 156 | my ($rootDir,$ret) = @_; 157 | 158 | assert( "Create file", open($file,'>',"$rootDir/File.rt$ret") && (print $file 'test') && close($file),0 ); 159 | if ($autoLock) 160 | { 161 | sleep(2); 162 | } 163 | else 164 | { 165 | lock("$rootDir/File.rt$ret",$ret); 166 | } 167 | 168 | testReadOnly($rootDir,"File.rt$ret"); 169 | testWriteOnly($rootDir,"File.rt$ret",$ret); 170 | } 171 | 172 | sub testLinkWithoutLock { 173 | my $file; 174 | my ($rootDir,$ret) = @_; 175 | 176 | assert( "Create file", open($file,'>',"$rootDir/LinkedFile.rt$ret") && (print $file 'test') && close($file),0 ); 177 | assert( "Create link to file",link("$rootDir/LinkedFile.rt$ret","$rootDir/Link$ret.rt$ret") ,0); 178 | 179 | testReadOnly($rootDir,"Link$ret.rt$ret"); 180 | testWriteOnly($rootDir,"Link$ret.rt$ret",0); 181 | 182 | assert( "Clean file", unlink("$rootDir/LinkedFile.rt$ret"),0); 183 | 184 | } 185 | sub testLinkWithLock { 186 | my $file; 187 | my ($rootDir,$ret) = @_; 188 | 189 | assert( "Create file", open($file,'>',"$rootDir/LinkedFile.rt$ret") && (print $file 'test') && close($file),0 ); 190 | assert( "Create link to file",link("$rootDir/LinkedFile.rt$ret","$rootDir/Link$ret.rt$ret"),0 ); 191 | if ($autoLock) 192 | { 193 | sleep(2); 194 | } 195 | else 196 | { 197 | lock("$rootDir/Link$ret.rt$ret",$ret); 198 | } 199 | testReadOnly($rootDir,"Link$ret.rt$ret"); 200 | testWriteOnly($rootDir,"Link$ret.rt$ret",$ret); 201 | 202 | assert( "Clean file", unlink("$rootDir/LinkedFile.rt$ret"),$ret); 203 | } 204 | 205 | sub testSymLink { 206 | my $file; 207 | my ($rootDir,$ret) = @_; 208 | 209 | assert( "Create file", open($file,'>',"$rootDir/LinkedFile.rt$ret") && (print $file 'test') && close($file),0 ); 210 | assert( "Create sym link to file",symlink("$rootDir/LinkedFile.rt$ret","$rootDir/Link$ret.rt$ret"),0 ); 211 | assert( "Read sym link",readlink("$rootDir/Link$ret.rt$ret") ,0); 212 | assert( "Remove sym link",unlink("$rootDir/Link$ret.rt$ret") ,0); 213 | 214 | 215 | assert( "Clean file", unlink("$rootDir/LinkedFile.rt$ret"),0); 216 | 217 | } 218 | 219 | sub testAll { 220 | my ($rootDir)= @_; 221 | print("\n"); 222 | print("Test directory without lock\n"); 223 | print("---------------------------\n"); 224 | testDirectoryWithoutLock($rootDir); 225 | 226 | print("\n"); 227 | print("Test directory with lock\n"); 228 | print("------------------------\n"); 229 | testDirectoryWithLock($rootDir); 230 | 231 | print("\n"); 232 | print("Test file without lock (rt0)\n"); 233 | print("----------------------------\n"); 234 | testFileWithoutLock($rootDir,0); 235 | 236 | print("\n"); 237 | print("Test file without lock (rt1)\n"); 238 | print("----------------------------\n"); 239 | testFileWithoutLock($rootDir,1); 240 | 241 | print("\n"); 242 | print("Test file with lock (rt0)\n"); 243 | print("-------------------------\n"); 244 | testFileWithLock($rootDir,0); 245 | 246 | print("\n"); 247 | print("Test file with lock (rt1)\n"); 248 | print("-------------------------\n"); 249 | testFileWithLock($rootDir,1); 250 | 251 | print("\n"); 252 | print("Test link without lock (rt0)\n"); 253 | print("----------------------------\n"); 254 | testLinkWithoutLock($rootDir,0); 255 | 256 | print("\n"); 257 | print("Test link without lock (rt1)\n"); 258 | print("----------------------------\n"); 259 | testLinkWithoutLock($rootDir,1); 260 | 261 | print("\n"); 262 | print("Test link with lock (rt0)\n"); 263 | print("----------------------------\n"); 264 | testLinkWithLock($rootDir,0); 265 | 266 | print("\n"); 267 | print("Test link with lock (rt1)\n"); 268 | print("----------------------------\n"); 269 | testLinkWithLock($rootDir,1); 270 | 271 | print("\n"); 272 | print("Test symlink (rt0)\n"); 273 | print("----------------------------\n"); 274 | testSymLink($rootDir,0); 275 | } 276 | 277 | 278 | $passed=0;$failed=0;$verbose = 0; 279 | 280 | if (($#ARGV + 1 > 0) && ($ARGV[0]=="-v")) 281 | { 282 | $verbose=1; 283 | } 284 | 285 | 286 | 287 | 288 | 289 | print("Config backup\n"); 290 | `sudo mv /etc/worm.conf /etc/worm.old`; 291 | 292 | 293 | print("Copy config with autolock\n"); 294 | `sudo cp TestWithAutoCommit.conf /etc/worm.conf`; 295 | 296 | print("Restart service\n"); 297 | `sudo systemctl restart worm`; 298 | 299 | 300 | print("Creating test directory\n"); 301 | mkdir("/mnt/WORM/Test") or warn "Cannot create Test directory"; 302 | 303 | $autoLock=1; 304 | testAll("/mnt/WORM/Test"); 305 | 306 | print("Cleanning directories\n"); 307 | `rm -rf /tmp/Test`; 308 | 309 | 310 | print("Copy config without autolock\n"); 311 | `sudo cp TestWithoutAutoCommit.conf /etc/worm.conf`; 312 | 313 | print("Restart service\n"); 314 | `sudo systemctl restart worm`; 315 | 316 | 317 | print("Creating test directory\n"); 318 | mkdir("/mnt/WORM/Test") or warn "Cannot create Test directory"; 319 | 320 | $autoLock=0; 321 | testAll("/mnt/WORM/Test"); 322 | 323 | print("Cleanning directories\n"); 324 | `rm -rf /tmp/Test`; 325 | 326 | print("Config restore\n"); 327 | `sudo rm /etc/worm.conf`; 328 | 329 | `sudo mv /etc/worm.old /etc/worm.conf`; 330 | 331 | 332 | print "\n"; 333 | print "Tests summary:\n"; 334 | print "----------------\n"; 335 | print "PASSED tests: $passed\n"; 336 | print "FAILED tests: $failed\n"; 337 | print "\n"; 338 | -------------------------------------------------------------------------------- /test.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | setup() { 4 | echo "setup" 5 | } 6 | 7 | teardown() { 8 | echo "teardown" 9 | } 10 | 11 | failing_function() { 12 | echo "not good" 13 | return 1 14 | } 15 | 16 | 17 | @test "Config backup" { 18 | mv /etc/WORM.conf /etc/WORM.old && cp TestWithAutoCommit.conf /etc/WORM.conf 19 | } 20 | @test "Restart service" { 21 | systemctl restart WORM 22 | } 23 | @test "Create directory" { 24 | mkdir /mnt/WORM/Test 25 | } 26 | @test "Overwrite directory" { 27 | run mkdir /mnt/WORM/Test 28 | [ $status -ne 0 ] 29 | } 30 | @test "Open/release directory" { 31 | ls /mnt/WORM/Test 32 | } 33 | @test "Remove directory" { 34 | rmdir /mnt/WORM/Test 35 | } 36 | 37 | @test "Create directory and delay" { 38 | mkdir /mnt/WORM/Test 39 | sleep 2 40 | } 41 | @test "Rename directory with delay" { 42 | run mv /mnt/WORM/Test /mnt/WORM/Test2 43 | [ $status -ne 0 ] 44 | } 45 | 46 | @test "Remove directory with delay" { 47 | run rmdir /mnt/WORM/Test 48 | [ $status -ne 0 ] 49 | } 50 | @test "Overwrite directory with delay" { 51 | run mkdir /mnt/WORM/Test 52 | [ $status -ne 0 ] 53 | } 54 | 55 | 56 | @test "Create file (ret 0)" { 57 | ls >/mnt/WORM/Test/test.rt0 58 | } 59 | 60 | @test "Overwrite file (ret 0)" { 61 | ls >/mnt/WORM/Test/test.rt0 62 | } 63 | 64 | @test "Append file (ret 0)" { 65 | ls >>/mnt/WORM/Test/test.rt0 66 | } 67 | @test "stat file (ret 0)" { 68 | stat /mnt/WORM/Test/test.rt0 69 | } 70 | @test "list extended attributes (ret 0)" { 71 | getfattr /mnt/WORM/Test/test.rt0 72 | } 73 | @test "get extended attribute (ret 0)" { 74 | getfattr -n user.Retention /mnt/WORM/Test/test.rt0 75 | } 76 | @test "set extended attribute (ret 0)" { 77 | setfattr -n user.Test /mnt/WORM/Test/test.rt0 78 | } 79 | @test "remove extended attribute (ret 0)" { 80 | setfattr -x user.Test /mnt/WORM/Test/test.rt0 81 | } 82 | 83 | @test "Update access time (ret 0)" { 84 | touch -a -t 299901010000.00 /mnt/WORM/Test/test.rt0 85 | } 86 | @test "Update modification time (ret 0)" { 87 | touch -m -t 299901010000.00 /mnt/WORM/Test/test.rt0 88 | } 89 | 90 | @test "Change file owner (ret 0)" { 91 | chown worm:worm /mnt/WORM/Test/test.rt0 92 | } 93 | @test "Change file permissions (ret 0)" { 94 | chmod o+w /mnt/WORM/Test/test.rt0 95 | } 96 | 97 | @test "Rename file (ret 0)" { 98 | mv /mnt/WORM/Test/test.rt0 /mnt/WORM/Test/test1.rt0 99 | } 100 | 101 | @test "Remove file (ret 0)" { 102 | rm /mnt/WORM/Test/test1.rt0 103 | } 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | @test "Create file and delay (ret 0)" { 113 | ls >/mnt/WORM/Test/test.rt0 114 | sleep 2 115 | } 116 | @test "Overwrite file with delay (ret 0)" { 117 | ls >/mnt/WORM/Test/test.rt0 118 | } 119 | @test "Append file with delay (ret 0)" { 120 | ls >>/mnt/WORM/Test/test.rt0 121 | } 122 | 123 | @test "stat file with delay (ret 0)" { 124 | stat /mnt/WORM/Test/test.rt0 125 | } 126 | @test "list extended attributes with delay (ret 0)" { 127 | getfattr /mnt/WORM/Test/test.rt0 128 | } 129 | @test "get extended attribute with delay (ret 0)" { 130 | getfattr -n user.Retention /mnt/WORM/Test/test.rt0 131 | } 132 | @test "set extended attribute with delay (ret 0)" { 133 | setfattr -n user.Test /mnt/WORM/Test/test.rt0 134 | } 135 | @test "remove extended attribute with delay (ret 0)" { 136 | setfattr -x user.Test /mnt/WORM/Test/test.rt0 137 | } 138 | 139 | @test "Update access time with delay (ret 0)" { 140 | touch -a -t 299901010000.00 /mnt/WORM/Test/test.rt0 141 | } 142 | @test "Update modification time with delay (ret 0)" { 143 | touch -m -t 299901010000.00 /mnt/WORM/Test/test.rt0 144 | } 145 | 146 | @test "Change file owner with delay (ret 0)" { 147 | chown worm:worm /mnt/WORM/Test/test.rt0 148 | } 149 | @test "Change file permissions with delay (ret 0)" { 150 | chmod o+w /mnt/WORM/Test/test.rt0 151 | } 152 | 153 | @test "Rename file with delay (ret 0)" { 154 | mv /mnt/WORM/Test/test.rt0 /mnt/WORM/Test/test1.rt0 155 | } 156 | 157 | @test "Remove file with delay (ret 0)" { 158 | rm /mnt/WORM/Test/test1.rt0 159 | } 160 | 161 | @test "Create file (ret 1)" { 162 | ls >/mnt/WORM/Test/test.rt1 163 | } 164 | @test "Overwrite file (ret 1)" { 165 | ls >/mnt/WORM/Test/test.rt1 166 | } 167 | @test "Append file (ret 1)" { 168 | ls >>/mnt/WORM/Test/test.rt1 169 | } 170 | @test "Remove file (ret 1)" { 171 | rm /mnt/WORM/Test/test.rt1 172 | } 173 | @test "Create file and delay (ret 1)" { 174 | ls >/mnt/WORM/Test/test.rt1 175 | sleep 2 176 | } 177 | @test "Overwrite file with delay (ret 1)" { 178 | run bash -c "ls >/mnt/WORM/Test/test.rt1" 179 | [ $status -ne 0 ] 180 | } 181 | @test "Append file with delay (ret 1)" { 182 | run bash -c 'ls >>/mnt/WORM/Test/test.rt1' 183 | [ $status -ne 0 ] 184 | } 185 | @test "stat file with delay (ret 1)" { 186 | stat /mnt/WORM/Test/test.rt1 187 | } 188 | @test "list extended attributes with delay (ret 1)" { 189 | getfattr /mnt/WORM/Test/test.rt1 190 | } 191 | @test "get extended attribute with delay (ret 1)" { 192 | getfattr -n user.Retention /mnt/WORM/Test/test.rt1 193 | } 194 | @test "set extended attribute with delay (ret 1)" { 195 | run setfattr -n user.Test /mnt/WORM/Test/test.rt1 196 | [ $status -ne 0 ] 197 | } 198 | @test "remove extended attribute with delay (ret 1)" { 199 | run setfattr -x user.Retention /mnt/WORM/Test/test.rt1 200 | [ $status -ne 0 ] 201 | } 202 | @test "Update access time with delay (ret 1)" { 203 | run touch -a -t 299901010000.00 /mnt/WORM/Test/test.rt1 204 | [ $status -ne 0 ] 205 | } 206 | @test "Update modification time with delay (ret 1)" { 207 | run touch -m -t 299901010000.00 /mnt/WORM/Test/test.rt1 208 | [ $status -ne 0 ] 209 | } 210 | @test "Change file owner (ret 1)" { 211 | run chown worm:worm /mnt/WORM/Test/test.rt1 212 | [ $status -ne 0 ] 213 | } 214 | @test "Change file permissions (ret 1)" { 215 | run chmod o+w /mnt/WORM/Test/test.rt1 216 | [ $status -ne 0 ] 217 | } 218 | 219 | @test "Rename file with delay (ret 1)" { 220 | run mv /mnt/WORM/Test/test.rt1 /mnt/WORM/Test/test2.rt1 221 | [ $status -ne 0 ] 222 | } 223 | 224 | @test "Remove file with delay (ret 1)" { 225 | run rm /mnt/WORM/Test/test.rt1 226 | [ $status -ne 0 ] 227 | } 228 | @test "Create file for link tests (ret 0)" { 229 | ls >/mnt/WORM/Test/test.rt0 230 | } 231 | @test "Create link (ret 0)" { 232 | ln /mnt/WORM/Test/test.rt0 /mnt/WORM/Test/link.rt0 233 | } 234 | @test "read link (ret 0)" { 235 | run readlink /mnt/WORM/Test/link.rt0 236 | [ $status -ne 0 ] 237 | } 238 | @test "ulink (ret 0)" { 239 | unlink /mnt/WORM/Test/link.rt0 240 | } 241 | 242 | @test "Create sym link (ret 0)" { 243 | ln -s /mnt/WORMTest/test.rt0 /mnt/WORM/Test/link.rt0 244 | } 245 | @test "read sym link (ret 0)" { 246 | readlink /mnt/WORM/Test/link.rt0 247 | } 248 | @test "ulink sym (ret 0)" { 249 | unlink /mnt/WORM/Test/link.rt0 250 | } 251 | 252 | @test "Create link and delay ret 1)" { 253 | ln /mnt/WORM/Test/test.rt1 /mnt/WORM/Test/link.rt1 254 | sleep 2 255 | } 256 | @test "read link with delay (ret 1)" { 257 | run readlink /mnt/WORM/Test/link.rt1 258 | [ $status -ne 0 ] 259 | } 260 | @test "ulink with delay (ret 1)" { 261 | run unlink /mnt/WORM/Test/link.rt1 262 | [ $status -ne 0 ] 263 | } 264 | 265 | @test "Create sym link and delay (ret 1)" { 266 | ln -s /mnt/WORMTest/test.rt1 /mnt/WORM/Test/link2.rt1 267 | sleep 2 268 | } 269 | @test "read sym link with delay (ret 1)" { 270 | readlink /mnt/WORM/Test/link2.rt1 271 | } 272 | 273 | @test "ulink sym with delay (ret 1)" { 274 | unlink /mnt/WORM/Test/link2.rt1 275 | } 276 | 277 | @test "Retrieve file state" { 278 | df /mnt/WORM 279 | } 280 | 281 | 282 | 283 | 284 | 285 | 286 | @test "Clean files for lock test" { 287 | rm -rf /tmp/Test & rm -rf /tmp/Test2 288 | } 289 | 290 | @test "Config change for lock test" { 291 | rm /etc/WORM.conf && cp TestWithoutAutoCommit.conf /etc/WORM.conf 292 | } 293 | @test "Restart service for lock test" { 294 | systemctl restart WORM 295 | } 296 | @test "Create directory without lock" { 297 | mkdir /mnt/WORM/Test 298 | } 299 | @test "Overwrite directory without lock" { 300 | run mkdir /mnt/WORM/Test 301 | [ $status -ne 0 ] 302 | } 303 | @test "Open/release directory without lock" { 304 | ls /mnt/WORM/Test 305 | } 306 | @test "Remove directory without lock" { 307 | rmdir /mnt/WORM/Test 308 | } 309 | 310 | @test "Create directory and lock" { 311 | mkdir /mnt/WORM/Test & chmod a-w /mnt/WORM/Test & touch -a -t 299901010000.00 /mnt/WORM/Test 312 | } 313 | @test "Rename directory with lock" { 314 | run mv /mnt/WORM/Test /mnt/WORM/Test2 315 | [ $status -ne 0 ] 316 | } 317 | 318 | @test "Remove directory with lock" { 319 | run rmdir /mnt/WORM/Test 320 | [ $status -ne 0 ] 321 | } 322 | @test "Overwrite directory with lock" { 323 | run mkdir /mnt/WORM/Test 324 | [ $status -ne 0 ] 325 | } 326 | 327 | 328 | @test "Create file without lock (ret 1)" { 329 | ls >/mnt/WORM/Test/test.rt1 330 | } 331 | 332 | @test "Overwrite file without lock (ret 1)" { 333 | ls >/mnt/WORM/Test/test.rt 334 | } 335 | 336 | @test "Append file without lock (ret 1)" { 337 | ls >>/mnt/WORM/Test/test.rt1 338 | } 339 | @test "stat file without lock (ret 1)" { 340 | stat /mnt/WORM/Test/test.rt1 341 | } 342 | @test "list extended attributes without lock (ret 1)" { 343 | getfattr /mnt/WORM/Test/test.rt1 344 | } 345 | @test "get extended attribute without lock (ret 1)" { 346 | getfattr -n user.Retention /mnt/WORM/Test/test.rt1 347 | } 348 | @test "set extended attribute without lock (ret 1)" { 349 | setfattr -n user.Test /mnt/WORM/Test/test.rt1 350 | } 351 | @test "remove extended attribute without lock (ret 1)" { 352 | setfattr -x user.Test /mnt/WORM/Test/test.rt1 353 | } 354 | 355 | @test "Update access time without lock (ret 1)" { 356 | touch -a -t 299901010000.00 /mnt/WORM/Test/test.rt1 357 | } 358 | @test "Update modification time without lock (ret 1)" { 359 | touch -m -t 299901010000.00 /mnt/WORM/Test/test.rt1 360 | } 361 | 362 | @test "Change file owner without lock (ret 1)" { 363 | chown worm:worm /mnt/WORM/Test/test.rt1 364 | } 365 | @test "Change file permissions without lock (ret 1)" { 366 | chmod o+w /mnt/WORM/Test/test.rt1 367 | } 368 | 369 | @test "Rename file without lock (ret 1)" { 370 | mv /mnt/WORM/Test/test.rt1 /mnt/WORM/Test/test1.rt1 371 | } 372 | 373 | @test "Remove file without lock (ret 1)" { 374 | rm /mnt/WORM/Test/test1.rt1 375 | } 376 | 377 | 378 | @test "Create file and lock (ret 1)" { 379 | ls >/mnt/WORM/Test/test.rt1 & chmod a-w /mnt/WORM/Test/test.rt1 & touch -a -t 299901010000.00 /mnt/WORM/Test/test.rt1 380 | } 381 | @test "Overwrite file with lock (ret 1)" { 382 | run bash -c "ls >/mnt/WORM/Test/test.rt1" 383 | [ $status -ne 0 ] 384 | } 385 | @test "Append file with lock (ret 1)" { 386 | run bash -c 'ls >>/mnt/WORM/Test/test.rt1' 387 | [ $status -ne 0 ] 388 | } 389 | @test "stat file with lock (ret 1)" { 390 | stat /mnt/WORM/Test/test.rt1 391 | } 392 | @test "list extended attributes with lock (ret 1)" { 393 | getfattr /mnt/WORM/Test/test.rt1 394 | } 395 | @test "get extended attribute with lock (ret 1)" { 396 | getfattr -n user.Retention /mnt/WORM/Test/test.rt1 397 | } 398 | @test "set extended attribute with lock (ret 1)" { 399 | run setfattr -n user.Test /mnt/WORM/Test/test.rt1 400 | [ $status -ne 0 ] 401 | } 402 | @test "remove extended attribute with lock (ret 1)" { 403 | run setfattr -x user.Retention /mnt/WORM/Test/test.rt1 404 | [ $status -ne 0 ] 405 | } 406 | @test "Update access time with lock (ret 1)" { 407 | run touch -a -t 299901010000.00 /mnt/WORM/Test/test.rt1 408 | [ $status -ne 0 ] 409 | } 410 | @test "Update modification time with lock (ret 1)" { 411 | run touch -m -t 299901010000.00 /mnt/WORM/Test/test.rt1 412 | [ $status -ne 0 ] 413 | } 414 | @test "Change file owner with lock (ret 1)" { 415 | run chown worm:worm /mnt/WORM/Test/test.rt1 416 | [ $status -ne 0 ] 417 | } 418 | @test "Change file permissions with lock (ret 1)" { 419 | run chmod o+w /mnt/WORM/Test/test.rt1 420 | [ $status -ne 0 ] 421 | } 422 | 423 | @test "Rename file with lock (ret 1)" { 424 | run mv /mnt/WORM/Test/test.rt1 /mnt/WORM/Test/test2.rt1 425 | [ $status -ne 0 ] 426 | } 427 | 428 | @test "Remove file with lock (ret 1)" { 429 | run rm /mnt/WORM/Test/test.rt1 430 | [ $status -ne 0 ] 431 | } 432 | 433 | 434 | 435 | @test "Clean files" { 436 | rm -rf /tmp/Test & rm -rf /tmp/Test2 437 | } 438 | 439 | @test "Config restore" { 440 | #rm /etc/WORM.conf && mv /etc/WORM.old /etc/WORM.conf 441 | } 442 | 443 | 444 | 445 | #@test "test for failure" { 446 | # run failing_function 447 | # 448 | # [ $status -ne 0 ] 449 | #} -------------------------------------------------------------------------------- /Create WORM.sql: -------------------------------------------------------------------------------- 1 | 2 | CREATE DATABASE IF NOT EXISTS `WORM` /*!40100 DEFAULT CHARACTER SET utf8 */; 3 | 4 | grant EXECUTE ON PROCEDURE `WORM`.WriteAudit to 'audit'@'%' identified by 'audit1708$'; 5 | grant ALL on WORM.* to 'administrator'@'%' identified by 'worm1708$'; 6 | 7 | USE `WORM`; 8 | -- MySQL dump 10.13 Distrib 5.6.17, for Win64 (x86_64) 9 | -- 10 | -- Host: 10.0.0.3 Database: WORM 11 | -- ------------------------------------------------------ 12 | -- Server version 5.6.22 13 | 14 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 15 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 16 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 17 | /*!40101 SET NAMES utf8 */; 18 | /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; 19 | /*!40103 SET TIME_ZONE='+00:00' */; 20 | /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; 21 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; 22 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; 23 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; 24 | 25 | -- 26 | -- Table structure for table `Audits` 27 | -- 28 | 29 | DROP TABLE IF EXISTS `Audits`; 30 | /*!40101 SET @saved_cs_client = @@character_set_client */; 31 | /*!40101 SET character_set_client = utf8 */; 32 | CREATE TABLE `Audits` ( 33 | `AuditID` int(10) NOT NULL AUTO_INCREMENT, 34 | `RepositoryID` smallint(5) NOT NULL, 35 | `DateTime` datetime NOT NULL, 36 | `Operation` enum('CREATE','DELETE','UPDATE') NOT NULL, 37 | `Entity` enum('FILE','DIRECTORY','EXPIRATION','RETENTION','OWNER','MODE','TIME','LOCATION') NOT NULL, 38 | `Result` enum('SUCCESS','FAILURE') NOT NULL, 39 | `FileName` longtext NOT NULL, 40 | `Value` longtext NOT NULL, 41 | `UID` smallint(5) unsigned NOT NULL DEFAULT '0', 42 | `GID` smallint(5) unsigned NOT NULL DEFAULT '0', 43 | PRIMARY KEY (`AuditID`) 44 | ) ENGINE=InnoDB AUTO_INCREMENT=3249 DEFAULT CHARSET=utf8; 45 | /*!40101 SET character_set_client = @saved_cs_client */; 46 | 47 | -- 48 | -- Table structure for table `Entries` 49 | -- 50 | 51 | DROP TABLE IF EXISTS `Entries`; 52 | /*!40101 SET @saved_cs_client = @@character_set_client */; 53 | /*!40101 SET character_set_client = utf8 */; 54 | CREATE TABLE `Entries` ( 55 | `EntryID` int(11) NOT NULL AUTO_INCREMENT, 56 | `RepositoryID` smallint(5) NOT NULL, 57 | `Path` longtext NOT NULL, 58 | `Retention` smallint(6) NOT NULL DEFAULT '0', 59 | `ExpirationDate` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', 60 | `IsDeleted` tinyint(1) NOT NULL DEFAULT '0', 61 | PRIMARY KEY (`EntryID`) 62 | ) ENGINE=InnoDB AUTO_INCREMENT=107 DEFAULT CHARSET=utf8; 63 | /*!40101 SET character_set_client = @saved_cs_client */; 64 | 65 | -- 66 | -- Temporary table structure for view `EntriesView` 67 | -- 68 | 69 | DROP TABLE IF EXISTS `EntriesView`; 70 | /*!50001 DROP VIEW IF EXISTS `EntriesView`*/; 71 | SET @saved_cs_client = @@character_set_client; 72 | SET character_set_client = utf8; 73 | /*!50001 CREATE TABLE `EntriesView` ( 74 | `EntryID` tinyint NOT NULL, 75 | `RepositoryID` tinyint NOT NULL, 76 | `Path` tinyint NOT NULL, 77 | `Retention` tinyint NOT NULL, 78 | `ExpirationDate` tinyint NOT NULL, 79 | `IsDeleted` tinyint NOT NULL, 80 | `IsExpired` tinyint NOT NULL 81 | ) ENGINE=MyISAM */; 82 | SET character_set_client = @saved_cs_client; 83 | 84 | -- 85 | -- Table structure for table `Stats` 86 | -- 87 | 88 | DROP TABLE IF EXISTS `Stats`; 89 | /*!40101 SET @saved_cs_client = @@character_set_client */; 90 | /*!40101 SET character_set_client = utf8 */; 91 | CREATE TABLE `Stats` ( 92 | `StatID` int(11) NOT NULL AUTO_INCREMENT, 93 | `RepositoryID` smallint(5) NOT NULL, 94 | `DateTime` datetime NOT NULL, 95 | `NumberOfExpiredEntries` int(11) NOT NULL, 96 | `NumberOfNonExpiredEntries` int(11) NOT NULL, 97 | `NumberOfEntries` int(11) NOT NULL, 98 | `NumberOfDeletedEntries` int(11) NOT NULL, 99 | PRIMARY KEY (`StatID`) 100 | ) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8; 101 | /*!40101 SET character_set_client = @saved_cs_client */; 102 | 103 | -- 104 | -- Dumping events for database 'WORM' 105 | -- 106 | /*!50106 SET @save_time_zone= @@TIME_ZONE */ ; 107 | /*!50106 DROP EVENT IF EXISTS `UpdateStats` */; 108 | DELIMITER ;; 109 | /*!50003 SET @saved_cs_client = @@character_set_client */ ;; 110 | /*!50003 SET @saved_cs_results = @@character_set_results */ ;; 111 | /*!50003 SET @saved_col_connection = @@collation_connection */ ;; 112 | /*!50003 SET character_set_client = utf8 */ ;; 113 | /*!50003 SET character_set_results = utf8 */ ;; 114 | /*!50003 SET collation_connection = utf8_general_ci */ ;; 115 | /*!50003 SET @saved_sql_mode = @@sql_mode */ ;; 116 | /*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION' */ ;; 117 | /*!50003 SET @saved_time_zone = @@time_zone */ ;; 118 | /*!50003 SET time_zone = 'SYSTEM' */ ;; 119 | /*!50106 CREATE*/ /*!50117 DEFINER=`administrator`@`%`*/ /*!50106 EVENT `UpdateStats` ON SCHEDULE EVERY 1 DAY STARTS '2010-06-20 00:00:00' ON COMPLETION NOT PRESERVE ENABLE DO call UpdateStats(1) */ ;; 120 | /*!50003 SET time_zone = @saved_time_zone */ ;; 121 | /*!50003 SET sql_mode = @saved_sql_mode */ ;; 122 | /*!50003 SET character_set_client = @saved_cs_client */ ;; 123 | /*!50003 SET character_set_results = @saved_cs_results */ ;; 124 | /*!50003 SET collation_connection = @saved_col_connection */ ;; 125 | DELIMITER ; 126 | /*!50106 SET TIME_ZONE= @save_time_zone */ ; 127 | 128 | -- 129 | -- Dumping routines for database 'WORM' 130 | -- 131 | /*!50003 DROP FUNCTION IF EXISTS `CreateEntryFromPath` */; 132 | /*!50003 SET @saved_cs_client = @@character_set_client */ ; 133 | /*!50003 SET @saved_cs_results = @@character_set_results */ ; 134 | /*!50003 SET @saved_col_connection = @@collation_connection */ ; 135 | /*!50003 SET character_set_client = utf8 */ ; 136 | /*!50003 SET character_set_results = utf8 */ ; 137 | /*!50003 SET collation_connection = utf8_general_ci */ ; 138 | /*!50003 SET @saved_sql_mode = @@sql_mode */ ; 139 | /*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION' */ ; 140 | DELIMITER ;; 141 | CREATE DEFINER=`administrator`@`%` FUNCTION `CreateEntryFromPath`( 142 | RepositoryID smallint, 143 | Entity ENUM('FILE','DIRECTORY','EXPIRATION','RETENTION','OWNER','MODE','TIME','LOCATION'), 144 | Path longtext 145 | ) RETURNS int(11) 146 | BEGIN 147 | declare id integer; 148 | 149 | set id=null; 150 | if (Entity='FILE' or Entity='DIRECTORY') THEN 151 | set id=GetEntryIDFromPath(RepositoryID,Path,0); 152 | if isnull(id) THEN 153 | insert into Entries(RepositoryID,Path) values (RepositoryID,Path); 154 | select LAST_INSERT_ID() into id; 155 | END IF; 156 | END IF; 157 | 158 | 159 | RETURN id; 160 | END ;; 161 | DELIMITER ; 162 | /*!50003 SET sql_mode = @saved_sql_mode */ ; 163 | /*!50003 SET character_set_client = @saved_cs_client */ ; 164 | /*!50003 SET character_set_results = @saved_cs_results */ ; 165 | /*!50003 SET collation_connection = @saved_col_connection */ ; 166 | /*!50003 DROP FUNCTION IF EXISTS `DeleteEntryFromPath` */; 167 | /*!50003 SET @saved_cs_client = @@character_set_client */ ; 168 | /*!50003 SET @saved_cs_results = @@character_set_results */ ; 169 | /*!50003 SET @saved_col_connection = @@collation_connection */ ; 170 | /*!50003 SET character_set_client = utf8 */ ; 171 | /*!50003 SET character_set_results = utf8 */ ; 172 | /*!50003 SET collation_connection = utf8_general_ci */ ; 173 | /*!50003 SET @saved_sql_mode = @@sql_mode */ ; 174 | /*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION' */ ; 175 | DELIMITER ;; 176 | CREATE DEFINER=`administrator`@`%` FUNCTION `DeleteEntryFromPath`( 177 | RepositoryID smallint, 178 | Entity ENUM('FILE','DIRECTORY','EXPIRATION','RETENTION','OWNER','MODE','TIME','LOCATION'), 179 | Path longtext 180 | 181 | ) RETURNS int(11) 182 | BEGIN 183 | declare id int; 184 | 185 | set id=null; 186 | if (Entity='FILE' or Entity='DIRECTORY') THEN 187 | set id=CreateEntryFromPath(RepositoryID,Entity,Path); 188 | CALL DeleteEntryFromID(id); 189 | END IF; 190 | 191 | RETURN id; 192 | END ;; 193 | DELIMITER ; 194 | /*!50003 SET sql_mode = @saved_sql_mode */ ; 195 | /*!50003 SET character_set_client = @saved_cs_client */ ; 196 | /*!50003 SET character_set_results = @saved_cs_results */ ; 197 | /*!50003 SET collation_connection = @saved_col_connection */ ; 198 | /*!50003 DROP FUNCTION IF EXISTS `GetEntryIDFromPath` */; 199 | /*!50003 SET @saved_cs_client = @@character_set_client */ ; 200 | /*!50003 SET @saved_cs_results = @@character_set_results */ ; 201 | /*!50003 SET @saved_col_connection = @@collation_connection */ ; 202 | /*!50003 SET character_set_client = utf8 */ ; 203 | /*!50003 SET character_set_results = utf8 */ ; 204 | /*!50003 SET collation_connection = utf8_general_ci */ ; 205 | /*!50003 SET @saved_sql_mode = @@sql_mode */ ; 206 | /*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION' */ ; 207 | DELIMITER ;; 208 | CREATE DEFINER=`administrator`@`%` FUNCTION `GetEntryIDFromPath`(RepositoryID smallint,Path longtext,ReturnDeleted tinyint(1)) RETURNS int(11) 209 | BEGIN 210 | declare id integer; 211 | 212 | set id=null; 213 | if (ReturnDeleted=1) then 214 | select EntryID into id from Entries where Entries.Path=Path and Entries.RepositoryID=RepositoryID LIMIT 1; 215 | else 216 | select EntryID into id from Entries where Entries.Path=Path and Entries.RepositoryID=RepositoryID and IsDeleted=0 LIMIT 1; 217 | end if; 218 | 219 | RETURN id; 220 | END ;; 221 | DELIMITER ; 222 | /*!50003 SET sql_mode = @saved_sql_mode */ ; 223 | /*!50003 SET character_set_client = @saved_cs_client */ ; 224 | /*!50003 SET character_set_results = @saved_cs_results */ ; 225 | /*!50003 SET collation_connection = @saved_col_connection */ ; 226 | /*!50003 DROP FUNCTION IF EXISTS `UpdateEntryFromPath` */; 227 | /*!50003 SET @saved_cs_client = @@character_set_client */ ; 228 | /*!50003 SET @saved_cs_results = @@character_set_results */ ; 229 | /*!50003 SET @saved_col_connection = @@collation_connection */ ; 230 | /*!50003 SET character_set_client = utf8 */ ; 231 | /*!50003 SET character_set_results = utf8 */ ; 232 | /*!50003 SET collation_connection = utf8_general_ci */ ; 233 | /*!50003 SET @saved_sql_mode = @@sql_mode */ ; 234 | /*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION' */ ; 235 | DELIMITER ;; 236 | CREATE DEFINER=`administrator`@`%` FUNCTION `UpdateEntryFromPath`( 237 | RepositoryID smallint, 238 | Entity ENUM('FILE','DIRECTORY','EXPIRATION','RETENTION','OWNER','MODE','TIME','LOCATION'), 239 | Path longtext , 240 | Value longtext 241 | ) RETURNS int(11) 242 | BEGIN 243 | 244 | declare id int; 245 | 246 | set id=GetEntryIDFromPath(RepositoryID,Path,0); 247 | 248 | case Entity 249 | WHEN 'EXPIRATION' then CALL UpdateExpirationFromID(id,Value); 250 | WHEN 'RETENTION' then CALL UpdateRetentionFromID(id,Value); 251 | WHEN 'LOCATION' then CALL UpdateLocationFromID(id,Value); 252 | ELSE BEGIN END; 253 | END CASE; 254 | 255 | RETURN id; 256 | END ;; 257 | DELIMITER ; 258 | /*!50003 SET sql_mode = @saved_sql_mode */ ; 259 | /*!50003 SET character_set_client = @saved_cs_client */ ; 260 | /*!50003 SET character_set_results = @saved_cs_results */ ; 261 | /*!50003 SET collation_connection = @saved_col_connection */ ; 262 | /*!50003 DROP PROCEDURE IF EXISTS `DeleteEntryFromID` */; 263 | /*!50003 SET @saved_cs_client = @@character_set_client */ ; 264 | /*!50003 SET @saved_cs_results = @@character_set_results */ ; 265 | /*!50003 SET @saved_col_connection = @@collation_connection */ ; 266 | /*!50003 SET character_set_client = utf8 */ ; 267 | /*!50003 SET character_set_results = utf8 */ ; 268 | /*!50003 SET collation_connection = utf8_general_ci */ ; 269 | /*!50003 SET @saved_sql_mode = @@sql_mode */ ; 270 | /*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION' */ ; 271 | DELIMITER ;; 272 | CREATE DEFINER=`administrator`@`%` PROCEDURE `DeleteEntryFromID`(in EntryID int) 273 | BEGIN 274 | Update Entries set IsDeleted=1 where Entries.EntryID=EntryID; 275 | 276 | END ;; 277 | DELIMITER ; 278 | /*!50003 SET sql_mode = @saved_sql_mode */ ; 279 | /*!50003 SET character_set_client = @saved_cs_client */ ; 280 | /*!50003 SET character_set_results = @saved_cs_results */ ; 281 | /*!50003 SET collation_connection = @saved_col_connection */ ; 282 | /*!50003 DROP PROCEDURE IF EXISTS `UpdateExpirationFromID` */; 283 | /*!50003 SET @saved_cs_client = @@character_set_client */ ; 284 | /*!50003 SET @saved_cs_results = @@character_set_results */ ; 285 | /*!50003 SET @saved_col_connection = @@collation_connection */ ; 286 | /*!50003 SET character_set_client = utf8 */ ; 287 | /*!50003 SET character_set_results = utf8 */ ; 288 | /*!50003 SET collation_connection = utf8_general_ci */ ; 289 | /*!50003 SET @saved_sql_mode = @@sql_mode */ ; 290 | /*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION' */ ; 291 | DELIMITER ;; 292 | CREATE DEFINER=`administrator`@`%` PROCEDURE `UpdateExpirationFromID`(in EntryID int,in Value longtext) 293 | BEGIN 294 | declare seconds int; 295 | declare expiration datetime; 296 | 297 | set seconds=CONVERT(Value,unsigned); 298 | set expiration=FROM_UNIXTIME(seconds); 299 | 300 | update Entries set ExpirationDate=expiration where Entries.EntryID=EntryID; 301 | 302 | END ;; 303 | DELIMITER ; 304 | /*!50003 SET sql_mode = @saved_sql_mode */ ; 305 | /*!50003 SET character_set_client = @saved_cs_client */ ; 306 | /*!50003 SET character_set_results = @saved_cs_results */ ; 307 | /*!50003 SET collation_connection = @saved_col_connection */ ; 308 | /*!50003 DROP PROCEDURE IF EXISTS `UpdateLocationFromID` */; 309 | /*!50003 SET @saved_cs_client = @@character_set_client */ ; 310 | /*!50003 SET @saved_cs_results = @@character_set_results */ ; 311 | /*!50003 SET @saved_col_connection = @@collation_connection */ ; 312 | /*!50003 SET character_set_client = utf8 */ ; 313 | /*!50003 SET character_set_results = utf8 */ ; 314 | /*!50003 SET collation_connection = utf8_general_ci */ ; 315 | /*!50003 SET @saved_sql_mode = @@sql_mode */ ; 316 | /*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION' */ ; 317 | DELIMITER ;; 318 | CREATE DEFINER=`administrator`@`%` PROCEDURE `UpdateLocationFromID`(in EntryID int,in NewPath longtext) 319 | BEGIN 320 | 321 | Update Entries set Path=NewPath where Entries.EntryID=EntryID; 322 | 323 | 324 | END ;; 325 | DELIMITER ; 326 | /*!50003 SET sql_mode = @saved_sql_mode */ ; 327 | /*!50003 SET character_set_client = @saved_cs_client */ ; 328 | /*!50003 SET character_set_results = @saved_cs_results */ ; 329 | /*!50003 SET collation_connection = @saved_col_connection */ ; 330 | /*!50003 DROP PROCEDURE IF EXISTS `UpdateRetentionFromID` */; 331 | /*!50003 SET @saved_cs_client = @@character_set_client */ ; 332 | /*!50003 SET @saved_cs_results = @@character_set_results */ ; 333 | /*!50003 SET @saved_col_connection = @@collation_connection */ ; 334 | /*!50003 SET character_set_client = utf8 */ ; 335 | /*!50003 SET character_set_results = utf8 */ ; 336 | /*!50003 SET collation_connection = utf8_general_ci */ ; 337 | /*!50003 SET @saved_sql_mode = @@sql_mode */ ; 338 | /*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION' */ ; 339 | DELIMITER ;; 340 | CREATE DEFINER=`administrator`@`%` PROCEDURE `UpdateRetentionFromID`(in EntryID int,in Value longtext) 341 | BEGIN 342 | 343 | update Entries set Retention=Value where Entries.EntryID=EntryID; 344 | 345 | END ;; 346 | DELIMITER ; 347 | /*!50003 SET sql_mode = @saved_sql_mode */ ; 348 | /*!50003 SET character_set_client = @saved_cs_client */ ; 349 | /*!50003 SET character_set_results = @saved_cs_results */ ; 350 | /*!50003 SET collation_connection = @saved_col_connection */ ; 351 | /*!50003 DROP PROCEDURE IF EXISTS `UpdateStats` */; 352 | /*!50003 SET @saved_cs_client = @@character_set_client */ ; 353 | /*!50003 SET @saved_cs_results = @@character_set_results */ ; 354 | /*!50003 SET @saved_col_connection = @@collation_connection */ ; 355 | /*!50003 SET character_set_client = utf8 */ ; 356 | /*!50003 SET character_set_results = utf8 */ ; 357 | /*!50003 SET collation_connection = utf8_general_ci */ ; 358 | /*!50003 SET @saved_sql_mode = @@sql_mode */ ; 359 | /*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION' */ ; 360 | DELIMITER ;; 361 | CREATE DEFINER=`administrator`@`%` PROCEDURE `UpdateStats`(RepositoryID smallint) 362 | BEGIN 363 | declare numberOfEntries int; 364 | declare numberOfExpiredEntries int; 365 | declare numberOfNonExpiredEntries int; 366 | declare numberOfDeletedEntries int; 367 | 368 | SELECT count(EntryID) into numberOfNonExpiredEntries FROM EntriesView where IsExpired=0 and IsDeleted=0 and EntriesView.RepositoryID=RepositoryID; 369 | SELECT count(EntryID) into numberOfExpiredEntries FROM EntriesView where IsExpired=1 and IsDeleted=0 and EntriesView.RepositoryID=RepositoryID; 370 | set numberOfEntries=numberOfExpiredEntries+numberOfNonExpiredEntries; 371 | SELECT count(EntryID) into numberOfDeletedEntries FROM EntriesView where IsDeleted=1 and EntriesView.RepositoryID=RepositoryID; 372 | 373 | insert into Stats (RepositoryID,DateTime,NumberOfExpiredEntries,NumberOfNonExpiredEntries,NumberOfEntries,NumberOfDeletedEntries) values (RepositoryID,NOW(),numberOfExpiredEntries,numberOfNonExpiredEntries,numberOfEntries,numberOfDeletedEntries); 374 | 375 | END ;; 376 | DELIMITER ; 377 | /*!50003 SET sql_mode = @saved_sql_mode */ ; 378 | /*!50003 SET character_set_client = @saved_cs_client */ ; 379 | /*!50003 SET character_set_results = @saved_cs_results */ ; 380 | /*!50003 SET collation_connection = @saved_col_connection */ ; 381 | /*!50003 DROP PROCEDURE IF EXISTS `WriteAudit` */; 382 | /*!50003 SET @saved_cs_client = @@character_set_client */ ; 383 | /*!50003 SET @saved_cs_results = @@character_set_results */ ; 384 | /*!50003 SET @saved_col_connection = @@collation_connection */ ; 385 | /*!50003 SET character_set_client = utf8 */ ; 386 | /*!50003 SET character_set_results = utf8 */ ; 387 | /*!50003 SET collation_connection = utf8_general_ci */ ; 388 | /*!50003 SET @saved_sql_mode = @@sql_mode */ ; 389 | /*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION' */ ; 390 | DELIMITER ;; 391 | CREATE DEFINER=`administrator`@`%` PROCEDURE `WriteAudit`( 392 | IN DateTime datetime , 393 | IN RepositoryID smallint, 394 | IN Operation ENUM('CREATE','DELETE','UPDATE'), 395 | in Entity ENUM('FILE','DIRECTORY','EXPIRATION','RETENTION','OWNER','MODE','TIME','LOCATION'), 396 | in Result ENUM('SUCCESS','FAILURE') , 397 | in FileName longtext , 398 | in Value longtext , 399 | in UID smallint , 400 | in GID smallint) 401 | BEGIN 402 | insert into Audits (Datetime,RepositoryID,Operation,Entity,Result,FileName,Value,UID,GID) 403 | values (DateTime,RepositoryID,Operation,Entity,Result,FileName,Value,UID,GID); 404 | 405 | if (Result!='FAILURE') then 406 | case Operation 407 | WHEN 'CREATE' then select CreateEntryFromPath(RepositoryID,Entity,FileName); 408 | WHEN 'DELETE' then select DeleteEntryFromPath(RepositoryID,Entity,FileName); 409 | WHEN 'UPDATE' then select UpdateEntryFromPath(RepositoryID,Entity,FileName,Value); 410 | ELSE BEGIN END; 411 | END CASE; 412 | END IF; 413 | 414 | END ;; 415 | DELIMITER ; 416 | /*!50003 SET sql_mode = @saved_sql_mode */ ; 417 | /*!50003 SET character_set_client = @saved_cs_client */ ; 418 | /*!50003 SET character_set_results = @saved_cs_results */ ; 419 | /*!50003 SET collation_connection = @saved_col_connection */ ; 420 | 421 | -- 422 | -- Final view structure for view `EntriesView` 423 | -- 424 | 425 | /*!50001 DROP TABLE IF EXISTS `EntriesView`*/; 426 | /*!50001 DROP VIEW IF EXISTS `EntriesView`*/; 427 | /*!50001 SET @saved_cs_client = @@character_set_client */; 428 | /*!50001 SET @saved_cs_results = @@character_set_results */; 429 | /*!50001 SET @saved_col_connection = @@collation_connection */; 430 | /*!50001 SET character_set_client = utf8 */; 431 | /*!50001 SET character_set_results = utf8 */; 432 | /*!50001 SET collation_connection = utf8_general_ci */; 433 | /*!50001 CREATE ALGORITHM=UNDEFINED */ 434 | /*!50013 DEFINER=`administrator`@`%` SQL SECURITY DEFINER */ 435 | /*!50001 VIEW `EntriesView` AS select `Entries`.`EntryID` AS `EntryID`,`Entries`.`RepositoryID` AS `RepositoryID`,`Entries`.`Path` AS `Path`,`Entries`.`Retention` AS `Retention`,`Entries`.`ExpirationDate` AS `ExpirationDate`,`Entries`.`IsDeleted` AS `IsDeleted`,if(((to_days(now()) - to_days(`Entries`.`ExpirationDate`)) < 0),0,1) AS `IsExpired` from `Entries` */; 436 | /*!50001 SET character_set_client = @saved_cs_client */; 437 | /*!50001 SET character_set_results = @saved_cs_results */; 438 | /*!50001 SET collation_connection = @saved_col_connection */; 439 | /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; 440 | 441 | /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; 442 | /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; 443 | /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; 444 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 445 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 446 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 447 | /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; 448 | 449 | -- Dump completed on 2015-04-18 11:08:05 450 | 451 | 452 | 453 | --------------------------------------------------------------------------------