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.

filejoinfs.c 7.2KB

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