fjfs is FUSE module that implements virtual joining of multiple files as one. https://georgi.unixsol.org/programs/fjfs/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

fjfs.c 7.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. /*
  2. # fjfs
  3. # Quick'n'dirty FUSE module implementing joining of different files as one
  4. #
  5. # Copyright (c) 2010 Georgi Chorbadzhiyski (georgi@unixsol.org)
  6. # All rights reserved.
  7. #
  8. # Redistribution and use of this script, with or without modification, is
  9. # permitted provided that the following conditions are met:
  10. #
  11. # 1. Redistributions of this script must retain the above copyright
  12. # notice, this list of conditions and the following disclaimer.
  13. #
  14. # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
  15. # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  16. # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
  17. # EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  18. # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  19. # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
  20. # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  21. # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
  22. # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  23. # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  24. #
  25. # To compile the program run:
  26. # gcc -Wall -Wextra `pkg-config fuse --cflags --libs` fjfs.c -lfuse -o fjfs
  27. #
  28. # To use it:
  29. # 1. Install the FUSE module
  30. # sudo modprobe fuse
  31. #
  32. # 2. Create the list of files to join
  33. # echo /etc/group >> filelist.txt
  34. # echo /etc/issue >> filelist.txt
  35. # echo /etc/passwd >> filelist.txt
  36. #
  37. # 3. Create an empty file over which the files in the list will be joined
  38. # touch joined.txt
  39. #
  40. # 4. Mount fjfs
  41. # ./fjfs filelist.txt joined.txt
  42. #
  43. # 5. Check the result with
  44. # cat joined.txt
  45. #
  46. # You will see the contents of all the files listed in filelist.txt
  47. #
  48. # 6. Unmount the fs
  49. # fusermount -u joined.txt
  50. #
  51. */
  52. #define FUSE_USE_VERSION 26
  53. #define _XOPEN_SOURCE 500 // for "pread"
  54. #define _GNU_SOURCE 1 // for "getline"
  55. #include <stdio.h>
  56. #include <stdlib.h>
  57. #include <error.h>
  58. #include <string.h>
  59. #include <errno.h>
  60. #include <unistd.h>
  61. #include <sys/types.h>
  62. #include <fuse.h>
  63. /* Handle list of files */
  64. struct fileinfo {
  65. int fd;
  66. char *name;
  67. off_t size;
  68. };
  69. struct files {
  70. int num_files;
  71. off_t total_size;
  72. int alloc_files;
  73. struct fileinfo **data;
  74. };
  75. struct files *filelist;
  76. static struct files *files_alloc(void);
  77. static void files_free(struct files **pfiles);
  78. #ifdef DO_DUMP
  79. static void files_dump(struct files *files);
  80. #endif
  81. static int files_add_file(struct files *files, char *filename);
  82. static int files_load_filelist(struct files *files, char *filename);
  83. struct files *files_alloc(void) {
  84. struct files *f = calloc(1, sizeof(struct files));
  85. f->alloc_files = 64;
  86. f->data = calloc(f->alloc_files, sizeof(struct fileinfo *));
  87. return f;
  88. }
  89. void files_free(struct files **pfiles) {
  90. struct files *files = *pfiles;
  91. struct fileinfo *file;
  92. int i;
  93. if (files) {
  94. for (i=0; i<files->num_files; i++) {
  95. file = files->data[i];
  96. close(file->fd);
  97. free(file->name);
  98. free(file);
  99. }
  100. free(files->data);
  101. free(*pfiles);
  102. *pfiles = NULL;
  103. }
  104. }
  105. #ifdef DO_DUMP
  106. void files_dump(struct files *files) {
  107. int i;
  108. fprintf(stdout,"num_files:%d\n", files->num_files);
  109. fprintf(stdout,"alloc_files:%d\n", files->alloc_files);
  110. fprintf(stdout,"total_sizes:%lld\n", files->total_size);
  111. for (i=0; i<files->num_files; i++) {
  112. struct fileinfo *f = files->data[i];
  113. fprintf(stdout,"file[%d]->fd=%d\n", i, f->fd);
  114. fprintf(stdout,"file[%d]->name=%s\n", i, f->name);
  115. fprintf(stdout,"file[%d]->size=%llu\n", i, f->size);
  116. }
  117. }
  118. #endif
  119. int files_add_file(struct files *files, char *filename) {
  120. int ret = 0;
  121. struct stat sb;
  122. if (stat(filename, &sb) != -1) {
  123. struct fileinfo *file;
  124. int fd = open(filename, O_RDONLY);
  125. if (fd == -1) {
  126. fprintf(stderr, "open(%s) error : %s\n", filename, strerror(errno));
  127. return ret;
  128. }
  129. file = calloc(1, sizeof(struct fileinfo));
  130. file->name = strdup(filename);
  131. file->size = sb.st_size;
  132. file->fd = fd;
  133. files->total_size += file->size;
  134. files->data[files->num_files] = file;
  135. files->num_files++;
  136. if (files->num_files >= files->alloc_files-1) {
  137. files->alloc_files *= 2;
  138. files->data = realloc(files->data, files->alloc_files * sizeof(struct fileinfo *));
  139. }
  140. ret = 1;
  141. } else {
  142. fprintf(stderr, "stat(%s) error : %s\n", filename, strerror(errno));
  143. }
  144. return ret;
  145. }
  146. int files_load_filelist(struct files *files, char *filename) {
  147. size_t len;
  148. ssize_t readed;
  149. int ret = 0;
  150. char *line = NULL;
  151. FILE *file = fopen(filename, "r");
  152. if (!file) {
  153. fprintf(stderr, "Can't open %s : %s\n", filename, strerror(errno));
  154. return ret;
  155. }
  156. while ((readed = getline(&line, &len, file)) != -1) {
  157. line[readed-1] = '\0';
  158. ret += files_add_file(files, line);
  159. }
  160. free(line);
  161. fclose(file);
  162. return ret;
  163. }
  164. static int fuse_getattr(const char *path, struct stat *stbuf) {
  165. if (strcmp(path, "/") != 0)
  166. return -ENOENT;
  167. if (fstat(filelist->data[0]->fd, stbuf) == -1)
  168. return -errno;
  169. stbuf->st_size = filelist->total_size;
  170. return 0;
  171. }
  172. static int fuse_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) {
  173. (void)fi; // prevent warning
  174. int i;
  175. struct fileinfo *file = filelist->data[0];
  176. ssize_t readen, totreaden = 0;
  177. off_t fileofs = offset, passed = 0;
  178. int filenum = 0;
  179. if (strcmp(path, "/") != 0)
  180. return -ENOENT;
  181. if (offset >= filelist->total_size) // Hmm :)
  182. return 0;
  183. if (offset + size > filelist->total_size) { // Asking for too much
  184. if (filelist->total_size < offset) // Prevent overflow
  185. return 0;
  186. size = filelist->total_size - offset;
  187. }
  188. if (offset > file->size) { // After the first file
  189. // Find the file that corresponds to the required offset
  190. // Can be slow with lots of files, but do it simple for now
  191. for (i=0; i<filelist->num_files; i++) {
  192. struct fileinfo *f = filelist->data[i];
  193. passed += f->size;
  194. if (passed >= offset) {
  195. file = f;
  196. filenum = i;
  197. fileofs = file->size - (passed - offset);
  198. break;
  199. }
  200. }
  201. }
  202. // Read all data
  203. do {
  204. readen = pread(file->fd, buf, size, fileofs);
  205. if (readen == -1) // Error reading
  206. return -errno;
  207. totreaden += readen;
  208. fileofs += readen;
  209. buf += readen;
  210. size -= readen;
  211. if (fileofs >= file->size) {
  212. fileofs = 0;
  213. filenum++;
  214. if (filenum >= filelist->num_files) {
  215. break;
  216. }
  217. file = filelist->data[filenum];
  218. }
  219. } while(size > 0);
  220. return totreaden;
  221. }
  222. static struct fuse_operations concatfs_op = {
  223. .getattr = fuse_getattr,
  224. .read = fuse_read,
  225. };
  226. int main(int argc, char *argv[]) {
  227. int ret;
  228. char *filenames;
  229. char *mountpoint_file;
  230. struct stat sb;
  231. if (argc < 3) {
  232. fprintf(stderr, "Usage: fjfs filelist.txt mount-point-file\n");
  233. exit(EXIT_FAILURE);
  234. }
  235. filenames = argv[1];
  236. mountpoint_file = argv[2];
  237. if (stat(mountpoint_file, &sb) == -1) {
  238. fprintf(stderr, "Can't mount on %s : %s\n", mountpoint_file, strerror(errno));
  239. exit(EXIT_FAILURE);
  240. } else {
  241. if (!S_ISREG(sb.st_mode)) {
  242. fprintf(stderr, "%s is not a file!\n", mountpoint_file);
  243. exit(EXIT_FAILURE);
  244. }
  245. }
  246. filelist = files_alloc();
  247. if (!files_load_filelist(filelist, filenames)) {
  248. fprintf(stderr, "Error no files loaded from %s.\n", argv[1]);
  249. files_free(&filelist);
  250. exit(EXIT_FAILURE);
  251. }
  252. char *fuse_argv[5];
  253. fuse_argv[0] = argv[0];
  254. fuse_argv[1] = mountpoint_file;
  255. fuse_argv[2] = "-o";
  256. fuse_argv[3] = "nonempty,allow_other,direct_io";
  257. fuse_argv[4] = 0;
  258. ret = fuse_main(4, fuse_argv, &concatfs_op, NULL);
  259. files_free(&filelist);
  260. return ret;
  261. }