Repository navigation
Expand file tree
/
Copy pathfs.cpp
More file actions
193 lines (173 loc) · 6.67 KB
/
Copy pathfs.cpp
File metadata and controls
193 lines (173 loc) · 6.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
/**
* Filesystem helpers implementation. See include/ragger/util/fs.h.
*/
#include "util/fs.h"
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <filesystem>
#include <fstream>
#include <iterator>
#include <stdexcept>
#include <pwd.h>
#include <unistd.h>
namespace fs = std::filesystem;
namespace ragger {
std::string read_file_to_string(const std::string& path) {
std::ifstream f(path);
if (!f.is_open()) return {};
return std::string((std::istreambuf_iterator<char>(f)),
std::istreambuf_iterator<char>());
}
std::string home_dir() {
const char* home = std::getenv("HOME");
if (home && *home) return home;
// Fallback: passwd entry for the current uid (e.g. when HOME is unset
// under a daemon).
struct passwd* pw = getpwuid(getuid());
if (pw && pw->pw_dir) return pw->pw_dir;
return {};
}
std::string collapse_home(const std::string& path) {
std::string home = home_dir();
if (home.empty()) return path;
// Ensure home doesn't end with '/' for a clean prefix check.
if (home.back() == '/') home.pop_back();
if (path.size() > home.size() &&
path.compare(0, home.size(), home) == 0 &&
path[home.size()] == '/') {
return "~" + path.substr(home.size());
}
if (path == home) return "~";
return path;
}
// Testing-only base-dir override, set once at startup from the hidden
// --ragger-base CLI flag. No env var equivalent — CLI-only, by design.
static std::string _ragger_base_override;
void set_ragger_base_override(const std::string& path) {
_ragger_base_override = path;
}
std::string ragger_base_dir() {
if (!_ragger_base_override.empty()) return _ragger_base_override;
std::string home = home_dir();
if (home.empty()) return {};
return home + "/.ragger";
}
namespace {
std::string shell_quote_path(const std::string& s) {
std::string out = "'";
for (char c : s) {
if (c == '\'') out += "'\\''";
else out += c;
}
out += "'";
return out;
}
} // namespace
std::string archive_db_files(const std::string& db_path,
const std::vector<std::string>& extra_db_paths) {
fs::path src(db_path);
std::string stem = src.stem().string(); // e.g. "memories"
std::string ts;
{
std::time_t now = std::time(nullptr);
std::tm tmv{};
#if defined(_WIN32)
localtime_s(&tmv, &now);
#else
localtime_r(&now, &tmv);
#endif
char buf[32];
std::strftime(buf, sizeof(buf), "%Y%m%d-%H%M%S", &tmv);
ts = buf;
}
std::string prefix = stem + "_BACKUP_" + ts;
fs::path parent = src.parent_path();
// Collect every file to bundle as (containing_dir, filename) pairs: the
// primary DB + its -wal/-shm siblings, then the same for each extra DB
// path (e.g. stats.db). A path that doesn't exist is silently skipped --
// an optional sibling DB (or one with no WAL/SHM at rest) isn't an error.
struct FileRef { fs::path dir; std::string name; };
std::vector<FileRef> files;
auto add_db_and_siblings = [&](const std::string& path) {
fs::path p(path);
if (!fs::exists(p)) return;
fs::path dir = p.parent_path();
std::string name = p.filename().string();
files.push_back({dir, name});
for (const char* suffix : {"-wal", "-shm"}) {
fs::path sib = dir / (name + suffix);
if (fs::exists(sib)) files.push_back({dir, name + suffix});
}
};
add_db_and_siblings(db_path);
for (const auto& extra : extra_db_paths) add_db_and_siblings(extra);
// -- tar.gz -- (-C per entry lets bsdtar/GNU tar pull files from
// different source directories into one flat archive)
{
fs::path tar_path = parent / (prefix + ".tar.gz");
std::string cmd = "tar -czf " + shell_quote_path(tar_path.string());
for (const auto& f : files) {
cmd += " -C " + shell_quote_path(f.dir.string()) +
" " + shell_quote_path(f.name);
}
if (std::system(cmd.c_str()) == 0 && fs::exists(tar_path)) {
return tar_path.string();
}
}
// -- zip fallback -- (zip has no per-file -C; cd into each dir and
// append, since the source dirs may differ across files)
{
fs::path zip_path = parent / (prefix + ".zip");
bool ok = true;
for (const auto& f : files) {
std::string cmd = "cd " + shell_quote_path(f.dir.string()) +
" && zip -q " + shell_quote_path(zip_path.string()) +
" -j " + shell_quote_path(f.name);
if (std::system(cmd.c_str()) != 0) { ok = false; break; }
}
if (ok && fs::exists(zip_path)) {
return zip_path.string();
}
}
// -- plain copy fallback (primary DB + its siblings only -- extras are
// best-effort and skipped here since there's no archive to bundle them
// into; tar/zip failing at all is already an unusual environment) --
std::string db_filename = src.filename().string();
std::string wal_filename = db_filename + "-wal";
std::string shm_filename = db_filename + "-shm";
bool has_wal = fs::exists(parent / wal_filename);
bool has_shm = fs::exists(parent / shm_filename);
fs::path copy_path = parent / (prefix + ".db");
if (fs::exists(copy_path)) {
throw std::runtime_error(
"backup target already exists: " + copy_path.string());
}
fs::copy_file(src, copy_path);
if (has_wal) fs::copy_file(parent / wal_filename, parent / (prefix + ".db-wal"));
if (has_shm) fs::copy_file(parent / shm_filename, parent / (prefix + ".db-shm"));
return copy_path.string();
}
bool is_safe_model_repo(std::string_view repo) {
if (repo.empty() || repo.size() > 200) return false;
if (repo.find('\\') != std::string_view::npos) return false;
if (repo.find('\0') != std::string_view::npos) return false;
// Exactly one '/', both sides non-empty.
const size_t slash = repo.find('/');
if (slash == std::string_view::npos) return false;
if (repo.find('/', slash + 1) != std::string_view::npos) return false;
const std::string_view provider = repo.substr(0, slash);
const std::string_view model = repo.substr(slash + 1);
if (provider.empty() || model.empty()) return false;
auto part_ok = [](std::string_view p) {
if (p == "." || p == "..") return false;
for (char c : p) {
const bool ok = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') || c == '.' || c == '_' || c == '-';
if (!ok) return false;
}
return true;
};
return part_ok(provider) && part_ok(model);
}
} // namespace ragger