#include //in tarfs_instance, node_id_t and directory_iter_t refer to the number //of bytes into the block device that the info sector is located. namespace mercury::kernel::storage::fs { #define BD_TO_FS(expr) \ { \ bd_result _result = expr; \ if (_result == bd_result::out_of_bounds) \ return fs_result::fs_corrupt; \ if (_result == bd_result::device_error) \ return fs_result::device_error; \ } #define FS_TO_FS(expr) \ { \ fs_result _result = expr; \ if (_result != fs_result::success) \ return _result; \ } tarfs_instance::tarfs_instance(block_device *bd) : bd(bd) {} fs_result tarfs_instance::next_node( node_id_t node, std::optional &out ) { uint64_t bytes; FS_TO_FS(read_num(node + 124, 12, bytes)) out = node + ((bytes - 1) / 512 + 2) * 512; uint8_t sector[512]; BD_TO_FS(bd->read_bytes(node, 512, sector)) for (unsigned i = 0; i < 512; ++i) if (sector[i] != 0) return fs_result::success; out = {}; return fs_result::success; } fs_result tarfs_instance::read_full_name( node_id_t node, utility::string &out ) { out.count = 0; out.verify_buffer_len(155); BD_TO_FS(bd->read_bytes(node + 345, 155, out.buffer)) while (out.count < 155 && out.buffer[out.count] != '\0') ++out.count; unsigned new_max = out.count + 100; out.verify_buffer_len(new_max); BD_TO_FS(bd->read_bytes(node, 100, out.buffer + out.count)) while (out.count < 255 && out.buffer[out.count] != '\0') ++out.count; return fs_result::success; } //len <= 12. fs_result tarfs_instance::read_num( uint64_t offset, unsigned len, uint64_t &out ) { char buf[12]; BD_TO_FS(bd->read_bytes(offset, len, buf)) out = 0; for (unsigned i = 0; i < len && buf[i] != '\0'; ++i) { if (buf[i] < '0' || buf[i] > '7') return fs_result::fs_corrupt; out = out * 8 + buf[i] - '0'; } return fs_result::success; } fs_result tarfs_instance::first_child_starting_at( node_id_t parent, node_id_t start, std::optional &out ) { utility::string parent_full_name; FS_TO_FS(read_full_name(parent, parent_full_name)) utility::string child_full_name; out = start; do { FS_TO_FS(read_full_name(*out, child_full_name)) if (child_full_name.count > parent_full_name.count && child_full_name.starts_with(parent_full_name) ) { if (child_full_name.buffer[child_full_name.count - 1] == '/') --child_full_name.count; for (unsigned i = parent_full_name.count; i < child_full_name.count; ++i) if (child_full_name.buffer[i] == '/') goto next; return fs_result::success; } next: next_node(*out, out); } while (out); return fs_result::success; } fs_result tarfs_instance::get_dir_entry(node_id_t node, dir_entry &entry) { utility::string full_name; read_full_name(node, full_name); if (full_name.count == 2) entry.name.count = 0; else { if (full_name.buffer[full_name.count - 1] == '/') --full_name.count; unsigned last_slash = utility::find_last(full_name.buffer, full_name.count, '/'); entry.name.count = full_name.count - last_slash - 1; entry.name.verify_buffer_len(entry.name.count); for (unsigned i = 0; i < entry.name.count; ++i) entry.name.buffer[i] = full_name.buffer[last_slash + 1 + i]; } entry.node = node; char ch; BD_TO_FS(bd->read_bytes(node + 156, 1, &ch)); switch (ch) { case '0': entry.type = file_type::regular_file; break; case '2': entry.type = file_type::symlink; break; case '5': entry.type = file_type::directory; break; default: return fs_result::fs_corrupt; } if (entry.type == file_type::regular_file) { uint64_t length; FS_TO_FS(read_num(node + 124, 12, length)) entry.length = length; } else if (entry.type == file_type::symlink) { utility::string target; target.verify_buffer_len(100); BD_TO_FS(bd->read_bytes(node + 157, 100, target.buffer)) while (target.count < 100 && target.buffer[target.count] != '\0') ++target.count; entry.target = std::move(target); } return fs_result::success; } fs_result tarfs_instance::get_root_node(node_id_t &out) { utility::string full_name; std::optional on = 0; do { FS_TO_FS(read_full_name(*on, full_name)) if (full_name.count == 2) { out = *on; return fs_result::success; } next_node(*on, on); } while (on); return fs_result::fs_corrupt; } fs_result tarfs_instance::get_first_child( node_id_t node, std::optional &out, directory_iter_t &iter_out ) { std::optional child; FS_TO_FS(first_child_starting_at(node, 0, child)) if (!child) { out = {}; return fs_result::success; } dir_entry entry; FS_TO_FS(get_dir_entry(*child, entry)) out = std::move(entry); iter_out = (directory_iter_t)*child; return fs_result::success; } fs_result tarfs_instance::get_next_child( node_id_t node, std::optional &out, directory_iter_t &iter ) { std::optional start; FS_TO_FS(next_node((node_id_t)iter, start)) if (!start) { out = {}; return fs_result::success; } std::optional child; FS_TO_FS(first_child_starting_at(node, *start, child)) if (!child) { out = {}; return fs_result::success; } dir_entry entry; FS_TO_FS(get_dir_entry(*child, entry)) out = std::move(entry); iter = (directory_iter_t)*child; return fs_result::success; } fs_result tarfs_instance::read_bytes_from_file( node_id_t node, uint64_t start, uint64_t count, void *into ) { BD_TO_FS(bd->read_bytes(node + 512 + start, count, into)) return fs_result::success; } }