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

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