text: #include "fs.h"
  #include "defaults.h"
  
  #if defined(_win_)
      #include "fs_win.h"
  #else
      #include <unistd.h>
      #include <errno.h>
  #endif
  
  #include <util/generic/yexception.h>
  #include <util/memory/tempbuf.h>
  #include <util/stream/file.h>
  #include <util/charset/wide.h>
  #include <util/folder/iterator.h>
  #include <util/system/fstat.h>
  #include <util/folder/path.h>
  
  bool NFs::Remove(const TString& path) {
  #if defined(_win_)
      return NFsPrivate::WinRemove(path);
  #else
      return ::remove(path.data()) == 0;
  #endif
  }
  
  void NFs::RemoveRecursive(const TString& path) {
      static const TStringBuf errStr = "error while removing ";
  
      if (!NFs::Exists(path)) {
          return;
      }
  
      if (!TFileStat(path).IsDir()) {
          if (!NFs::Remove(path)) {
              ythrow TSystemError() << errStr << path << " with cwd (" << NFs::CurrentWorkingDirectory() << ")";
          }
      }
  
      TDirIterator dir(path);
  
      for (auto it = dir.begin(); it != dir.end(); ++it) {
          switch (it->fts_info) {
              case FTS_DOT:
              case FTS_D:
                  break;
              default:
                  if (!NFs::Remove(it->fts_path)) {
                      ythrow TSystemError() << errStr << it->fts_path << " with cwd (" << NFs::CurrentWorkingDirectory() << ")";
                  }
                  break;
          }
      }
  }
  
  bool NFs::MakeDirectory(const TString& path, EFilePermissions mode) {
  #if defined(_win_)
      Y_UNUSED(mode);
      return NFsPrivate::WinMakeDirectory(path);
  #else
      return mkdir(path.data(), mode) == 0;
  #endif
  }
  
  bool NFs::MakeDirectoryRecursive(const TString& path, EFilePermissions mode, bool alwaysCreate) {
      if (NFs::Exists(path) && TFileStat(path).IsDir()) {
          if (alwaysCreate) {
              ythrow TIoException() << "path " << path << " already exists"
                                    << " with cwd (" << NFs::CurrentWorkingDirectory() << ")";
          }
          return true;
      } else {
          //NOTE: recursion is finite due to existence of "." and "/"
          if (!NFs::MakeDirectoryRecursive(TFsPath(path).Parent(), mode, false)) {
              return false;
          }
  
          bool isDirMade = NFs::MakeDirectory(path, mode);
          if (!isDirMade && alwaysCreate) {
              ythrow TIoException() << "failed to create " << path << " with cwd (" << NFs::CurrentWorkingDirectory() << ")";
          }
  
          return TFileStat(path).IsDir();
      }
  }
  
  bool NFs::Rename(const TString& oldPath, const TString& newPath) {
  #if defined(_win_)
      return NFsPrivate::WinRename(oldPath, newPath);
  #else
      return ::rename(oldPath.data(), newPath.data()) == 0;
  #endif
  }
  
  void NFs::HardLinkOrCopy(const TString& existingPath, const TString& newPath) {
      if (!NFs::HardLink(existingPath, newPath)) {
          Copy(existingPath, newPath);
      }
  }
  
  bool NFs::HardLink(const TString& existingPath, const TString& newPath) {
  #if defined(_win_)
      return NFsPrivate::WinHardLink(existingPath, newPath);
  #elif defined(_unix_)
      return (0 == link(existingPath.data(), newPath.data()));
  #endif
  }
  
  bool NFs::SymLink(const TString& targetPath, const TString& linkPath) {
  #if defined(_win_)
      return NFsPrivate::WinSymLink(targetPath, linkPath);
  #elif defined(_unix_)
      return 0 == symlink(targetPath.data(), linkPath.data());
  #endif
  }
  
  TString NFs::ReadLink(const TString& path) {
  #if defined(_win_)
      return NFsPrivate::WinReadLink(path);
  #elif defined(_unix_)
      TTempBuf buf;
      while (true) {
          ssize_t r = readlink(path.data(), buf.Data(), buf.Size());
          if (r < 0) {
              ythrow yexception() << "can't read link " << path << ", errno = " << errno;
          }
          if (r < (ssize_t)buf.Size()) {
              return TString(buf.Data(), r);
          }
          buf = TTempBuf(buf.Size() * 2);
      }
  #endif
  }
  
  void NFs::Cat(const TString& dstPath, const TString& srcPath) {
      TUnbufferedFileInput src(srcPath);
      TUnbufferedFileOutput dst(TFile(dstPath, ForAppend | WrOnly | Seq));
  
      TransferData(&src, &dst);
  }
  
  void NFs::Copy(const TString& existingPath, const TString& newPath) {
      TUnbufferedFileInput src(existingPath);
      TUnbufferedFileOutput dst(TFile(newPath, CreateAlways | WrOnly | Seq));
  
      TransferData(&src, &dst);
  }
  
  bool NFs::Exists(const TString& path) {
  #if defined(_win_)
      return NFsPrivate::WinExists(path);
  #elif defined(_unix_)
      return access(path.data(), F_OK) == 0;
  #endif
  }
  
  TString NFs::CurrentWorkingDirectory() {
  #if defined(_win_)
      return NFsPrivate::WinCurrentWorkingDirectory();
  #elif defined(_unix_)
      TTempBuf result;
      char* r = getcwd(result.Data(), result.Size());
      if (r == nullptr) {
          throw TIoSystemError() << "failed to getcwd";
      }
      return result.Data();
  #endif
  }
  
  void NFs::SetCurrentWorkingDirectory(TString path) {
  #ifdef _win_
      bool ok = NFsPrivate::WinSetCurrentWorkingDirectory(path);
  #else
      bool ok = !chdir(path.data());
  #endif
      if (!ok) {
          ythrow TSystemError() << "failed to change directory to " << path.Quote();
      }
  }
  