ewmscp  ..
outputHandlerLibssh.cpp
Go to the documentation of this file.
1 #include "Options.h"
2 #include "outputHandlerLibssh.h"
3 #include "block.h"
4 #include "copyRequestTypes.h"
5 #include "ewmscp.h"
6 #include "throwcall.h"
7 #include "errMsgQueue.h"
8 #include "timer.h"
9 #include <libgen.h>
10 #include <sys/stat.h>
11 #include <sys/types.h>
12 #include <vector>
13 #include <fcntl.h>
14 
15 
16 namespace outputHandler {
17  decltype(libssh::factory) libssh::factory("sftp");
18 
19  static libsshCommon::sshOptions outputConfig("sshOut");
20 
23  }
24 
25 
26 
27  std::unique_ptr<base::writer> libssh::newWriter(const std::string& path,
28  bool mightAppend,
29  size_t sourceSize,
30  size_t readBlockSize,
32  bool noWrite,
33  std::unique_ptr<ioHandle::attrDataType> attrData,
34  std::unique_ptr<acl::list> aclData) {
35  return std::unique_ptr<base::writer>(new writerLibssh(path,
36  *this,
37  mightAppend,
38  sourceSize, readBlockSize,
39  state, noWrite, std::move(attrData),
40  std::move(aclData)));
41  }
42 
43  void libssh::ensureParentDirs(const std::string& path,
44  const std::string& srcPath,
45  inputHandler::base* InputHandler) {
46  std::vector<std::remove_reference<decltype(path)>::type::value_type> disposable_buffer(path.c_str(), path.c_str() + path.size() + 1);
47  std::string dir(dirname(disposable_buffer.data()));
48  static timer::anchor a("sftp_stat");
50  auto stat = getUniquePtr(sftp_stat(sftp, dir.c_str()), sftp_attributes_free);
51  i.stop();
52 
53  if (stat == nullptr) {
54  if (sftp_get_error(sftp) == SSH_FX_NO_SUCH_FILE) {
55  ensureParentDirs(dir, srcPath, InputHandler);
56  {
57  timerInst(sftp_mkdir);
58  auto result = sftp_mkdir(sftp, dir.c_str(), 0777u);
59 
60  if (result != 0 && sftp_get_error(sftp) == SSH_FX_FILE_ALREADY_EXISTS) {
61  return; // we lost a race with another copy process...
62  }
63 
64  throwcall::sftp::good0(result, *this, "can't create directory ", dir);
65  }
66  if (gid != -1 || uid != -1) {
67  timerInst(sftp_chown);
68  throwcall::sftp::good0(sftp_chown(sftp, dir.c_str(), uid, gid), *this,
69  "can't set owner/group (", uid, ",", gid, ") on ", dir);
70  }
71  }
72  } else if (stat->type != SSH_FILEXFER_TYPE_DIRECTORY) {
74  dir, "ensure parents",
75  "is not a directory (st_mode is ",
76  static_cast<int>(stat->type),
77  ", perm: ", std::hex, stat->permissions,
78  ") but should be");
79  }
80  }
81 
82 
83  void libssh::remove(const std::string& path, copyRequest::stateType& state) {
84  auto stat = getUniquePtr(sftp_lstat(sftp, path.c_str()), sftp_attributes_free);
85  if (stat == nullptr && sftp_get_error(sftp) == SSH_FX_NO_SUCH_FILE) {
87  return;
88  }
89  if (stat->type == SSH_FILEXFER_TYPE_DIRECTORY) {
90  auto retval = sftp_rmdir(sftp, path.c_str());
91  if (retval) {
92  switch (sftp_get_error(sftp)) {
93  case SSH_FX_NO_SUCH_FILE:
95  return;
96  case SSH_FX_FILE_ALREADY_EXISTS:
98  path, "remove", "directory not empty but should be removed");
100  return;
101  }
102  throwcall::sftp::good0(retval, *this, "can't remove directory ", path);
103  } else {
105  return;
106  }
107  } else {
108  auto retval = sftp_unlink(sftp, path.c_str());
109 
110  if (retval && sftp_get_error(sftp) == SSH_FX_NO_SUCH_FILE) {
112  } else {
113  throwcall::sftp::good0(retval, *this, "can't unlink ", path);
114  }
116  }
117  }
118 
119  bool libssh::renameSimple(const std::string& fromPath,
120  const std::string& toPath) {
121  if (pathExists(toPath)) {
122  auto retval = sftp_unlink(sftp, toPath.c_str());
123  if (retval && sftp_get_error(sftp) == SSH_FX_NO_SUCH_FILE) {
125  toPath, "unlink in renameSimple",
126  "vanished");
127  } else {
128  throwcall::sftp::good0(retval, *this, "can't unlink in renameSimple ", toPath);
129  }
130  }
131  auto retval = sftp_rename(sftp, fromPath.c_str(), toPath.c_str());
132  if (retval && (sftp_get_error(sftp) == SSH_FX_NO_SUCH_FILE)) {
133  return false;
134  }
135  throwcall::sftp::good0(retval, *this, "can't rename '", fromPath, "' to '", toPath, "'");
136  return true;
137  }
138 
139 
140  base::renameRetvalType libssh::rename(const std::string& fromPath,
141  const std::unique_ptr<const genericStat>& readInitialStat,
142  const std::string& toPath,
143  copyRequest::stateType& state) {
144  auto fromPathStatBuf = getUniquePtr(sftp_stat(sftp, fromPath.c_str()),
145  sftp_attributes_free);
146  if (fromPathStatBuf == nullptr && sftp_get_error(sftp) == SSH_FX_NO_SUCH_FILE) {
147  if (readInitialStat && readInitialStat->isDir()) { // no need to act, dirs are created on the fly
151  }
153  } else if (fromPathStatBuf == nullptr) { // curious error
154  throwcall::sftp::badval(nullptr, nullptr, *this, "can't stat '", fromPath, "'");
155  return base::renameRetvalType::cantHappen; // never reached
156  }
157  genericSftpStat fromPathStat(*fromPathStatBuf, getVfsStat(fromPath));
158  if (!readInitialStat || (! readInitialStat->isDir() &&
159  (fromPathStat.size != readInitialStat->size
160  || !fromPathStat.isSameMtimeAs(*readInitialStat)))) {
161  // the copy on fromPath is not up to date, create a new copy
162  if (readInitialStat) {
163  std::string initialTime;
164  std::string finalTime;
165  readInitialStat->getMtime(initialTime);
166  fromPathStat.getMtime(finalTime);
168  fromPath, "move"
169  "file (", toPath, ") changed unexpectedly ("
170  , initialTime, " -> ", finalTime, ", "
171  , readInitialStat->size, " -> ", fromPathStat.size
172  , ", doing fresh copy");
173  } else {
175  fromPath, "move",
176  "file (", toPath, ") has no initial stat, doing fresh copy");
177  }
179  } else { // try to move the existing copy
180  auto retval = sftp_rename(sftp, fromPath.c_str(), toPath.c_str());
181 
182  if (retval && (errno == ENOENT || errno == ENOTDIR)) {
183  if (readInitialStat->isDir()) { // no need to act, dirs are created on the fly
186  }
188  fromPath, "move",
189  "vanished unexpectedly, doing fresh copy");
190  // file was probably not copied yet, try to copy it from the move target
192  } else {
193  throwcall::good0(retval, "can't rename '", fromPath, "' to '", toPath, "'");
196  }
197  }
198  }
199 
200 
201  void libssh::createSymlink(const std::vector<char>& target,
202  const std::string& path,
203  uid_t uid, gid_t gid) {
204  {
205  timerInst(sftp_symlink);
206  auto retval = sftp_symlink(sftp, target.data(), path.c_str());
207  if (retval != 0 && sftp_get_error(sftp) != SSH_FX_FILE_ALREADY_EXISTS) {
208  throwcall::sftp::good0(retval, *this, "can't create link at ", path);
209  }
210  }
211  if (static_cast<std::make_signed<decltype(gid)>::type>(gid) != -1
212  || static_cast<std::make_signed<decltype(uid)>::type>(uid) != -1) {
213  timerInst(sftp_chown);
214  throwcall::sftp::good0(sftp_chown(sftp, path.c_str(), uid, gid), *this,
215  "can't set owner/group (", uid, ",", gid, ") on ", path);
216  }
217  }
218 
219 
220  libssh::writerLibssh::writerLibssh(const std::string& aPath,
221  libsshCommon& aHandler,
222  bool mightAppend,
223  size_t sourceSize,
224  size_t readBlockSize,
225  copyRequest::stateType &state,
226  bool noWrite,
227  std::unique_ptr<ioHandle::attrDataType> aAttrData,
228  std::unique_ptr<acl::list> /*aAclData*/):
229  libsshIoCommon(aPath, aHandler),
230  writeInitialStat(nullptr),
231  attrData(std::move(aAttrData)) {
232  if (noWrite) {
233  file = nullptr;
234  blockSize = 1024 * 1024;
235  } else {
236  auto openMode = O_CREAT | O_TRUNC | O_WRONLY;
237  state.clear(copyRequest::stateBitType::append); // clear any old append bits, may be left over from former attempts
238  if (mightAppend) {
239  writeInitialStat = sftp_stat(handler.sftp, path.c_str());
240  if (writeInitialStat == nullptr) {
241  if (sftp_get_error(handler.sftp) != SSH_FX_NO_SUCH_FILE) {
242  throwcall::sftp::good0(1, handler, "can't stat destination file ", path);
243  }
244  } else {
245  if (static_cast<size_t>(writeInitialStat->size) < sourceSize &&
246  sourceSize > readBlockSize) {
248  openMode = O_WRONLY;
249  }
250  }
251  }
252  static timer::anchor a("sftp_open");
254  file = throwcall::sftp::badval(sftp_open(handler.sftp, path.c_str(), openMode, S_IWUSR),
255  nullptr, handler,
256  "can't open ", path, " for writing");
257  i.stop();
258  if (writeInitialStat == nullptr) {
259  timerInst(sftp_fstat);
260  writeInitialStat = throwcall::sftp::badval(sftp_fstat(file), nullptr,
261  handler,
262  "can't stat destination file ", path);
263  }
264  blockSize = 16 * 1024;
265  }
266  };
267 
269  static timer::anchor a("sftp_close");
271  if (sftp_close(file) == SSH_ERROR) {
273  path, "close during unwind ",
274  "sftp error ", sftp_get_error(handler.sftp),
275  ", ssh error ", ssh_get_error(handler.session));
276  }
277  i.stop();
279  path, "unlink failed copy", "due to exception");
280  timerInst(sftp_unlink);
281  auto retval = sftp_unlink(handler.sftp, path.c_str());
282  if (retval && sftp_get_error(handler.sftp) != SSH_FX_NO_SUCH_FILE) {
284  path, "can't remove bad copy ",
285  "sftp error ", sftp_get_error(handler.sftp),
286  ", ssh error ", ssh_get_error(handler.session));
287  }
288  };
289 
291  if (writeInitialStat != nullptr) {
292  sftp_attributes_free(writeInitialStat);
293  }
294  if (file != nullptr) {
295  if (isUnwinding()) {
296  closeAndRemoveBadCopy();
297  return;
298  }
299  if (attrData) {
300  try {
301  attrData->set(this);
302  } catch (const std::exception& e) {
304  path, "set attr at close ",
305  e.what());
306  closeAndRemoveBadCopy();
307  throw;
308  }
309  }
310  timerInst(sftp_close);
311  throwcall::badval(sftp_close(file), SSH_ERROR, "can't close ", path);
312  }
313  };
314 
315 
316  void libssh::writerLibssh::seek(size_t position) {
317  throwcall::sftp::good0(sftp_seek64(file, position), handler,
318  "can't seek ", path, " to ", position);
319  }
321  return writeInitialStat->size;
322  }
323 
325  if (b.isHole()) { // just write a hole...
326  auto currentPosition = sftp_tell64(file); // cannot fail, in constrast to docs
327  auto holeEnd = currentPosition + b.size();
328  throwcall::sftp::good0(sftp_seek64(file, holeEnd), handler,
329  "can't seek to hole end (", holeEnd, ") in ", path);
330  return;
331  }
332 
333  size_t bytes_writen_so_far = 0;
334 
335  while (bytes_writen_so_far < b.size()) {
336  auto count = b.size() - bytes_writen_so_far;
337 
338  if (count > blockSize) {
339  count = blockSize;
340  }
342  timerInst(sftp_write);
343  auto bytes_writen = sftp_write(file, b.bufferAt(bytes_writen_so_far), count);
344  if (bytes_writen < 0) {
345  throwcall::sftp::good0(-1, handler, "write failed on ", path);
346  }
347  writeRateLimit.update(bytes_writen);
348  bytes_writen_so_far += bytes_writen;
349  }
350  }
351 
352 
354  if (preserve.timestamps) {
355  struct timeval times[2];
356  readInitialStat.getAtime(times[0]);
357  readInitialStat.getMtime(times[1]);
358  timerInst(sftp_utimes);
359  throwcall::sftp::good0(sftp_utimes(handler.sftp, path.c_str(), times), handler,
360  "can't set time stamp of ", path);
361  }
362 
363  if (preserve.mode) {
364  timerInst(sftp_chmod);
365  throwcall::sftp::good0(sftp_chmod(handler.sftp, path.c_str(), readInitialStat.mode), handler,
366  "can't set mode of ", path);
367  } else {
368  auto oldmask = umask(0);
369  timerInst(sftp_chmod);
370  throwcall::sftp::good0(sftp_chmod(handler.sftp, path.c_str(), modeBits & ~oldmask), handler,
371  "can't set mode of ", path);
372  umask(oldmask);
373  }
374 
375  if (gid != -1 || uid != -1) {
376  timerInst(sftp_chown);
377  throwcall::sftp::good0(sftp_chown(handler.sftp, path.c_str(), uid, gid), handler,
378  "can't set owner/group (", uid, ",", gid, ") of ", path);
379  } else if (preserve.ownership) {
380  timerInst(sftp_chown);
381  throwcall::sftp::good0(sftp_chown(handler.sftp, path.c_str(),
382  readInitialStat.ownerUid, readInitialStat.ownerGid),
383  handler,
384  "can't set owner/group (", readInitialStat.ownerUid, ",", readInitialStat.ownerGid, ") of ",
385  path);
386  }
387  }
388 
389 
391  // old versions of libssh do not have fsync...
392  #if LIBSFTP_VERSION > 3
393  throwcall::sftp::good0(sftp_fsync(file), handler.sftp,
394  "can't sync ", path);
395  #endif
396  }
397 
398  size_t libssh::getMaxNameLength(const std::string& dirPath) {
399  auto stat = getVfsStat(dirPath);
400  if (stat.f_namemax > 0) {
401  return stat.f_namemax;
402  } else {
403  return 255; // best guess if no sane value forem system
404  }
405  }
406 }; // end namespace outputHandler
genericStat::ownerGid
gid_t ownerGid
Definition: genericStat.h:24
block.h
outputHandler::outputConfig
static davixConfigObject outputConfig("davixOut")
outputHandler::libssh::writerLibssh::writeBlock
void writeBlock(const block &b) override
Definition: outputHandlerLibssh.cpp:324
genericStat::mode
mode_t mode
Definition: genericStat.h:22
errMsgQueue.h
outputHandler::libssh::writerLibssh::doAttributePreservations
void doAttributePreservations(const genericStat &readInitialStat) override
Definition: outputHandlerLibssh.cpp:353
outputHandler::libssh::createSymlink
void createSymlink(const std::vector< char > &target, const std::string &path, uid_t uid, gid_t gid) override
Definition: outputHandlerLibssh.cpp:201
errMsg::location
class for defining the location of a error message in the source code.
Definition: errMsgQueue.h:14
outputHandler::libssh::rename
base::renameRetvalType rename(const std::string &fromPath, const std::unique_ptr< const genericStat > &readInitialStat, const std::string &toPath, copyRequest::stateType &state) override
Definition: outputHandlerLibssh.cpp:140
genericStat::getAtime
void getAtime(struct timespec &spec) const
Definition: genericStat.cpp:62
throwcall::badval
T badval(T call, t badvalue, const Args &... args)
template function to wrap system calls that return a special bad value on failure
Definition: throwcall.h:54
outputHandler::libssh::remove
void remove(const std::string &path, copyRequest::stateType &state) override
Definition: outputHandlerLibssh.cpp:83
genericStat
generic stat abstraction class Used to abstract the variants of the stat structure.
Definition: genericStat.h:12
libsshCommon
Definition: libsshCommon.h:25
libsshCommon::pathExists
bool pathExists(const std::string &path) override
Definition: libsshCommon.cpp:120
copyRequestTypes.h
outputHandler::libssh::ensureParentDirs
void ensureParentDirs(const std::string &path, const std::string &srcPath, inputHandler::base *InputHandler) override
Definition: outputHandlerLibssh.cpp:43
copyRequest::stateBitType::ignore
@ ignore
errMsg::level::info
@ info
libsshIoCommon::file
sftp_file file
Definition: libsshCommon.h:60
outputHandler::base::renameRetvalType::cantHappen
@ cantHappen
outputHandler::libssh::newWriter
std::unique_ptr< writer > newWriter(const std::string &path, bool mightAppend, size_t sourceSize, size_t readBlockSize, copyRequest::stateType &state, bool noWrite, std::unique_ptr< ioHandle::attrDataType > attrData, std::unique_ptr< acl::list > aclData) override
Definition: outputHandlerLibssh.cpp:27
outputHandler::libssh::writerLibssh::closeAndRemoveBadCopy
void closeAndRemoveBadCopy() override
Definition: outputHandlerLibssh.cpp:268
outputHandler::libssh::writerLibssh
Definition: outputHandlerLibssh.h:13
copyRequest::stateBitType::done
@ done
outputHandler::base::renameRetvalType::fileChanged
@ fileChanged
copyRequest::stateBitType::failed
@ failed
outputHandler
Definition: ioHandle.h:9
genericStat::getMtime
void getMtime(struct timespec &spec) const
Definition: genericStat.cpp:65
gid
options::single< int > gid
copyRequest::stateBitType::vanished
@ vanished
copyRequest::stateType
Definition: copyRequestTypes.h:66
Options.h
libsshIoCommon
Definition: libsshCommon.h:56
libsshCommon::sshOptions
Definition: libsshCommon.h:30
outputHandler::libssh::renameSimple
bool renameSimple(const std::string &fromPath, const std::string &toPath) override
Definition: outputHandlerLibssh.cpp:119
block::isHole
bool isHole() const
Definition: block.h:36
genericStat::isSameMtimeAs
bool isSameMtimeAs(const genericStat &that) const
Definition: genericStat.cpp:87
errMsg::level::debug
@ debug
genericStat::isDir
bool isDir() const
Definition: genericStat.cpp:92
outputHandler::libssh::libssh
libssh()
Definition: outputHandlerLibssh.cpp:21
ioHandle::blockSize
size_t blockSize
in bytes, block size to be used when reading or writing
Definition: ioHandle.h:17
genericStat::size
size_t size
Definition: genericStat.h:16
outputHandler::base::renameRetvalType::fileVanished
@ fileVanished
timer::instanceUnscoped::stop
void stop()
Definition: timer.h:107
libsshIoCommon::path
const std::string path
Definition: libsshCommon.h:59
block::bufferAt
void * bufferAt(size_t offset)
only way to access the data in the block
Definition: block.cpp:28
outputHandler::base::renameRetvalType::ok
@ ok
timer.h
outputHandler::base::renameRetvalType
renameRetvalType
Definition: outputHandler.h:106
throwcall.h
block
data block, used to hold the data that are being copied (or checksummed).
Definition: block.h:7
outputHandler::libssh::getMaxNameLength
size_t getMaxNameLength(const std::string &dirPath) override
Definition: outputHandlerLibssh.cpp:398
outputHandlerLibssh.h
timer::instanceUnscoped
Definition: timer.h:95
libsshIoCommon::handler
libsshCommon & handler
Definition: libsshCommon.h:58
outputHandler::libssh::writerLibssh::writeInitialStat
sftp_attributes writeInitialStat
Definition: outputHandlerLibssh.h:15
timer::anchor
Definition: timer.h:22
outputHandler::libssh::factory
static factoryTemplate< libssh > factory
Definition: outputHandlerLibssh.h:11
preserve
decltype(preserve) preserve
set of properties to preserve in the copy
Definition: ewmscp.cpp:111
outputHandler::libssh::writerLibssh::sync
void sync() override
Definition: outputHandlerLibssh.cpp:390
uid
options::single< int > uid
inputHandler::base
class for handling input This is the (abstract) base class for handling input, both reading a file vi...
Definition: inputHandler.h:35
outputHandler::libssh::writerLibssh::writerLibssh
writerLibssh(const std::string &aPath, libsshCommon &aHandler, bool mightAppend, size_t sourceSize, size_t readBlockSize, copyRequest::stateType &state, bool noWrite, std::unique_ptr< ioHandle::attrDataType > attrData, std::unique_ptr< acl::list > aclData)
Definition: outputHandlerLibssh.cpp:220
errMsg::level::err
@ err
throttle::watch::wait
void wait()
Definition: throttle.h:50
errMsg::emit
void emit(level aLogLevel, const location &loc, const std::string &aObject, const std::string &aAction, const Args &... args)
function to create and enqueue a message, this is the only way that messages should be created!
Definition: errMsgQueue.h:148
timerInst
#define timerInst(subfunc)
Definition: timer.h:157
libsshCommon::getUniquePtr
std::unique_ptr< T, void(*)(T *)> getUniquePtr(T *obj, void(*deleter)(T *))
Definition: libsshCommon.h:45
block::size
size_t size() const
Definition: block.h:16
writeRateLimit
throttle::watch writeRateLimit
libsshCommon::sftp
sftp_session sftp
Definition: libsshCommon.h:28
outputHandler::libssh::writerLibssh::seek
void seek(size_t position) override
Definition: outputHandlerLibssh.cpp:316
genericSftpStat
Definition: libsshCommon.h:16
throwcall::good0
void good0(T call, const Args &... args)
template function to wrap system calls that return 0 on success
Definition: throwcall.h:40
outputHandler::libssh::writerLibssh::~writerLibssh
~writerLibssh() noexcept(false) override
Definition: outputHandlerLibssh.cpp:290
errMsg::level::notice
@ notice
enumAsBitmask::clear
void clear(const T aBits)
Definition: enumAsBitmask.h:31
throwcall::sftp::badval
T badval(T call, t badvalue, libsshCommon &handler, const Args &... args)
Definition: libsshCommon.h:82
throttle::watch::update
void update(double units=1.0)
Definition: throttle.h:35
libsshCommon::getVfsStat
const sftp_statvfs_struct & getVfsStat(const std::string &path)
Definition: libsshCommon.cpp:152
ewmscp.h
copyRequest::stateBitType::append
@ append
outputHandler::libssh::writerLibssh::getSize
size_t getSize() const override
Definition: outputHandlerLibssh.cpp:320
genericStat::ownerUid
uid_t ownerUid
Definition: genericStat.h:23
modeBits
options::single< modeBitType > modeBits
throwcall::sftp::good0
void good0(T call, libsshCommon &handler, const Args &... args)
Definition: libsshCommon.h:70