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 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. /*
  2. * fjfs
  3. * fjfs is FUSE module that implements virtual joining of multiple files as one.
  4. *
  5. * Copyright (c) 2010-2011 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. #define FUSE_USE_VERSION 26
  26. #define _XOPEN_SOURCE 500 // for "pread"
  27. #define _GNU_SOURCE 1 // for "getline"
  28. #include <stdio.h>
  29. #include <stdlib.h>
  30. #include <error.h>
  31. #include <string.h>
  32. #include <getopt.h>
  33. #include <errno.h>
  34. #include <unistd.h>
  35. #include <sys/types.h>
  36. #include <dirent.h>
  37. #include <libgen.h>
  38. #include <fnmatch.h>
  39. #include <fuse.h>
  40. /* Handle list of files */
  41. struct fileinfo {
  42. int fd;
  43. char *name;
  44. off_t size;
  45. };
  46. struct files {
  47. int num_files;
  48. off_t total_size;
  49. int alloc_files;
  50. struct fileinfo **data;
  51. };
  52. enum fl_mode {
  53. FL_FILE,
  54. FL_GLOB,
  55. FL_ARGS
  56. };
  57. struct files *filelist;
  58. char *filenames;
  59. char *mountpoint;
  60. int debug = 0;
  61. int allow_other = 0;
  62. int mountpoint_created = 0;
  63. enum fl_mode list_mode = FL_FILE;
  64. static struct files *files_alloc(void) {
  65. struct files *f = calloc(1, sizeof(struct files));
  66. f->alloc_files = 64;
  67. f->data = calloc(f->alloc_files, sizeof(struct fileinfo *));
  68. return f;
  69. }
  70. static void files_free(struct files **pfiles) {
  71. struct files *files = *pfiles;
  72. struct fileinfo *file;
  73. int i;
  74. if (files) {
  75. for (i=0; i<files->num_files; i++) {
  76. file = files->data[i];
  77. close(file->fd);
  78. free(file->name);
  79. free(file);
  80. }
  81. free(files->data);
  82. free(*pfiles);
  83. *pfiles = NULL;
  84. }
  85. }
  86. static void files_dump(struct files *files) {
  87. int i;
  88. fprintf(stdout,"num_files:%d\n", files->num_files);
  89. fprintf(stdout,"alloc_files:%d\n", files->alloc_files);
  90. fprintf(stdout,"total_sizes:%lld\n", (unsigned long long)files->total_size);
  91. for (i=0; i<files->num_files; i++) {
  92. struct fileinfo *f = files->data[i];
  93. fprintf(stdout,"file[%d]->fd=%d\n", i, f->fd);
  94. fprintf(stdout,"file[%d]->name=%s\n", i, f->name);
  95. fprintf(stdout,"file[%d]->size=%llu\n", i, (unsigned long long)f->size);
  96. }
  97. }
  98. static int files_add_file(struct files *files, char *filename) {
  99. int ret = 0;
  100. struct stat sb;
  101. if (stat(filename, &sb) != -1) {
  102. struct fileinfo *file;
  103. int fd = open(filename, O_RDONLY);
  104. if (fd == -1) {
  105. fprintf(stderr, "open(%s) error : %s\n", filename, strerror(errno));
  106. return ret;
  107. }
  108. file = calloc(1, sizeof(struct fileinfo));
  109. file->name = strdup(filename);
  110. file->size = sb.st_size;
  111. file->fd = fd;
  112. files->total_size += file->size;
  113. files->data[files->num_files] = file;
  114. files->num_files++;
  115. if (files->num_files >= files->alloc_files-1) {
  116. files->alloc_files *= 2;
  117. files->data = realloc(files->data, files->alloc_files * sizeof(struct fileinfo *));
  118. }
  119. ret = 1;
  120. } else {
  121. fprintf(stderr, "stat(%s) error : %s\n", filename, strerror(errno));
  122. }
  123. return ret;
  124. }
  125. static int files_load_filelist(struct files *files, char *filename) {
  126. size_t len;
  127. ssize_t readed;
  128. int ret = 0;
  129. char *line = NULL;
  130. FILE *file = fopen(filename, "r");
  131. if (!file) {
  132. fprintf(stderr, "Can't open %s : %s\n", filename, strerror(errno));
  133. return ret;
  134. }
  135. while ((readed = getline(&line, &len, file)) != -1) {
  136. line[readed-1] = '\0';
  137. ret += files_add_file(files, line);
  138. }
  139. free(line);
  140. fclose(file);
  141. return ret;
  142. }
  143. static int files_load_glob(struct files *files, char *glob_match) {
  144. int i, entries, ret = 0;
  145. struct dirent **namelist;
  146. char *f1 = strdup(glob_match);
  147. char *f2 = strdup(glob_match);
  148. char *dir = dirname(f1);
  149. char *match = basename(f2);
  150. if (debug)
  151. fprintf(stderr, "dir:%s match:%s req:%s\n", dir, match, glob_match);
  152. entries = scandir(dir, &namelist, NULL, alphasort);
  153. if (entries < 0) {
  154. fprintf(stderr, "scandir %s : %s\n", dir, strerror(errno));
  155. free(f1);
  156. free(f2);
  157. return 0;
  158. }
  159. char *filename = calloc(1, strlen(dir) + NAME_MAX + 16);
  160. for (i=0;i<entries;i++) {
  161. struct dirent *entry = namelist[i];
  162. if (fnmatch(match, entry->d_name, FNM_PATHNAME) == 0) {
  163. sprintf(filename, "%s/%s", dir, entry->d_name);
  164. ret += files_add_file(files, filename);
  165. }
  166. free(entry);
  167. }
  168. free(filename);
  169. free(namelist);
  170. free(f1);
  171. free(f2);
  172. return ret;
  173. }
  174. static int fuse_getattr(const char *path, struct stat *stbuf) {
  175. if (strcmp(path, "/") != 0)
  176. return -ENOENT;
  177. if (fstat(filelist->data[0]->fd, stbuf) == -1)
  178. return -errno;
  179. stbuf->st_size = filelist->total_size;
  180. return 0;
  181. }
  182. static int fuse_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) {
  183. (void)fi; // prevent warning
  184. int i;
  185. struct fileinfo *file = filelist->data[0];
  186. ssize_t readen, totreaden = 0;
  187. off_t fileofs = offset, passed = 0;
  188. int filenum = 0;
  189. if (strcmp(path, "/") != 0)
  190. return -ENOENT;
  191. if (offset >= filelist->total_size) // Hmm :)
  192. return 0;
  193. if (offset + size > filelist->total_size) { // Asking for too much
  194. if (filelist->total_size < offset) // Prevent overflow
  195. return 0;
  196. size = filelist->total_size - offset;
  197. }
  198. if (offset > file->size) { // After the first file
  199. // Find the file that corresponds to the required offset
  200. // Can be slow with lots of files, but do it simple for now
  201. for (i=0; i<filelist->num_files; i++) {
  202. struct fileinfo *f = filelist->data[i];
  203. passed += f->size;
  204. if (passed >= offset) {
  205. file = f;
  206. filenum = i;
  207. fileofs = file->size - (passed - offset);
  208. break;
  209. }
  210. }
  211. }
  212. // Read all data
  213. do {
  214. readen = pread(file->fd, buf, size, fileofs);
  215. if (readen == -1) // Error reading
  216. return -errno;
  217. totreaden += readen;
  218. fileofs += readen;
  219. buf += readen;
  220. size -= readen;
  221. if (fileofs >= file->size) {
  222. fileofs = 0;
  223. filenum++;
  224. if (filenum >= filelist->num_files) {
  225. break;
  226. }
  227. file = filelist->data[filenum];
  228. }
  229. } while(size > 0);
  230. return totreaden;
  231. }
  232. static int fjfs_unlink(const char *path) {
  233. if (strcmp(path, "/") != 0)
  234. return -ENOENT;
  235. return unlink(path);
  236. }
  237. static void fjfs_destroy(void *f __attribute__((unused))) {
  238. if (mountpoint_created)
  239. unlink(mountpoint);
  240. }
  241. static struct fuse_operations concatfs_op = {
  242. .getattr = fuse_getattr,
  243. .read = fuse_read,
  244. .unlink = fjfs_unlink,
  245. .destroy = fjfs_destroy,
  246. };
  247. static const char *short_options = "fgaohd";
  248. static const struct option long_options[] = {
  249. { "file", no_argument, NULL, 'f' },
  250. { "glob", no_argument, NULL, 'g' },
  251. { "args", no_argument, NULL, 'a' },
  252. { "allow-other", no_argument, NULL, 'o' },
  253. { "help", no_argument, NULL, 'h' },
  254. { "debug", no_argument, NULL, 'd' },
  255. { 0, 0, 0, 0 }
  256. };
  257. static void show_usage(void) {
  258. printf("fjfs - FUSE module for virtual joining of multiple files into one.\n");
  259. printf("\n");
  260. printf("Usage: fjfs [file-list-options] [options] mount-point-file file-list\n");
  261. printf("\n");
  262. printf("Note: file-list depends on the options described bellow.\n");
  263. printf("\n");
  264. printf("File list options:\n");
  265. printf(" -f --file | file-list is text file containing list of files (default).\n");
  266. printf(" -g --glob | file-list is glob (*, ?, dir/file*).\n");
  267. printf(" -a --args | file-list is N filenames (file1 file2 fileX).\n");
  268. printf("\n");
  269. printf("Examples:\n");
  270. printf(" - Join files listed in filelist.txt as test-mount.txt\n");
  271. printf(" fjfs test-mount.txt filelist.txt\n");
  272. printf("\n");
  273. printf(" - Join files named testfile*.txt as test-mount.txt\n");
  274. printf(" fjfs --glob test-mount.txt 'testfile*.txt'\n");
  275. printf("\n");
  276. printf(" - Join files named testfileX.txt testfileY.txt testfileZ.txt as test-mount.txt\n");
  277. printf(" fjfs --args test-mount.txt testfileX.txt testfileY.txt testfileZ.txt\n");
  278. printf("\n");
  279. printf("Other options:\n");
  280. printf(" -o --allow-other | Mount FUSE with allow_other option. This allows other users\n");
  281. printf(" . to access the mounted fjfs instance. /etc/fuse.conf must\n");
  282. printf(" . contain \"user_allow_other\" in order for this option to work.\n");
  283. }
  284. static void parse_parameters(int argc, char *argv[]) {
  285. int j;
  286. while ( (j = getopt_long(argc, argv, short_options, long_options, NULL)) != -1 ) {
  287. switch (j) {
  288. case 'f': list_mode = FL_FILE; break;
  289. case 'g': list_mode = FL_GLOB; break;
  290. case 'a': list_mode = FL_ARGS; break;
  291. case 'd': debug = 1; break;
  292. case 'o': allow_other = 1; break;
  293. case 'h':
  294. show_usage();
  295. exit(EXIT_SUCCESS);
  296. break;
  297. }
  298. }
  299. mountpoint = argv[optind];
  300. filenames = argv[optind + 1];
  301. if (!mountpoint || !filenames) {
  302. show_usage();
  303. exit(EXIT_FAILURE);
  304. }
  305. if (debug) {
  306. fprintf(stderr, "mount point: %s\n", mountpoint);
  307. switch (list_mode) {
  308. case FL_FILE:
  309. fprintf(stderr, "list (file) : %s\n", filenames);
  310. break;
  311. case FL_GLOB:
  312. fprintf(stderr, "list (glob) : %s\n", filenames);
  313. break;
  314. case FL_ARGS:
  315. fprintf(stderr, "list (args) :");
  316. for (j = optind + 1; j < argc; j++) {
  317. fprintf(stderr, " %s", argv[j]);
  318. }
  319. fprintf(stderr, "\n");
  320. break;
  321. }
  322. }
  323. }
  324. static int init_filelist(int argc, char *argv[]) {
  325. int i, ret = 0;
  326. filelist = files_alloc();
  327. if (!filelist)
  328. return ret;
  329. switch (list_mode) {
  330. case FL_FILE:
  331. ret = files_load_filelist(filelist, filenames);
  332. break;
  333. case FL_GLOB:
  334. ret = files_load_glob(filelist, filenames);
  335. break;
  336. case FL_ARGS:
  337. for (i = optind + 1; i < argc; i++) {
  338. ret += files_add_file(filelist, argv[i]);
  339. }
  340. break;
  341. }
  342. if (debug)
  343. files_dump(filelist);
  344. if (!ret)
  345. fprintf(stderr, "ERROR: No files were selected for joining.\n");
  346. return ret;
  347. }
  348. static int mount_fuse(char *program_file) {
  349. char *fuse_argv[5];
  350. fuse_argv[0] = program_file;
  351. fuse_argv[1] = mountpoint;
  352. fuse_argv[2] = "-o";
  353. if (!allow_other)
  354. fuse_argv[3] = "nonempty,fsname=fjfs";
  355. else
  356. fuse_argv[3] = "nonempty,allow_other,fsname=fjfs";
  357. fuse_argv[4] = 0;
  358. return fuse_main(4, fuse_argv, &concatfs_op, NULL);
  359. }
  360. int main(int argc, char *argv[]) {
  361. int ret = EXIT_FAILURE;
  362. struct stat sb;
  363. parse_parameters(argc, argv);
  364. if (stat(mountpoint, &sb) == -1) {
  365. FILE *f = fopen(mountpoint, "wb");
  366. if (f) {
  367. mountpoint_created = 1;
  368. fclose(f);
  369. } else {
  370. fprintf(stderr, "Can't create mount point %s : %s\n", mountpoint, strerror(errno));
  371. exit(EXIT_FAILURE);
  372. }
  373. } else {
  374. if (!S_ISREG(sb.st_mode)) {
  375. fprintf(stderr, "%s is not a file!\n", mountpoint);
  376. exit(EXIT_FAILURE);
  377. }
  378. }
  379. if (init_filelist(argc, argv))
  380. ret = mount_fuse(argv[0]);
  381. if (mountpoint_created)
  382. unlink(mountpoint);
  383. files_free(&filelist);
  384. return ret;
  385. }