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.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  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. struct files *files_alloc(void) {
  77. struct files *f = calloc(1, sizeof(struct files));
  78. f->alloc_files = 64;
  79. f->data = calloc(f->alloc_files, sizeof(struct fileinfo *));
  80. return f;
  81. }
  82. void files_free(struct files **pfiles) {
  83. struct files *files = *pfiles;
  84. struct fileinfo *file;
  85. int i;
  86. if (files) {
  87. for (i=0; i<files->num_files; i++) {
  88. file = files->data[i];
  89. close(file->fd);
  90. free(file->name);
  91. free(file);
  92. }
  93. free(files->data);
  94. free(*pfiles);
  95. *pfiles = NULL;
  96. }
  97. }
  98. #ifdef DO_DUMP
  99. void files_dump(struct files *files) {
  100. int i;
  101. fprintf(stdout,"num_files:%d\n", files->num_files);
  102. fprintf(stdout,"alloc_files:%d\n", files->alloc_files);
  103. fprintf(stdout,"total_sizes:%lld\n", files->total_size);
  104. for (i=0; i<files->num_files; i++) {
  105. struct fileinfo *f = files->data[i];
  106. fprintf(stdout,"file[%d]->fd=%d\n", i, f->fd);
  107. fprintf(stdout,"file[%d]->name=%s\n", i, f->name);
  108. fprintf(stdout,"file[%d]->size=%llu\n", i, f->size);
  109. }
  110. }
  111. #endif
  112. int files_add_file(struct files *files, char *filename) {
  113. int ret = 0;
  114. struct stat sb;
  115. if (stat(filename, &sb) != -1) {
  116. struct fileinfo *file;
  117. int fd = open(filename, O_RDONLY);
  118. if (fd == -1) {
  119. fprintf(stderr, "open(%s) error : %s\n", filename, strerror(errno));
  120. return ret;
  121. }
  122. file = calloc(1, sizeof(struct fileinfo));
  123. file->name = strdup(filename);
  124. file->size = sb.st_size;
  125. file->fd = fd;
  126. files->total_size += file->size;
  127. files->data[files->num_files] = file;
  128. files->num_files++;
  129. if (files->num_files >= files->alloc_files-1) {
  130. files->alloc_files *= 2;
  131. files->data = realloc(files->data, files->alloc_files * sizeof(struct fileinfo *));
  132. }
  133. ret = 1;
  134. } else {
  135. fprintf(stderr, "stat(%s) error : %s\n", filename, strerror(errno));
  136. }
  137. return ret;
  138. }
  139. int files_load_filelist(struct files *files, char *filename) {
  140. size_t len;
  141. ssize_t readed;
  142. int ret = 0;
  143. char *line = NULL;
  144. FILE *file = fopen(filename, "r");
  145. if (!file) {
  146. fprintf(stderr, "Can't open %s : %s\n", filename, strerror(errno));
  147. return ret;
  148. }
  149. while ((readed = getline(&line, &len, file)) != -1) {
  150. line[readed-1] = '\0';
  151. ret += files_add_file(files, line);
  152. }
  153. free(line);
  154. fclose(file);
  155. return ret;
  156. }
  157. static int fuse_getattr(const char *path, struct stat *stbuf) {
  158. if (strcmp(path, "/") != 0)
  159. return -ENOENT;
  160. if (fstat(filelist->data[0]->fd, stbuf) == -1)
  161. return -errno;
  162. stbuf->st_size = filelist->total_size;
  163. return 0;
  164. }
  165. static int fuse_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) {
  166. (void)fi; // prevent warning
  167. int i;
  168. struct fileinfo *file = filelist->data[0];
  169. ssize_t readen, totreaden = 0;
  170. off_t fileofs = offset, passed = 0;
  171. int filenum = 0;
  172. if (strcmp(path, "/") != 0)
  173. return -ENOENT;
  174. if (offset >= filelist->total_size) // Hmm :)
  175. return 0;
  176. if (offset + size > filelist->total_size) { // Asking for too much
  177. if (filelist->total_size < offset) // Prevent overflow
  178. return 0;
  179. size = filelist->total_size - offset;
  180. }
  181. if (offset > file->size) { // After the first file
  182. // Find the file that corresponds to the required offset
  183. // Can be slow with lots of files, but do it simple for now
  184. for (i=0; i<filelist->num_files; i++) {
  185. struct fileinfo *f = filelist->data[i];
  186. passed += f->size;
  187. if (passed >= offset) {
  188. file = f;
  189. filenum = i;
  190. fileofs = file->size - (passed - offset);
  191. break;
  192. }
  193. }
  194. }
  195. // Read all data
  196. do {
  197. readen = pread(file->fd, buf, size, fileofs);
  198. if (readen == -1) // Error reading
  199. return -errno;
  200. totreaden += readen;
  201. fileofs += readen;
  202. buf += readen;
  203. size -= readen;
  204. if (fileofs >= file->size) {
  205. fileofs = 0;
  206. filenum++;
  207. if (filenum >= filelist->num_files) {
  208. break;
  209. }
  210. file = filelist->data[filenum];
  211. }
  212. } while(size > 0);
  213. return totreaden;
  214. }
  215. static struct fuse_operations concatfs_op = {
  216. .getattr = fuse_getattr,
  217. .read = fuse_read,
  218. };
  219. int main(int argc, char *argv[]) {
  220. int ret;
  221. char *filenames;
  222. char *mountpoint_file;
  223. struct stat sb;
  224. if (argc < 3) {
  225. fprintf(stderr, "Usage: fjfs filelist.txt mount-point-file\n");
  226. exit(EXIT_FAILURE);
  227. }
  228. filenames = argv[1];
  229. mountpoint_file = argv[2];
  230. if (stat(mountpoint_file, &sb) == -1) {
  231. fprintf(stderr, "Can't mount on %s : %s\n", mountpoint_file, strerror(errno));
  232. exit(EXIT_FAILURE);
  233. } else {
  234. if (!S_ISREG(sb.st_mode)) {
  235. fprintf(stderr, "%s is not a file!\n", mountpoint_file);
  236. exit(EXIT_FAILURE);
  237. }
  238. }
  239. filelist = files_alloc();
  240. if (!files_load_filelist(filelist, filenames)) {
  241. fprintf(stderr, "Error no files loaded from %s.\n", argv[1]);
  242. files_free(&filelist);
  243. exit(EXIT_FAILURE);
  244. }
  245. char *fuse_argv[5];
  246. fuse_argv[0] = argv[0];
  247. fuse_argv[1] = mountpoint_file;
  248. fuse_argv[2] = "-o";
  249. fuse_argv[3] = "nonempty,allow_other,direct_io";
  250. fuse_argv[4] = 0;
  251. ret = fuse_main(4, fuse_argv, &concatfs_op, NULL);
  252. files_free(&filelist);
  253. return ret;
  254. }