// SpeedBreaker runtime. GPL-3.0-or-later (see COPYING). See portable.h. #include "portable.h" #include #include #include #include namespace portable { namespace { namespace fs = std::filesystem; // Normalized, no trailing separator. fs::path Normal(const fs::path& path) { fs::path p = path.lexically_normal(); while (p.has_relative_path() && p.filename().empty()) p = p.parent_path(); return p; } // `relative` stays inside the folder it's relative to (not "", ".", // or "../..."). bool Inside(const fs::path& relative) { return !relative.empty() && relative != "." && *relative.begin() != ".."; } } Layout Resolve(const std::filesystem::path& root, bool marker, bool supported) { Layout layout; if (!supported || !marker || root.empty() || !root.is_absolute()) return layout; layout.root = Normal(root); layout.data = layout.root / kDataFolder; layout.cache = layout.root / kCacheFolder; return layout; } bool MarkerIn(const std::filesystem::path& root) { std::error_code ec; return !root.empty() && fs::is_regular_file(root / kMarker, ec); } const Layout& Detect(const std::filesystem::path& root) { CurrentLayout() = Resolve(root, kSupported && MarkerIn(root)); return Current(); } std::string RecordText(const std::filesystem::path& folder, const Layout& layout) { fs::path path = Normal(folder); if (layout.Active()) if (fs::path relative = path.lexically_relative(layout.root); Inside(relative)) return relative.generic_string(); return path.string(); } std::optional ParseRecord(std::string_view text, const Layout& layout) { while (!text.empty() && (text.back() == '\n' || text.back() == '\r')) text.remove_suffix(1); if (text.empty()) return std::nullopt; fs::path path{ std::string(text) }; // (Records before v0.1.1 kept `--dest dir/`'s trailing separator.) if (path.is_absolute()) return Normal(path); if (!layout.Active()) return std::nullopt; fs::path relative = path.lexically_normal(); if (!Inside(relative) || relative.has_root_path()) return std::nullopt; return Normal(layout.root / relative); } int CheckWritable(const Layout& layout) { if (!layout.Active()) return 0; std::error_code ec; fs::create_directories(layout.data, ec); if (ec) { std::error_condition condition = ec.default_error_condition(); return condition.category() == std::generic_category() ? condition.value() : EIO; } // A real write: a read-only mount, a locked card or permissions all // show here, whatever the file system. fs::path probe = layout.data / ".write-check"; errno = 0; FILE* f = fopen(probe.string().c_str(), "wb"); if (!f) return errno ? errno : EACCES; bool ok = fputc('1', f) != EOF; int error = errno; ok = fclose(f) == 0 && ok; if (!ok && !error) error = errno; fs::remove(probe, ec); return ok ? 0 : (error ? error : EIO); } std::string Describe(const Layout& layout) { if (!layout.Active()) return {}; return std::format("[paths] portable mode ({} in {}): data in {}, caches in {}\n", kMarker, layout.root.string(), layout.data.string(), layout.cache.string()); } }