Home > Blockchain >  How to search for all hardlinks associated with a file descriptor on the filesystem?
How to search for all hardlinks associated with a file descriptor on the filesystem?

Time:03-05

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
  • Related