Basic getattr, directory listing and read support.
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
bin_PROGRAMS=tcfs
|
bin_PROGRAMS=tcfs
|
||||||
tcfs_SOURCES=src/tcfs.c
|
tcfs_SOURCES=src/tcfs.c
|
||||||
tcfs_LDADD = $(LIBOBJS) $(FUSE_LIBS)
|
tcfs_LDADD = $(LIBOBJS) $(FUSE_LIBS) $(libavcore_LIBS) $(libavutil_LIBS) $(libavformat_LIBS) $(libavcodec_LIBS)
|
||||||
tcfs_CFLAGS = $(FUSE_CFLAGS) -DFUSE_USE_VERSION=28
|
tcfs_CFLAGS = $(FUSE_CFLAGS) $(libavcore_CFLAGS) $(libavutil_CFLAGS) $(libavformat_CFLAGS) $(libavcodec_CFLAGS) -DFUSE_USE_VERSION=28
|
||||||
3
autogen.sh
Executable file
3
autogen.sh
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
aclocal && autoconf && autoheader && automake --add-missing
|
||||||
10
configure.ac
10
configure.ac
@@ -9,18 +9,24 @@ AC_CONFIG_HEADERS([config.h])
|
|||||||
|
|
||||||
# Checks for programs.
|
# Checks for programs.
|
||||||
AC_PROG_CC
|
AC_PROG_CC
|
||||||
AM_PROG_CC_C_O
|
|
||||||
|
|
||||||
# Checks for libraries.
|
# Checks for libraries.
|
||||||
PKG_CHECK_MODULES([FUSE], [fuse >= 2.8])
|
PKG_CHECK_MODULES([FUSE], [fuse >= 2.8])
|
||||||
|
PKG_CHECK_MODULES([libavcodec], [libavcodec >= 52.94])
|
||||||
|
PKG_CHECK_MODULES([libavformat], [libavformat >= 52.84])
|
||||||
|
PKG_CHECK_MODULES([libavcore], [libavcore >= 0.12])
|
||||||
|
PKG_CHECK_MODULES([libavutil], [libavutil >= 50.32])
|
||||||
|
|
||||||
# Checks for header files.
|
# Checks for header files.
|
||||||
AC_CHECK_HEADERS([fcntl.h stddef.h stdlib.h string.h])
|
AC_CHECK_HEADERS([fcntl.h stddef.h stdlib.h string.h errno.h dirent.h])
|
||||||
|
|
||||||
# Checks for typedefs, structures, and compiler characteristics.
|
# Checks for typedefs, structures, and compiler characteristics.
|
||||||
|
AC_PROG_CC_C99
|
||||||
|
AM_PROG_CC_C_O
|
||||||
|
|
||||||
# Checks for library functions.
|
# Checks for library functions.
|
||||||
AC_CHECK_FUNCS([memset strerror])
|
AC_CHECK_FUNCS([memset strerror])
|
||||||
|
AC_CHECK_FUNCS([asprintf], [], [AC_MSG_ERROR([asprintf emulation for non-GNU systems NYI])])
|
||||||
|
|
||||||
AC_CONFIG_FILES([Makefile])
|
AC_CONFIG_FILES([Makefile])
|
||||||
AC_OUTPUT
|
AC_OUTPUT
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
aclocal && autoconf && automake --add-missing
|
|
||||||
61
src/common.h
Normal file
61
src/common.h
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#ifndef TCFS_COMMON_H
|
||||||
|
#define TCFS_COMMON_H 1
|
||||||
|
|
||||||
|
#if HAVE_CONFIG_H
|
||||||
|
# include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_ASPRINTF
|
||||||
|
# define _GNU_SOURCE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#if HAVE_SYS_TYPES_H
|
||||||
|
# include <sys/types.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_SYS_STAT_H
|
||||||
|
# include <sys/stat.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef STDC_HEADERS
|
||||||
|
# include <stdlib.h>
|
||||||
|
# include <stddef.h>
|
||||||
|
#else
|
||||||
|
# ifdef HAVE_STDLIB_H
|
||||||
|
# include <stdlib.h>
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_STRING_H
|
||||||
|
# if !defined STDC_HEADERS && defined HAVE_MEMORY_H
|
||||||
|
# include <memory.h>
|
||||||
|
# endif
|
||||||
|
# include <string.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_STRINGS_H
|
||||||
|
# include <strings.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_INTTYPES_H
|
||||||
|
# include <inttypes.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_STDINT_H
|
||||||
|
# include <stdint.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_UNISTD_H
|
||||||
|
# include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_ERRNO_H
|
||||||
|
# include <errno.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_DIRENT_H
|
||||||
|
# include <dirent.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
298
src/tcfs.c
298
src/tcfs.c
@@ -4,31 +4,300 @@
|
|||||||
This work is licensed under the Open Software License ("OSL") v. 3.0.
|
This work is licensed under the Open Software License ("OSL") v. 3.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "config.h"
|
#include "common.h"
|
||||||
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <fuse.h>
|
#include <fuse.h>
|
||||||
#include <fuse_opt.h>
|
#include <fuse_opt.h>
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
|
|
||||||
struct options {
|
struct options {
|
||||||
char *base;
|
char *base;
|
||||||
char *tfmt;
|
char *tfmt;
|
||||||
} options;
|
} options;
|
||||||
|
|
||||||
int is_audio_file(const char *path)
|
// Get the filename from a path, i.e. a pointer to the character after the last / in path.
|
||||||
|
// If path contains no /, returns path.
|
||||||
|
char *get_file_from_path(const char *path)
|
||||||
{
|
{
|
||||||
|
if(!path)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
char *ret = strchr(path, '/');
|
||||||
|
if(ret == NULL)
|
||||||
|
return path;
|
||||||
|
|
||||||
|
return ret+1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the extension from the file name
|
||||||
|
char *get_ext_from_file(const char *file)
|
||||||
|
{
|
||||||
|
if(!file)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
char *ret = strchr(file, '.');
|
||||||
|
if(!ret)
|
||||||
|
return file+strlen(file);
|
||||||
|
if(ret == file)
|
||||||
|
return file+strlen(file); // return pointer to '\0';
|
||||||
|
|
||||||
|
return ret+1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *strip_ext_from_path(const char *path)
|
||||||
|
{
|
||||||
|
if(!path)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
char *ret = strdup(path);
|
||||||
|
|
||||||
|
char *ext = strchr(ret, '.');
|
||||||
|
char *file = strchr(ret, '/');
|
||||||
|
if(!ext || (ext<file)) // no . or . not in last component
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
*ext = '\0';
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns 1 if the file is a transcodeable audio file, 0 if it is not and a negative value if an error occured
|
||||||
|
int is_tc_audio_file(const char *path)
|
||||||
|
{
|
||||||
|
if(!path)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
int ret = stat(path, &st);
|
||||||
|
if(ret == -1)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
if(!S_ISREG(st.st_mode))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
char *file = get_file_from_path(path);
|
||||||
|
if(!file || (file[0] == '\0'))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
char *ext = get_ext_from_file(file);
|
||||||
|
if(!ext)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if(strcmp(ext,options.tfmt) == 0)
|
||||||
|
return 0; // file already has right format
|
||||||
|
|
||||||
|
// TODO: proper detection code here
|
||||||
|
|
||||||
|
// for testing
|
||||||
|
if(strcmp(ext,"ogg") == 0)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the path of the transcodeable audio file that
|
||||||
|
// generates 'path'. Returns NULL when there is no such
|
||||||
|
// file.
|
||||||
|
char *get_tc_source_name(const char *path)
|
||||||
|
{
|
||||||
|
if(!path)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if(strcmp(get_ext_from_file(get_file_from_path(path)), options.tfmt)!=0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *path_no_ext = strip_ext_from_path(path);
|
||||||
|
if(!path_no_ext)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
// TODO : search for transcodable files in path_no_ext.*
|
||||||
|
|
||||||
|
// for testing
|
||||||
|
char *ret;
|
||||||
|
if(asprintf(&ret, "%s.ogg", path_no_ext) < 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// safely concatenate options.base and path
|
||||||
|
// the caller must free() the returned string
|
||||||
|
char *path_cat(const char *path)
|
||||||
|
{
|
||||||
|
if(!path)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
char *ret = (char*)malloc(strlen(options.base)+strlen(path)+1);
|
||||||
|
if(!ret)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
memcpy(ret, options.base, strlen(options.base));
|
||||||
|
memcpy(ret+strlen(options.base), path, strlen(path)+1);
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int tcfs_getattr(const char *path, struct stat *stbuf)
|
int tcfs_getattr(const char *path, struct stat *stbuf)
|
||||||
{
|
{
|
||||||
return -1;
|
char *base_path = path_cat(path);
|
||||||
|
if(!base_path)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
int ret = stat(base_path, stbuf);
|
||||||
|
if(ret == -1) {
|
||||||
|
if(errno == ENOENT) { // check if this file can be created by transcoding
|
||||||
|
char *tc_src = get_tc_source_name(base_path);
|
||||||
|
if(tc_src) {
|
||||||
|
ret = stat(tc_src, stbuf);
|
||||||
|
stbuf->st_size = 0;
|
||||||
|
if(ret == -1) {
|
||||||
|
free(base_path);
|
||||||
|
return -errno;
|
||||||
|
} else
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
free(base_path);
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
free(base_path);
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int tcfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
|
||||||
|
off_t offset, struct fuse_file_info *fi)
|
||||||
|
{
|
||||||
|
char *base_path = path_cat(path);
|
||||||
|
if(!base_path)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
int ret = stat(base_path, &st);
|
||||||
|
if(ret == -1) {
|
||||||
|
free(base_path);
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
if(!S_ISDIR(st.st_mode)) {
|
||||||
|
free(base_path);
|
||||||
|
return -ENOTDIR;
|
||||||
|
}
|
||||||
|
|
||||||
|
DIR *dir = opendir(base_path);
|
||||||
|
if(!dir) {
|
||||||
|
free(base_path);
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
union {
|
||||||
|
struct dirent de;
|
||||||
|
char b[offsetof(struct dirent, d_name) + NAME_MAX + 1];
|
||||||
|
} u;
|
||||||
|
struct dirent *dep;
|
||||||
|
while(readdir_r(dir, &u.de, &dep) == 0) {
|
||||||
|
if(!dep) {
|
||||||
|
free(base_path);
|
||||||
|
closedir(dir);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
char *full_file_path;
|
||||||
|
if(base_path[strlen(base_path)-1] == '/') {
|
||||||
|
if(asprintf(&full_file_path, "%s%s", base_path, dep->d_name) < 0) {
|
||||||
|
free(base_path);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(asprintf(&full_file_path, "%s/%s", base_path, dep->d_name) < 0) {
|
||||||
|
free(base_path);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = is_tc_audio_file(full_file_path);
|
||||||
|
free(full_file_path);
|
||||||
|
if(ret < 0) {
|
||||||
|
free(base_path);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ret == 0)
|
||||||
|
filler(buf, dep->d_name, NULL, 0);
|
||||||
|
else {
|
||||||
|
char *ext = strchr(dep->d_name, '.');
|
||||||
|
char *file_new_ext;
|
||||||
|
if(ext == NULL) {
|
||||||
|
if(asprintf(&file_new_ext, "%s.%s", dep->d_name, options.tfmt) < 0) {
|
||||||
|
free(base_path);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*(ext+1) = '\0';
|
||||||
|
if(asprintf(&file_new_ext, "%s%s", dep->d_name, options.tfmt) < 0) {
|
||||||
|
free(base_path);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filler(buf, file_new_ext, NULL, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(base_path);
|
||||||
|
closedir(dir);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct tcfs_file_info {
|
||||||
|
enum {
|
||||||
|
DIRECT,
|
||||||
|
TRANSCODE
|
||||||
|
} type;
|
||||||
|
FILE *fd;
|
||||||
|
};
|
||||||
|
|
||||||
|
int tcfs_open(const char *path, struct fuse_file_info *fi)
|
||||||
|
{
|
||||||
|
char *base_path = path_cat(path);
|
||||||
|
if(!base_path)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
struct tcfs_file_info *tcfs_fi = (struct tcfs_file_info *)malloc(sizeof(struct tcfs_file_info));
|
||||||
|
if(!tcfs_fi) {
|
||||||
|
free(base_path);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
tcfs_fi->fd = fopen(base_path, "r");
|
||||||
|
|
||||||
|
if(!tcfs_fi->fd) {
|
||||||
|
char *src_name = get_tc_source_name(base_path);
|
||||||
|
if(src_name) {
|
||||||
|
tcfs_fi->fd = fopen(src_name, "r");
|
||||||
|
if(!tcfs_fi->fd) {
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fi->fh = (uint64_t)tcfs_fi;
|
||||||
|
fi->direct_io = 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int tcfs_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
|
||||||
|
{
|
||||||
|
struct tcfs_file_info *tcfs_fi = (struct tcfs_file_info*)fi->fh;
|
||||||
|
if(!tcfs_fi)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
int ret = fread(buf, 1, size, tcfs_fi->fd);
|
||||||
|
if((ret == 0) && (errno != 0))
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void *tcfs_init(struct fuse_conn_info *conn)
|
void *tcfs_init(struct fuse_conn_info *conn)
|
||||||
@@ -40,7 +309,11 @@ void *tcfs_init(struct fuse_conn_info *conn)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static struct fuse_operations tcfs_oper = {
|
static struct fuse_operations tcfs_oper = {
|
||||||
.init = tcfs_init
|
.init = tcfs_init,
|
||||||
|
.getattr = tcfs_getattr,
|
||||||
|
.readdir = tcfs_readdir,
|
||||||
|
.open = tcfs_open,
|
||||||
|
.read = tcfs_read
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
@@ -72,6 +345,9 @@ int main(int argc, char *argv[])
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(options.base[strlen(options.base)-1] == '/')
|
||||||
|
options.base[strlen(options.base)-1] = '\0';
|
||||||
|
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if(stat(options.base, &st) != 0) {
|
if(stat(options.base, &st) != 0) {
|
||||||
fprintf(stderr, "Could not stat '%s': %s\n", options.base, strerror(errno));
|
fprintf(stderr, "Could not stat '%s': %s\n", options.base, strerror(errno));
|
||||||
|
|||||||
Reference in New Issue
Block a user