How to search for all hardlinks associated with a file descriptor on the filesystem?
CodePudding user response:
A more platform-specific approach here:
https://stackoverflow.com/a/70728234/4821390
A header that needs either C 17 onward or ghc::filesystem compiled with -DUSE_GHC_FILESYSTEM if you can't use std::filesystem. ghc::filesystem here:
https://github.com/gulrak/filesystem
Tested Windows, macOS, Linux, FreeBSD, DragonFly BSD, OpenBSD, and Emscripten. NetBSD, Illumos, Android, iOS, and other POSIX-compliant might be supported.
findhardlinks.hpp:
#include <string>
#include <vector>
#if !defined(USE_GHC_FILESYSTEM)
#include <filesystem>
#endif
#include <cstdlib>
#include <cstring>
#if defined(_WIN32)
#include <cwchar>
#endif
#if defined(USE_GHC_FILESYSTEM)
#include "filesystem.hpp"
#endif
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#if defined(_WIN32)
#include <windows.h>
#include <share.h>
#include <io.h>
#else
#include <unistd.h>
#endif
namespace findhardlinks {
#if defined(USE_GHC_FILESYSTEM)
namespace fs = ghc::filesystem;
#else
namespace fs = std::filesystem;
#endif
namespace {
/* necessary in GUI Windows applications to process window
clicks without crashing during lasting for/while loops. */
inline void message_pump() {
#if defined(_WIN32)
MSG msg; while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
#endif
}
#if defined(_WIN32)
// UTF-8 support on Windows: string to wstring.
inline std::wstring widen(std::string str) {
std::size_t wchar_count = str.size() 1; std::vector<wchar_t> buf(wchar_count);
return std::wstring{ buf.data(), (std::size_t)MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, buf.data(), (int)wchar_count) };
}
// UTF-8 support on Windows: wstring to string.
inline std::string narrow(std::wstring wstr) {
int nbytes = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int)wstr.length(), nullptr, 0, nullptr, nullptr); std::vector<char> buf(nbytes);
return std::string{ buf.data(), (std::size_t)WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int)wstr.length(), buf.data(), nbytes, nullptr, nullptr) };
}
#endif
// optional: get environment variable value.
inline std::string environment_get_variable(std::string name) {
#if defined(_WIN32)
std::string value;
wchar_t buffer[32767];
std::wstring u8name = widen(name);
if (GetEnvironmentVariableW(u8name.c_str(), buffer, 32767) != 0) {
value = narrow(buffer);
}
return value;
#else
char *value = getenv(name.c_str());
return value ? value : "";
#endif
}
// optional: check if environment variable exists.
inline bool environment_get_variable_exists(std::string name) {
#if defined(_WIN32)
std::string value;
wchar_t buffer[32767];
std::wstring u8name = widen(name);
GetEnvironmentVariableW(u8name.c_str(), buffer, 32767);
return (GetLastError() != ERROR_ENVVAR_NOT_FOUND);
#else
return (getenv(name.c_str()) != nullptr);
#endif
}
// optional: expand ${ENVVAR} in strings.
inline std::string environment_expand_variables(std::string str) {
if (str.find("${") == std::string::npos) return str;
std::string pre = str.substr(0, str.find("${"));
std::string post = str.substr(str.find("${") 2);
if (post.find('}') == std::string::npos) return str;
std::string variable = post.substr(0, post.find('}'));
std::size_t pos = post.find('}') 1; post = post.substr(pos);
std::string value = environment_get_variable(variable);
if (!environment_get_variable_exists(variable))
return str.substr(0, pos) environment_expand_variables(str.substr(pos));
return environment_expand_variables(pre value post);
}
/* force absolute path and remove trailing slashes;
keep trailing slash at the end if drive/fs root. */
inline std::string expand_without_trailing_slash(std::string dname) {
std::error_code ec;
dname = environment_expand_variables(dname);
fs::path p = fs::path(dname);
p = fs::absolute(p, ec);
if (ec.value() != 0) return "";
dname = p.string();
#if defined(_WIN32)
while ((dname.back() == '\\' || dname.back() == '/') &&
(p.root_name().string() "\\" != dname && p.root_name().string() "/" != dname)) {
message_pump(); p = fs::path(dname); dname.pop_back();
}
#else
while (dname.back() == '/' && (!dname.empty() && dname[0] != '/' && dname.length() != 1)) {
dname.pop_back();
}
#endif
return dname;
}
// force absolute path and add trailing slash.
inline std::string expand_with_trailing_slash(std::string dname) {
dname = expand_without_trailing_slash(dname);
#if defined(_WIN32)
if (dname.back() != '\\') dname = "\\";
#else
if (dname.back() != '/') dname = "/";
#endif
return dname;
}
// check if regular file (non-directory) exists.
inline bool file_exists(std::string fname) {
std::error_code ec;
fname = expand_without_trailing_slash(fname);
const fs::path path = fs::path(fname);
return (fs::exists(path, ec) && ec.value() == 0 &&
(!fs::is_directory(path, ec)) && ec.value() == 0);
}
// check if directory exists.
inline bool directory_exists(std::string dname) {
std::error_code ec;
dname = expand_without_trailing_slash(dname);
dname = expand_without_trailing_slash(dname);
const fs::path path = fs::path(dname);
return (fs::exists(path, ec) && ec.value() == 0 &&
fs::is_directory(path, ec) && ec.value() == 0);
}
// convert relative path to absolute path, when applicable.
inline std::string filename_absolute(std::string fname) {
std::string result;
if (directory_exists(fname)) {
result = expand_with_trailing_slash(fname);
} else if (file_exists(fname)) {
result = expand_without_trailing_slash(fname);
}
return result;
}
// find hardlinks helper struct.
struct findhardlinks_struct {
std::vector<std::string> x;
std::vector<std::string> y;
bool recursive;
unsigned i;
unsigned j;
#if defined(_WIN32)
BY_HANDLE_FILE_INFORMATION info;
#else
struct stat info;
#endif
};
/* find hardlinks directory iterator: search for equal files
like std::filesystem::equivalent but passing fd not path. */
std::vector<std::string> findhardlinks_result;
inline void findhardlinks_helper(findhardlinks_struct *s) {
#if defined(_WIN32)
if (findhardlinks_result.size() >= s->info.nNumberOfLinks) return;
#else
if (findhardlinks_result.size() >= s->info.st_nlink) return;
#endif
if (s->i < s->x.size()) {
std::error_code ec; if (!directory_exists(s->x[s->i])) return;
s->x[s->i] = expand_without_trailing_slash(s->x[s->i]);
const fs::path path = fs::path(s->x[s->i]);
if (directory_exists(s->x[s->i]) || path.root_name().string() "\\" == path.string()) {
fs::directory_iterator end_itr;
for (fs::directory_iterator dir_ite(path, ec); dir_ite != end_itr; dir_ite.increment(ec)) {
message_pump(); if (ec.value() != 0) { break; }
fs::path file_path = fs::path(filename_absolute(dir_ite->path().string()));
#if defined(_WIN32)
int fd = -1;
BY_HANDLE_FILE_INFORMATION info = { 0 };
if (file_exists(file_path.string())) {
// printf("%s\n", file_path.string().c_str());
if (!_wsopen_s(&fd, file_path.wstring().c_str(), _O_RDONLY, _SH_DENYNO, _S_IREAD)) {
bool success = GetFileInformationByHandle((HANDLE)_get_osfhandle(fd), &info);
bool matches = (info.ftLastWriteTime.dwLowDateTime == s->info.ftLastWriteTime.dwLowDateTime &&
info.ftLastWriteTime.dwHighDateTime == s->info.ftLastWriteTime.dwHighDateTime &&
info.nFileSizeHigh == s->info.nFileSizeHigh && info.nFileSizeLow == s->info.nFileSizeLow &&
info.nFileSizeHigh == s->info.nFileSizeHigh && info.nFileSizeLow == s->info.nFileSizeLow &&
info.dwVolumeSerialNumber == s->info.dwVolumeSerialNumber);
if (matches && success) {
findhardlinks_result.push_back(file_path.string());
if (findhardlinks_result.size() >= info.nNumberOfLinks) {
s->info.nNumberOfLinks = info.nNumberOfLinks; s->x.clear();
_close(fd);
return;
}
}
_close(fd);
}
}
#else
struct stat info = { 0 };
if (file_exists(file_path.string())) {
// printf("%s\n", file_path.string().c_str());
if (!stat(file_path.string().c_str(), &info)) {
if (info.st_dev == s->info.st_dev && info.st_ino == s->info.st_ino &&
info.st_size == s->info.st_size && info.st_mtime == s->info.st_mtime) {
findhardlinks_result.push_back(file_path.string());
if (findhardlinks_result.size() >= info.st_nlink) {
s->info.st_nlink = info.st_nlink; s->x.clear();
return;
}
}
}
}
#endif
if (s->recursive && directory_exists(file_path.string())) {
// printf("%s\n", file_path.string().c_str());
s->x.push_back(file_path.string());
s->i ; findhardlinks_helper(s);
}
}
}
}
while (s->j < s->y.size() && directory_exists(s->y[s->j])) {
message_pump(); s->x.clear(); s->x.push_back(s->y[s->j]);
s->j ; findhardlinks_helper(s);
}
}
} // anonymous namespace
// the actual function to call.
inline std::vector<std::string> findhardlinks(int fd, std::vector<std::string> dnames, bool recursive) {
std::vector<std::string> paths;
#if defined(_WIN32)
BY_HANDLE_FILE_INFORMATION info = { 0 };
if (GetFileInformationByHandle((HANDLE)_get_osfhandle(fd), &info) && info.nNumberOfLinks) {
#else
struct stat info = { 0 };
if (!fstat(fd, &info) && info.st_nlink) {
#endif
findhardlinks_result.clear();
struct findhardlinks_struct s;
std::vector<std::string> first;
first.push_back(dnames[0]);
dnames.erase(dnames.begin());
s.x = first;
s.y = dnames;
s.i = 0;
s.j = 0;
s.recursive = recursive;
s.info = info;
findhardlinks_helper(&s);
paths = findhardlinks_result;
}
return paths;
}
} // namespace findhardlinks
example.cpp:
#include <cstdio>
#include <algorithm>
#include "findhardlinks.hpp"
#if defined(_WIN32)
#include <cwchar>
#endif
using std::string;
using std::vector;
using std::size_t;
#if defined(_WIN32)
using std::wstring;
#endif
namespace {
/* read, write, append
open() permissions. */
enum {
FD_RDONLY,
FD_WRONLY,
FD_RDWR,
FD_APPEND,
FD_RDAP
};
#if defined(_WIN32)
// UTF-8 support on Windows: string to wstring.
wstring widen(string str) {
size_t wchar_count = str.size() 1; vector<wchar_t> buf(wchar_count);
return wstring{ buf.data(), (size_t)MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, buf.data(), (int)wchar_count) };
}
// UTF-8 support on Windows: wstring to string.
string narrow(wstring wstr) {
int nbytes = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int)wstr.length(), nullptr, 0, nullptr, nullptr); vector<char> buf(nbytes);
return string{ buf.data(), (size_t)WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int)wstr.length(), buf.data(), nbytes, nullptr, nullptr) };
}
#endif
// replace all substrings with new substring in string.
string string_replace_all(string str, string substr, string nstr) {
size_t pos = 0;
while ((pos = str.find(substr, pos)) != string::npos) {
str.replace(pos, substr.length(), nstr);
pos = nstr.length();
}
return str;
}
// path without filename spec.
string filename_path(string fname) {
#if defined(_WIN32)
size_t fp = fname.find_last_of("\\/");
#else
size_t fp = fname.find_last_of("/");
#endif
if (fp == string::npos) return fname;
return fname.substr(0, fp 1);
}
// path without directory path spec.
string filename_name(string fname) {
#if defined(_WIN32)
size_t fp = fname.find_last_of("\\/");
#else
size_t fp = fname.find_last_of("/");
#endif
if (fp == string::npos) return fname;
return fname.substr(fp 1);
}
// get temporary directory.
string directory_get_temporary_path() {
std::error_code ec;
string result = findhardlinks::fs::temp_directory_path(ec).string();
if (result.back() != '/') result.push_back('/');
#if defined(_WIN32)
result = string_replace_all(result, "/", "\\");
#endif
return (ec.value() == 0) ? result : "";
}
// write string to file descriptor.
long file_write_string(int fd, string str) {
char *buffer = str.data();
#if defined(_WIN32)
long result = _write(fd, buffer, (unsigned)str.length());
#else
long result = write(fd, buffer, (unsigned)str.length());
#endif
return result;
}
/* create a temporary file with the string contents str.
filename suffix - replaces XXXXXX in buffer randomly. */
int file_open_from_string(string str) {
string fname = directory_get_temporary_path() "temp.XXXXXX";
#if defined(_WIN32)
int fd = -1; wstring wfname = widen(fname);
wchar_t *buffer = wfname.data(); if (_wmktemp_s(buffer, wfname.length() 1)) return -1;
if (_wsopen_s(&fd, buffer, _O_CREAT | _O_RDWR | _O_WTEXT, _SH_DENYNO, _S_IREAD | _S_IWRITE)) {
return -1;
}
#else
char *buffer = fname.data();
int fd = mkstemp(buffer);
#endif
if (fd == -1) return -1;
file_write_string(fd, str);
#if defined(_WIN32)
_lseek(fd, 0, SEEK_SET);
#else
lseek(fd, 0, SEEK_SET);
#endif
return fd;
}
// open file descriptor with mode (read, write, append, etc).
int file_open(string fname, int mode) {
#if defined(_WIN32)
wstring wfname = widen(fname);
FILE *fp = nullptr;
switch (mode) {
case 0: { if (!_wfopen_s(&fp, wfname.c_str(), L"rb, ccs=UTF-8" )) break; return -1; }
case 1: { if (!_wfopen_s(&fp, wfname.c_str(), L"wb, ccs=UTF-8" )) break; return -1; }
case 2: { if (!_wfopen_s(&fp, wfname.c_str(), L"w b, ccs=UTF-8")) break; return -1; }
case 3: { if (!_wfopen_s(&fp, wfname.c_str(), L"ab, ccs=UTF-8" )) break; return -1; }
case 4: { if (!_wfopen_s(&fp, wfname.c_str(), L"a b, ccs=UTF-8")) break; return -1; }
default: return -1;
}
if (fp) { int fd = _dup(_fileno(fp));
fclose(fp); return fd; }
#else
FILE *fp = nullptr;
switch (mode) {
case 0: { fp = fopen(fname.c_str(), "rb" ); break; }
case 1: { fp = fopen(fname.c_str(), "wb" ); break; }
case 2: { fp = fopen(fname.c_str(), "w b"); break; }
case 3: { fp = fopen(fname.c_str(), "ab" ); break; }
case 4: { fp = fopen(fname.c_str(), "a b"); break; }
default: return -1;
}
if (fp) { int fd = dup(fileno(fp));
fclose(fp); return fd; }
#endif
return -1;
}
// close file descriptor.
int file_close(int fd) {
#if defined(_WIN32)
return _close(fd);
#else
return close(fd);
#endif
}
// check if regular file (non-directory) exists.
bool file_exists(string fname) {
std::error_code ec;
const findhardlinks::fs::path path = findhardlinks::fs::path(fname);
return (findhardlinks::fs::exists(path, ec) && ec.value() == 0 &&
(!findhardlinks::fs::is_directory(path, ec)) && ec.value() == 0);
}
// delete file or hardlink.
bool file_delete(string fname) {
std::error_code ec;
if (!file_exists(fname)) return false;
const findhardlinks::fs::path path = findhardlinks::fs::path(fname);
return (findhardlinks::fs::remove(path, ec) && ec.value() == 0);
}
// check if directory exists.
bool directory_exists(string dname) {
std::error_code ec;
const findhardlinks::fs::path path = findhardlinks::fs::path(dname);
return (findhardlinks::fs::exists(path, ec) && ec.value() == 0 &&
findhardlinks::fs::is_directory(path, ec) && ec.value() == 0);
}
// create directory recursively.
bool directory_create(string dname) {
std::error_code ec;
const findhardlinks::fs::path path = findhardlinks::fs::path(dname);
return (findhardlinks::fs::create_directories(path, ec) && ec.value() == 0);
}
// rename or move file to name and/or destination.
bool file_rename(string oldname, string newname) {
std::error_code ec;
if (!file_exists(oldname)) return false;
if (!directory_exists(filename_path(newname)))
directory_create(filename_path(newname));
const findhardlinks::fs::path path1 = findhardlinks::fs::path(oldname);
const findhardlinks::fs::path path2 = findhardlinks::fs::path(newname);
findhardlinks::fs::rename(path1, path2, ec);
return (ec.value() == 0);
}
// create hardlink.
bool hardlink_create(string fname, string newname) {
if (file_exists(fname)) {
if (!directory_exists(filename_path(newname)))
directory_create(filename_path(newname));
#if defined(_WIN32)
std::error_code ec;
const findhardlinks::fs::path path1 = findhardlinks::fs::path(fname);
const findhardlinks::fs::path path2 = findhardlinks::fs::path(newname);
findhardlinks::fs::create_hard_link(path1, path2, ec);
return (ec.value() == 0);
#else
return (!link(fname.c_str(), newname.c_str()));
#endif
}
return false;
}
} // anonymous namespace
// our test case:
int main(int argc, char **argv) {
int fd = file_open_from_string(filename_name(argv[0] ? argv[0] : "(null)"));
if (fd == -1) {
return 1; // failure
}
vector<string> dnames;
dnames.push_back(directory_get_temporary_path());
vector<string> p = findhardlinks::findhardlinks(fd, dnames, false);
/* close fist because Windows won't allow us to
move/rename it when opened; win32-only issue */
file_close(fd);
if (p.empty()) {
return 1; // failure
}
// rename original file to have hardlink number
vector<string> p2;
p2.push_back(p[0] " - hardlink 00");
file_rename(p[0], p2[0]);
// if file exists, create 99 hardlinks
if (file_exists(p2[0])) {
for (unsigned i = 1; i < 100; i ) {
if (!hardlink_create(p2[0], p[0] " - hardlink " ((std::to_string(i).length() == 1) ? ("0" std::to_string(i)) : std::to_string(i)))) {
return 1; // failure
}
}
}
// open original hardlink again, read-only
fd = file_open(p2[0], FD_RDONLY);
if (fd == -1) {
return 1;
}
// get hardlinks
p2 = findhardlinks::findhardlinks(fd, dnames, false);
file_close(fd);
// sort alphabetically, numerically
std::sort(p2.begin(), p2.end());
// print hardlink filenames, then delete them.
for (unsigned i = 0; i < p2.size(); i ) {
printf("%s\n", p2[i].c_str());
file_delete(p2[i]);
}
// done:
return 0;
}
Above example deletes hardlinks after creating them. The dnames
argument of findhardlinks()
is a string vector to check multiple folders. If recursive, don't include both a folder and its subfolder to avoid redundancy. For non-command-line apps there's a function to automatically expand environment variables. For example:
#include <sstream>
#include "findhardlinks.hpp"
// ${PATH} environment variable delimiter.
#if defined(_WIN32)
#define PATH_DELIM ';'
#else
#define PATH_DELIM ':'
#endif
using std::string;
using std::vector;
#if defined(_WIN32)
using std::wstring;
using std::size_t;
#endif
/* necessary in GUI Windows applications to process window
clicks without crashing during lasting for/while loops. */
void message_pump() {
#if defined(_WIN32)
MSG msg; while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
#endif
}
#if defined(_WIN32)
// UTF-8 support on Windows: string to wstring.
wstring widen(string str) {
size_t wchar_count = str.size() 1; vector<wchar_t> buf(wchar_count);
return wstring{ buf.data(), (size_t)MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, buf.data(), (int)wchar_count) };
}
// UTF-8 support on Windows: wstring to string.
string narrow(wstring wstr) {
int nbytes = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int)wstr.length(), nullptr, 0, nullptr, nullptr); vector<char> buf(nbytes);
return string{ buf.data(), (size_t)WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int)wstr.length(), buf.data(), nbytes, nullptr, nullptr) };
}
#endif
// split string by delimiter character.
vector<string> string_split(string str, char delimiter) {
vector<string> vec;
std::stringstream sstr(str);
string tmp;
while (std::getline(sstr, tmp, delimiter)) {
message_pump();
vec.push_back(tmp);
}
return vec;
}
// get environment variable value.
string environment_get_variable(string name) {
#if defined(_WIN32)
string value;
wchar_t buffer[32767];
wstring u8name = widen(name);
if (GetEnvironmentVariableW(u8name.c_str(), buffer, 32767) != 0) {
value = narrow(buffer);
}
return value;
#else
char *value = getenv(name.c_str());
return value ? value : "";
#endif
}
...
// our example:
vector<string> in = string_split(environment_get_variable("PATH"), PATH_DELIM);
// assuming "TMPDIR" and "HOME" exist in the calling process's environment:
in.push_back("${TMPDIR}"); // not %TMPDIR% or $TMPDIR.
in.push_back("${HOME}"); // use ${ENVVAR} to expand.
vector<string> out = findhardlinks::findhardlinks(fd, in, false);
// do something with "out" if the vector is not empty...
...
Let's say you have a file you want to delete and want to locate it, you know where one of its hard links are, but you'd like to locate and delete all of them. Here's an example:
#include <sstream>
#include "findhardlinks.hpp"
// ${PATH} environment variable delimiter.
#if defined(_WIN32)
#define PATH_DELIM ';'
#else
#define PATH_DELIM ':'
#endif
using std::string;
using std::vector;
/* read, write, append
open() permissions. */
enum {
FD_RDONLY,
FD_WRONLY,
FD_RDWR,
FD_APPEND,
FD_RDAP
};
// check if regular file (non-directory) exists.
bool file_exists(string fname) {
std::error_code ec;
const findhardlinks::fs::path path = findhardlinks::fs::path(fname);
return (findhardlinks::fs::exists(path, ec) && ec.value() == 0 &&
(!findhardlinks::fs::is_directory(path, ec)) && ec.value() == 0);
}
// open file descriptor with mode (read, write, append, etc).
int file_open(string fname, int mode) {
#if defined(_WIN32)
wstring wfname = widen(fname);
FILE *fp = nullptr;
switch (mode) {
case 0: { if (!_wfopen_s(&fp, wfname.c_str(), L"rb, ccs=UTF-8" )) break; return -1; }
case 1: { if (!_wfopen_s(&fp, wfname.c_str(), L"wb, ccs=UTF-8" )) break; return -1; }
case 2: { if (!_wfopen_s(&fp, wfname.c_str(), L"w b, ccs=UTF-8")) break; return -1; }
case 3: { if (!_wfopen_s(&fp, wfname.c_str(), L"ab, ccs=UTF-8" )) break; return -1; }
case 4: { if (!_wfopen_s(&fp, wfname.c_str(), L"a b, ccs=UTF-8")) break; return -1; }
default: return -1;
}
if (fp) { int fd = _dup(_fileno(fp));
fclose(fp); return fd; }
#else
FILE *fp = nullptr;
switch (mode) {
case 0: { fp = fopen(fname.c_str(), "rb" ); break; }
case 1: { fp = fopen(fname.c_str(), "wb" ); break; }
case 2: { fp = fopen(fname.c_str(), "w b"); break; }
case 3: { fp = fopen(fname.c_str(), "ab" ); break; }
case 4: { fp = fopen(fname.c_str(), "a b"); break; }
default: return -1;
}
if (fp) { int fd = dup(fileno(fp));
fclose(fp); return fd; }
#endif
return -1;
}
// close file descriptor.
int file_close(int fd) {
#if defined(_WIN32)
return _close(fd);
#else
return close(fd);
#endif
}
// delete file or hardlink.
bool file_delete(string fname) {
std::error_code ec;
if (!file_exists(fname)) return false;
const findhardlinks::fs::path path = findhardlinks::fs::path(fname);
return (findhardlinks::fs::remove(path, ec) && ec.value() == 0);
}
// split string by delimiter character.
vector<string> string_split(string str, char delimiter) {
vector<string> vec;
std::stringstream sstr(str);
string tmp;
while (std::getline(sstr, tmp, delimiter)) {
vec.push_back(tmp);
}
return vec;
}
// example:
int main(int argc, char **argv) {
int fd = file_open(((argc >= 2) ? argv[1] : ""), FD_RDONLY);
if (fd == -1) return 1; // error.
#if defined(_WIN32)
vector<string> in = string_split(((argc >= 3) ? argv[2] : "C:\\"), PATH_DELIM);
#else
vector<string> in = string_split(((argc >= 3) ? argv[2] : "/"), PATH_DELIM);
#endif
vector<string> out = findhardlinks::findhardlinks(fd, in, ((argc == 4) ? (bool)strtoul(argv[3], nullptr, 10) : false));
file_close(fd);
for (unsigned i = 0; i < out.size(); i ) {
file_delete(out[i]);
}
return 0;
}
Build:
g deletehardlinks.cpp -o deletehardlinks -std=c 17 -Wno-empty-body -static-libgcc -static-libstdc
Test (args: file,path,recursive):
./deletehardlinks test.txt . 0