[
  {
    "path": "AUTHORS",
    "content": "Alexandre Bique <bique.alexandre@gmail.com>\nTor Arne Vestbø <torarnv@gmail.com>\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.16)\n\nproject(TMFS CXX)\n\nset(CMAKE_CXX_STANDARD 17)\n\nfind_package(PkgConfig)\npkg_check_modules(FUSE REQUIRED fuse)\n\nadd_definitions(${FUSE_CFLAGS} -Wall)\ninclude_directories(${Boost_INCLUDE_DIRS})\n\nadd_executable(tmfs\n  src/main.cc\n  src/readdir.cc\n  src/read.cc\n  src/readlink.cc\n  src/getattr.cc\n  src/get_real_path.cc\n)\n\nlink_directories(${FUSE_LIBRARY_DIRS})\ntarget_link_libraries(tmfs ${FUSE_LIBRARIES})\ninstall(TARGETS tmfs RUNTIME DESTINATION bin\n  PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE\n              GROUP_READ GROUP_EXECUTE\n              WORLD_READ WORLD_EXECUTE)\n"
  },
  {
    "path": "ChangeLog",
    "content": "2013-03-12  Alexandre Bique  <bique.alexandre@gmail.com>\n\n\t* release: 4\n\t* dependancies: need boost >= 1.42\n\t* improves readme and help message\n\t* handle hard-links inside of .HFS+ Private Directory Data\n\n2012-07-23  Alexandre Bique  <bique.alexandre@gmail.com>\n\n\t* release: 3\n\n2012-03-21  Alexandre Bique  <bique.alexandre@gmail.com>\n\n\t* license: change to MIT\n\n2011-07-08  Alexandre Bique  <bique.alexandre@gmail.com>\n\n\t* release: 2\n\t* dependancies: switched from C++0x to standard C++\n\t* release: 1\n\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (C) 2011-2012 Alexandre Bique\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.markdown",
    "content": "TMFS\n====\n\n```\n__/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\___/\\\\\\\\____________/\\\\\\\\___/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\___/\\\\\\\\\\\\\\\\\\\\\\___\n _\\///////\\\\\\/////___\\/\\\\\\\\\\\\________/\\\\\\\\\\\\__\\/\\\\\\///////////__/\\\\\\/////////\\\\\\_\n  _______\\/\\\\\\________\\/\\\\\\//\\\\\\____/\\\\\\//\\\\\\__\\/\\\\\\____________\\//\\\\\\______\\///__\n   _______\\/\\\\\\________\\/\\\\\\\\///\\\\\\/\\\\\\/_\\/\\\\\\__\\/\\\\\\\\\\\\\\\\\\\\\\_____\\////\\\\\\_________\n    _______\\/\\\\\\________\\/\\\\\\__\\///\\\\\\/___\\/\\\\\\__\\/\\\\\\///////_________\\////\\\\\\______\n     _______\\/\\\\\\________\\/\\\\\\____\\///_____\\/\\\\\\__\\/\\\\\\___________________\\////\\\\\\___\n      _______\\/\\\\\\________\\/\\\\\\_____________\\/\\\\\\__\\/\\\\\\____________/\\\\\\______\\//\\\\\\__\n       _______\\/\\\\\\________\\/\\\\\\_____________\\/\\\\\\__\\/\\\\\\___________\\///\\\\\\\\\\\\\\\\\\\\\\/___\n        _______\\///_________\\///______________\\///___\\///______________\\///////////_____\n```\n\nTime Machine File System is a read-only virtual filesystem which helps you to read your Apple's time machine backup.\n\nThis filesystem does not targets performances, it has been written for a friend who has lost his macbook and wants to recover its data on Linux.\n\nIt's actually not perfect, feel free to report bugs or suggestions at [https://github.com/abique/tmfs/issues][https://github.com/abique/tmfs/issues].\n\nEnjoy!\n\nHow to use it?\n--------------\n\nFirst you have to mount your HFS partition, by doing something like:\n\n`mount /dev/sdXX /mnt/hfs-root`\n\nThen as root run:\n\n`tmfs /mnt/hfs-root /mnt/tm-root -ouid=$(id -u),gid=$(id -g),allow_other`\n\nThen as a normal user, go to the directory /mnt/tm-root/ and enjoy your data! :-)\n\nSparsebundle Disk Images\n------------------------\n\nI haven't used it myself and I can't comment too much on it, but in short you should be able to mount a sparse bundle using https://github.com/torarnv/sparsebundlefs and then mount tmfs on top of it. Please have a look at their project page there should be further explanation.\n\nDependancies\n------------\n\n - c++ 17\n - cmake >= 3.16\n - fuse\n\nHow to build and install it?\n----------------------------\n\nManually, run these commands:\n\n```\nmkdir build\ncd build\ncmake -DCMAKE_INSTALL_PREFIX=/usr/local ..\nmake\nDESTDIR=install-test make install\n```\n\nThen if the installation looks ok to you in install-test/ do make install as root with `sudo make install`.\n\nInternals\n---------\n\nTime Machine structure:\n\nSnapshot root: `${hfs_root}/Backups.backupdb/${comp_name}/${date}/${disk_name}/`\n\nHardlink count equals to dir_id: `${hfs_root}/Backups.backupdb/${comp_name}/${date}/${disk_name}/.../Folder`\n\nReal folder with data: `${hfs_root}/.HFS+ Private Directory Data/dir_${dir_id}/`\n\nOur representation:\n\n`/${comp_name}/${date}/${disk_name}/${Real root}`\n"
  },
  {
    "path": "src/get_real_path.cc",
    "content": "#include \"tmfs.hh\"\n\nstatic std::string _get_real_path(const std::string & str)\n{\n  // use the relative path so that the real_path doesn't get replaced\n  const auto clean_path = fs::path(str).relative_path();\n\n  fs::path real_path(tmfs::instance().hfs_root());\n  real_path /= \"Backups.backupdb\"; // ${hfs_root}/Backups.backupdb/\n\n  // ok let's copy the 3 first part of the virtual path\n  // (${comp_name}, ${date}, ${disk_name})\n  auto it = clean_path.begin();\n  for (int i = 0; i < 3 && it != clean_path.end(); ++i, ++it)\n    real_path /= *it;\n\n  // let's resolv all the parts of the path\n  struct stat stbuf;\n  for (; it != clean_path.end(); ++it)\n  {\n    real_path /= *it;\n    // Does the file exists ?\n    if (lstat(real_path.string().c_str(), &stbuf))\n      return real_path.string();\n\n    // Is the file a dir_id ?\n    if (S_ISREG(stbuf.st_mode) && stbuf.st_size == 0 && stbuf.st_nlink > 0)\n    {\n      // build the real path\n      fs::path dir_path = tmfs::instance().hfs_root();\n      dir_path /= \".HFS+ Private Directory Data\\r/dir_\" + std::to_string(stbuf.st_nlink);\n\n      // check if it's really a ${dir_id}\n      if (stat(dir_path.c_str(), &stbuf))\n        continue; // it's not\n      real_path = dir_path; // it is\n    }\n  }\n  return real_path.string();\n}\n\nstd::string get_real_path(const std::string & str)\n{\n  auto result = _get_real_path(str);\n#ifndef NDEBUG\n  std::cout << \"get_real_path(\\\"\" << str << \"\\\") -> \"  << result << std::endl;\n#endif\n  return result;\n}\n"
  },
  {
    "path": "src/getattr.cc",
    "content": "#include \"tmfs.hh\"\n\nint tmfs_getattr(const char *path, struct stat *stbuf)\n{\n  // get the real path\n  std::string real_path = get_real_path(path);\n\n  // and now just stat the real path\n  memset(stbuf, 0, sizeof(struct stat));\n  if (lstat(real_path.c_str(), stbuf))\n    return -errno;\n  return 0;\n}\n"
  },
  {
    "path": "src/main.cc",
    "content": "#include \"tmfs.hh\"\n\nint main(int argc, char ** argv)\n{\n  if (argc < 3)\n  {\n    fprintf(stderr, \"Usage: %s: <HFS+ mount point> <Time Machine mount point>\"\n            \" [FUSE options]\\n\", argv[0]);\n    return 2;\n  }\n\n  /* global structure setup */\n  tmfs::instance().hfs_root_ = fs::absolute(fs::path(argv[1]));\n  --argc;\n  for (int i = 1; i < argc; ++i)\n    argv[i] = argv[i + 1];\n\n  /* check that hfs_root is a directory */\n  struct stat st;\n  if (lstat(tmfs::instance().hfs_root_.c_str(), &st)) {\n    fprintf(stderr, \"%s: %m\\n\", tmfs::instance().hfs_root_.c_str());\n    return 1;\n  }\n\n  if (!S_ISDIR(st.st_mode)) {\n    fprintf(stderr, \"%s: is not a directory\\n\", tmfs::instance().hfs_root_.c_str());\n    return 1;\n  }\n\n  /* vtable setup */\n  struct fuse_operations ops;\n  memset(&ops, 0, sizeof (ops));\n  ops.read    = tmfs_read;\n  ops.getattr = tmfs_getattr;\n  ops.readdir = tmfs_readdir;\n  ops.readlink = tmfs_readlink;\n\n  /* lets go */\n  fuse_main(argc, argv, &ops, NULL);\n  return 0;\n}\n"
  },
  {
    "path": "src/read.cc",
    "content": "#include \"tmfs.hh\"\n\nint tmfs_read(const char * path, char * buf, size_t nbytes, off_t offset,\n              struct fuse_file_info * fi)\n{\n  // get the real path\n  std::string real_path = get_real_path(path);\n\n  // open the file\n  int fd = open(real_path.c_str(), O_RDONLY);\n  if (fd < 0)\n    return -errno;\n\n  // read the data and close\n  ssize_t bytes = pread(fd, buf, nbytes, offset);\n  close(fd);\n  if (bytes < 0)\n    return -errno;\n  return bytes;\n}\n"
  },
  {
    "path": "src/readdir.cc",
    "content": "#include \"tmfs.hh\"\n\nint tmfs_readdir(const char * path, void * buf, fuse_fill_dir_t filler_cb, off_t offset,\n                 struct fuse_file_info * fi)\n{\n  // get the real path\n  std::string real_path = get_real_path(path);\n\n  // checks if it's really a directory\n  if (!fs::is_directory(real_path))\n    return -ENOTDIR;\n\n  struct stat stbuf;\n\n  // report ./ and ../\n  stbuf.st_mode = S_IFDIR | 0755;\n  stbuf.st_nlink = 2;\n  filler_cb(buf, \".\", &stbuf, 0);\n  filler_cb(buf, \"..\", &stbuf, 0);\n\n  // now iterate over the real directory\n  DIR * dir = opendir(real_path.c_str());\n  if (!dir)\n    return 0;\n\n  struct dirent * entry;\n  while ((entry = readdir(dir)))\n  {\n    // stat the file pointed by entry\n    auto file_path = fs::path(path) / entry->d_name;\n    if (tmfs_getattr(file_path.string().c_str(), &stbuf))\n      continue;\n    stbuf.st_mode |= 0755;\n    // report the entry\n    filler_cb(buf, entry->d_name, &stbuf, 0);\n  }\n  closedir(dir);\n\n  return 0;\n}\n"
  },
  {
    "path": "src/readlink.cc",
    "content": "#include \"tmfs.hh\"\n\nint tmfs_readlink(const char * path, char * buf, size_t size)\n{\n  if (size < 1)\n    return 0;\n\n  // get the real path\n  std::string real_path = get_real_path(path);\n\n  // and now just readlink the real path\n  ssize_t result = readlink(real_path.c_str(), buf, size - 1);\n  if (result < 0)\n    return -errno;\n  buf[result] = 0;\n  return 0;\n}\n"
  },
  {
    "path": "src/tmfs.hh",
    "content": "#pragma once\n\n#define FUSE_USE_VERSION 26\n\n#include <sys/time.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n#include <errno.h>\n#include <dirent.h>\n\n#include <fuse.h>\n\n#include <map>\n#include <string>\n#include <vector>\n#include <sstream>\n#include <iostream>\n\n#include <filesystem>\n\nnamespace fs = std::filesystem;\n\n/** this is the global structure of tmfs */\nstruct tmfs {\n  inline const fs::path & hfs_root() const noexcept { return hfs_root_; }\n  static inline tmfs & instance() { static tmfs i; return i; }\n\n  fs::path  hfs_root_; // the hfs root\n};\n\n/** transforms a virtual paths in the tmfs's root to the real path in hfs's root */\nstd::string get_real_path(const std::string & path);\n\n/** fuse functions\n * @{ */\nint tmfs_getattr(const char * path, struct stat *stbuf);\nint tmfs_readdir(const char * path, void * buf, fuse_fill_dir_t filler_callback,\n                 off_t offset, struct fuse_file_info * fi);\nint tmfs_read(const char * path, char * buf, size_t nbytes, off_t offset,\n              struct fuse_file_info * fi);\nint tmfs_readlink(const char * path, char * buf, size_t size);\n/** @} */\n"
  }
]