sftpchannel.cpp 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175
  1. /**************************************************************************
  2. **
  3. ** This file is part of Qt Creator
  4. **
  5. ** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
  6. **
  7. ** Contact: http://www.qt-project.org/
  8. **
  9. **
  10. ** GNU Lesser General Public License Usage
  11. **
  12. ** This file may be used under the terms of the GNU Lesser General Public
  13. ** License version 2.1 as published by the Free Software Foundation and
  14. ** appearing in the file LICENSE.LGPL included in the packaging of this file.
  15. ** Please review the following information to ensure the GNU Lesser General
  16. ** Public License version 2.1 requirements will be met:
  17. ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
  18. **
  19. ** In addition, as a special exception, Nokia gives you certain additional
  20. ** rights. These rights are described in the Nokia Qt LGPL Exception
  21. ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
  22. **
  23. ** Other Usage
  24. **
  25. ** Alternatively, this file may be used in accordance with the terms and
  26. ** conditions contained in a signed written agreement between you and Nokia.
  27. **
  28. **
  29. **************************************************************************/
  30. #include "sftpchannel.h"
  31. #include "sftpchannel_p.h"
  32. #include "sftpdefs.h"
  33. #include "sshexception_p.h"
  34. #include "sshincomingpacket_p.h"
  35. #include "sshlogging_p.h"
  36. #include "sshsendfacility_p.h"
  37. #include <QDir>
  38. #include <QFile>
  39. namespace QSsh {
  40. namespace Internal {
  41. namespace {
  42. const quint32 ProtocolVersion = 3;
  43. QString errorMessage(const QString &serverMessage,
  44. const QString &alternativeMessage)
  45. {
  46. return serverMessage.isEmpty() ? alternativeMessage : serverMessage;
  47. }
  48. QString errorMessage(const SftpStatusResponse &response,
  49. const QString &alternativeMessage)
  50. {
  51. return response.status == SSH_FX_OK ? QString()
  52. : errorMessage(response.errorString, alternativeMessage);
  53. }
  54. bool openFile(QFile *localFile, SftpOverwriteMode mode)
  55. {
  56. if (mode == SftpSkipExisting && localFile->exists())
  57. return false;
  58. QIODevice::OpenMode openMode = QIODevice::WriteOnly;
  59. if (mode == SftpOverwriteExisting)
  60. openMode |= QIODevice::Truncate;
  61. else if (mode == SftpAppendToExisting)
  62. openMode |= QIODevice::Append;
  63. return localFile->open(openMode);
  64. }
  65. SftpError sftpStatusToError(const SftpStatusCode status)
  66. {
  67. switch (status) {
  68. case SSH_FX_OK:
  69. return SftpError::NoError;
  70. case SSH_FX_EOF:
  71. return SftpError::EndOfFile;
  72. case SSH_FX_NO_SUCH_FILE:
  73. return SftpError::FileNotFound;
  74. case SSH_FX_PERMISSION_DENIED:
  75. return SftpError::PermissionDenied;
  76. case SSH_FX_BAD_MESSAGE:
  77. return SftpError::BadMessage;
  78. case SSH_FX_NO_CONNECTION:
  79. return SftpError::NoConnection;
  80. case SSH_FX_CONNECTION_LOST:
  81. return SftpError::ConnectionLost;
  82. case SSH_FX_OP_UNSUPPORTED:
  83. return SftpError::UnsupportedOperation;
  84. case SSH_FX_FAILURE:
  85. default:
  86. return SftpError::GenericFailure;
  87. }
  88. }
  89. } // anonymous namespace
  90. } // namespace Internal
  91. //--------------------------------------------------------------------------------------------------
  92. // SftpChannel
  93. //--------------------------------------------------------------------------------------------------
  94. SftpChannel::SftpChannel(quint32 channelId,
  95. Internal::SshSendFacility &sendFacility)
  96. : d(new Internal::SftpChannelPrivate(channelId, sendFacility, this))
  97. {
  98. connect(d, &Internal::SftpChannelPrivate::initialized,
  99. this, &SftpChannel::initialized, Qt::QueuedConnection);
  100. connect(d, &Internal::SftpChannelPrivate::channelError,
  101. this, &SftpChannel::channelError, Qt::QueuedConnection);
  102. connect(d, &Internal::SftpChannelPrivate::dataAvailable,
  103. this, &SftpChannel::dataAvailable, Qt::QueuedConnection);
  104. connect(d, &Internal::SftpChannelPrivate::fileInfoAvailable,
  105. this, &SftpChannel::fileInfoAvailable, Qt::QueuedConnection);
  106. connect(d, &Internal::SftpChannelPrivate::finished,
  107. this, &SftpChannel::finished, Qt::QueuedConnection);
  108. connect(d, &Internal::SftpChannelPrivate::closed,
  109. this, &SftpChannel::closed, Qt::QueuedConnection);
  110. connect(d, &Internal::SftpChannelPrivate::transferProgress,
  111. this, &SftpChannel::transferProgress, Qt::QueuedConnection);
  112. }
  113. SftpChannel::State SftpChannel::state() const
  114. {
  115. switch (d->channelState()) {
  116. case Internal::AbstractSshChannel::Inactive:
  117. return Uninitialized;
  118. case Internal::AbstractSshChannel::SessionRequested:
  119. return Initializing;
  120. case Internal::AbstractSshChannel::CloseRequested:
  121. return Closing;
  122. case Internal::AbstractSshChannel::Closed:
  123. return Closed;
  124. case Internal::AbstractSshChannel::SessionEstablished:
  125. return d->m_sftpState == Internal::SftpChannelPrivate::Initialized
  126. ? Initialized : Initializing;
  127. default:
  128. Q_ASSERT(!"Oh no, we forgot to handle a channel state!");
  129. return Closed; // For the compiler.
  130. }
  131. }
  132. void SftpChannel::initialize()
  133. {
  134. d->requestSessionStart();
  135. d->m_sftpState = Internal::SftpChannelPrivate::SubsystemRequested;
  136. }
  137. void SftpChannel::closeChannel()
  138. {
  139. d->closeChannel();
  140. }
  141. SftpJobId SftpChannel::statFile(const QString &path)
  142. {
  143. return d->createJob(Internal::SftpStatFile::Ptr(
  144. new Internal::SftpStatFile(++d->m_nextJobId, path)));
  145. }
  146. SftpJobId SftpChannel::listDirectory(const QString &path)
  147. {
  148. return d->createJob(Internal::SftpListDir::Ptr(
  149. new Internal::SftpListDir(++d->m_nextJobId, path)));
  150. }
  151. SftpJobId SftpChannel::createDirectory(const QString &path)
  152. {
  153. return d->createJob(Internal::SftpMakeDir::Ptr(
  154. new Internal::SftpMakeDir(++d->m_nextJobId, path)));
  155. }
  156. SftpJobId SftpChannel::removeDirectory(const QString &path)
  157. {
  158. return d->createJob(Internal::SftpRmDir::Ptr(
  159. new Internal::SftpRmDir(++d->m_nextJobId, path)));
  160. }
  161. SftpJobId SftpChannel::removeFile(const QString &path)
  162. {
  163. return d->createJob(Internal::SftpRm::Ptr(
  164. new Internal::SftpRm(++d->m_nextJobId, path)));
  165. }
  166. SftpJobId SftpChannel::renameFileOrDirectory(const QString &oldPath,
  167. const QString &newPath)
  168. {
  169. return d->createJob(Internal::SftpRename::Ptr(
  170. new Internal::SftpRename(++d->m_nextJobId, oldPath, newPath)));
  171. }
  172. SftpJobId SftpChannel::createLink(const QString &filePath, const QString &target)
  173. {
  174. return d->createJob(Internal::SftpCreateLink::Ptr(
  175. new Internal::SftpCreateLink(++d->m_nextJobId, filePath, target)));
  176. }
  177. SftpJobId SftpChannel::createFile(const QString &path, SftpOverwriteMode mode)
  178. {
  179. return d->createJob(Internal::SftpCreateFile::Ptr(
  180. new Internal::SftpCreateFile(++d->m_nextJobId, path, mode)));
  181. }
  182. SftpJobId SftpChannel::uploadFile(QSharedPointer<QIODevice> device,
  183. const QString &remoteFilePath, SftpOverwriteMode mode)
  184. {
  185. if (!device->isOpen() && !device->open(QIODevice::ReadOnly))
  186. return SftpInvalidJob;
  187. return d->createJob(Internal::SftpUploadFile::Ptr(
  188. new Internal::SftpUploadFile(++d->m_nextJobId, remoteFilePath, device, mode)));
  189. }
  190. SftpJobId SftpChannel::uploadFile(const QString &localFilePath,
  191. const QString &remoteFilePath, SftpOverwriteMode mode)
  192. {
  193. QSharedPointer<QFile> localFile(new QFile(localFilePath));
  194. if (!localFile->open(QIODevice::ReadOnly))
  195. return SftpInvalidJob;
  196. return d->createJob(Internal::SftpUploadFile::Ptr(
  197. new Internal::SftpUploadFile(++d->m_nextJobId, remoteFilePath, localFile, mode)));
  198. }
  199. SftpJobId SftpChannel::downloadFile(const QString &remoteFilePath,
  200. const QString &localFilePath, SftpOverwriteMode mode)
  201. {
  202. QSharedPointer<QFile> localFile(new QFile(localFilePath));
  203. return d->createJob(Internal::SftpDownload::Ptr(
  204. new Internal::SftpDownload(++d->m_nextJobId, remoteFilePath, localFile, mode)));
  205. }
  206. SftpJobId SftpChannel::downloadFile(const QString &remoteFilePath, QSharedPointer<QIODevice> device)
  207. {
  208. return d->createJob(Internal::SftpDownload::Ptr(
  209. new Internal::SftpDownload(++d->m_nextJobId, remoteFilePath, device, SftpOverwriteExisting)));
  210. }
  211. SftpJobId SftpChannel::uploadDir(const QString &localDirPath,
  212. const QString &remoteParentDirPath)
  213. {
  214. if (state() != Initialized)
  215. return SftpInvalidJob;
  216. const QDir localDir(localDirPath);
  217. if (!localDir.exists() || !localDir.isReadable())
  218. return SftpInvalidJob;
  219. const Internal::SftpUploadDir::Ptr uploadDirOp(
  220. new Internal::SftpUploadDir(++d->m_nextJobId));
  221. const QString remoteDirPath
  222. = remoteParentDirPath + u'/' + localDir.dirName();
  223. const Internal::SftpMakeDir::Ptr mkdirOp(
  224. new Internal::SftpMakeDir(++d->m_nextJobId, remoteDirPath, uploadDirOp));
  225. uploadDirOp->mkdirsInProgress.insert(mkdirOp,
  226. Internal::SftpUploadDir::Dir(localDirPath, remoteDirPath));
  227. d->createJob(mkdirOp);
  228. return uploadDirOp->jobId;
  229. }
  230. SftpJobId SftpChannel::downloadDir(const QString &remoteDirPath,
  231. const QString &localDirPath, SftpOverwriteMode mode)
  232. {
  233. if (state() != Initialized)
  234. return SftpInvalidJob;
  235. if (!QDir().mkpath(localDirPath))
  236. return SftpInvalidJob;
  237. const Internal::SftpDownloadDir::Ptr downloadDirOp(
  238. new Internal::SftpDownloadDir(++d->m_nextJobId, mode));
  239. const Internal::SftpListDir::Ptr lsdirOp(
  240. new Internal::SftpListDir(++d->m_nextJobId, remoteDirPath, downloadDirOp));
  241. downloadDirOp->lsdirsInProgress.insert(lsdirOp,
  242. Internal::SftpDownloadDir::Dir(localDirPath, remoteDirPath));
  243. d->createJob(lsdirOp);
  244. return downloadDirOp->jobId;
  245. }
  246. SftpChannel::~SftpChannel()
  247. {
  248. delete d;
  249. }
  250. //--------------------------------------------------------------------------------------------------
  251. // SftpChannelPrivate
  252. //--------------------------------------------------------------------------------------------------
  253. namespace Internal {
  254. SftpChannelPrivate::SftpChannelPrivate(quint32 channelId,
  255. SshSendFacility &sendFacility, SftpChannel *sftp)
  256. : AbstractSshChannel(channelId, sendFacility),
  257. m_nextJobId(0), m_sftpState(Inactive), m_sftp(sftp)
  258. {
  259. }
  260. SftpJobId SftpChannelPrivate::createJob(const AbstractSftpOperation::Ptr &job)
  261. {
  262. if (m_sftp->state() != SftpChannel::Initialized)
  263. return SftpInvalidJob;
  264. m_jobs.insert(job->jobId, job);
  265. sendData(job->initialPacket(m_outgoingPacket).rawData());
  266. return job->jobId;
  267. }
  268. void SftpChannelPrivate::handleChannelSuccess()
  269. {
  270. if (channelState() == CloseRequested)
  271. return;
  272. qCDebug(sshLog, "sftp subsystem initialized");
  273. sendData(m_outgoingPacket.generateInit(ProtocolVersion).rawData());
  274. m_sftpState = InitSent;
  275. }
  276. void SftpChannelPrivate::handleChannelFailure()
  277. {
  278. if (channelState() == CloseRequested)
  279. return;
  280. if (m_sftpState != SubsystemRequested) {
  281. throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
  282. "Unexpected SSH_MSG_CHANNEL_FAILURE packet.");
  283. }
  284. emit channelError(tr("Server could not start SFTP subsystem."));
  285. closeChannel();
  286. }
  287. void SftpChannelPrivate::handleChannelDataInternal(const QByteArray &data)
  288. {
  289. if (channelState() == CloseRequested)
  290. return;
  291. m_incomingData += data;
  292. m_incomingPacket.consumeData(m_incomingData);
  293. while (m_incomingPacket.isComplete()) {
  294. handleCurrentPacket();
  295. m_incomingPacket.clear();
  296. m_incomingPacket.consumeData(m_incomingData);
  297. }
  298. }
  299. void SftpChannelPrivate::handleChannelExtendedDataInternal(quint32 type,
  300. const QByteArray &data)
  301. {
  302. qCWarning(sshLog, "Unexpected extended data '%s' of type %d on SFTP channel.",
  303. data.data(), type);
  304. }
  305. void SftpChannelPrivate::handleExitStatus(const SshChannelExitStatus &exitStatus)
  306. {
  307. qCDebug(sshLog, "Remote SFTP service exited with exit code %d", exitStatus.exitStatus);
  308. if (channelState() == CloseRequested || channelState() == Closed)
  309. return;
  310. emit channelError(tr("The SFTP server finished unexpectedly with exit code %1.")
  311. .arg(exitStatus.exitStatus));
  312. // Note: According to the specs, the server must close the channel after this happens,
  313. // but OpenSSH doesn't do that, so we need to initiate the closing procedure ourselves.
  314. closeChannel();
  315. }
  316. void SftpChannelPrivate::handleExitSignal(const SshChannelExitSignal &signal)
  317. {
  318. emit channelError(tr("The SFTP server crashed: %1.").arg(signal.error));
  319. closeChannel(); // See above.
  320. }
  321. void SftpChannelPrivate::handleCurrentPacket()
  322. {
  323. qCDebug(sshLog, "Handling SFTP packet of type %d", m_incomingPacket.type());
  324. switch (m_incomingPacket.type()) {
  325. case SSH_FXP_VERSION:
  326. handleServerVersion();
  327. break;
  328. case SSH_FXP_HANDLE:
  329. handleHandle();
  330. break;
  331. case SSH_FXP_NAME:
  332. handleName();
  333. break;
  334. case SSH_FXP_STATUS:
  335. handleStatus();
  336. break;
  337. case SSH_FXP_DATA:
  338. handleReadData();
  339. break;
  340. case SSH_FXP_ATTRS:
  341. handleAttrs();
  342. break;
  343. default:
  344. throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
  345. "Unexpected packet.",
  346. tr("Unexpected packet of type %1.").arg(m_incomingPacket.type()));
  347. }
  348. }
  349. void SftpChannelPrivate::handleServerVersion()
  350. {
  351. checkChannelActive();
  352. if (m_sftpState != InitSent) {
  353. throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
  354. "Unexpected SSH_FXP_VERSION packet.");
  355. }
  356. qCDebug(sshLog, "sftp init received");
  357. const quint32 serverVersion = m_incomingPacket.extractServerVersion();
  358. if (serverVersion != ProtocolVersion) {
  359. emit channelError(tr("Protocol version mismatch: Expected %1, got %2")
  360. .arg(serverVersion).arg(ProtocolVersion));
  361. closeChannel();
  362. } else {
  363. m_sftpState = Initialized;
  364. emit initialized();
  365. }
  366. }
  367. void SftpChannelPrivate::handleHandle()
  368. {
  369. const SftpHandleResponse &response = m_incomingPacket.asHandleResponse();
  370. JobMap::Iterator it = lookupJob(response.requestId);
  371. const QSharedPointer<AbstractSftpOperationWithHandle> job
  372. = it.value().dynamicCast<AbstractSftpOperationWithHandle>();
  373. if (job.isNull()) {
  374. throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
  375. "Unexpected SSH_FXP_HANDLE packet.");
  376. }
  377. if (job->state != AbstractSftpOperationWithHandle::OpenRequested) {
  378. throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
  379. "Unexpected SSH_FXP_HANDLE packet.");
  380. }
  381. job->remoteHandle = response.handle;
  382. job->state = AbstractSftpOperationWithHandle::Open;
  383. switch (it.value()->type()) {
  384. case AbstractSftpOperation::ListDir:
  385. handleLsHandle(it);
  386. break;
  387. case AbstractSftpOperation::CreateFile:
  388. handleCreateFileHandle(it);
  389. break;
  390. case AbstractSftpOperation::Download:
  391. handleGetHandle(it);
  392. break;
  393. case AbstractSftpOperation::UploadFile:
  394. handlePutHandle(it);
  395. break;
  396. default:
  397. Q_ASSERT(!"Oh no, I forgot to handle an SFTP operation type!");
  398. }
  399. }
  400. void SftpChannelPrivate::handleLsHandle(JobMap::Iterator it)
  401. {
  402. SftpListDir::Ptr op = it.value().staticCast<SftpListDir>();
  403. sendData(m_outgoingPacket.generateReadDir(op->remoteHandle,
  404. op->jobId).rawData());
  405. }
  406. void SftpChannelPrivate::handleCreateFileHandle(JobMap::Iterator it)
  407. {
  408. SftpCreateFile::Ptr op = it.value().staticCast<SftpCreateFile>();
  409. sendData(m_outgoingPacket.generateCloseHandle(op->remoteHandle,
  410. op->jobId).rawData());
  411. }
  412. void SftpChannelPrivate::handleGetHandle(JobMap::Iterator it)
  413. {
  414. SftpDownload::Ptr op = it.value().staticCast<SftpDownload>();
  415. sendData(m_outgoingPacket.generateFstat(op->remoteHandle,
  416. op->jobId).rawData());
  417. op->statRequested = true;
  418. }
  419. void SftpChannelPrivate::handlePutHandle(JobMap::Iterator it)
  420. {
  421. SftpUploadFile::Ptr op = it.value().staticCast<SftpUploadFile>();
  422. if (op->parentJob && op->parentJob->hasError)
  423. sendTransferCloseHandle(op, it.key());
  424. // OpenSSH does not implement the RFC's append functionality, so we
  425. // have to emulate it.
  426. if (op->mode == SftpAppendToExisting) {
  427. sendData(m_outgoingPacket.generateFstat(op->remoteHandle,
  428. op->jobId).rawData());
  429. op->statRequested = true;
  430. } else {
  431. spawnWriteRequests(it);
  432. }
  433. }
  434. void SftpChannelPrivate::handleStatus()
  435. {
  436. const SftpStatusResponse &response = m_incomingPacket.asStatusResponse();
  437. qCDebug(sshLog, "%s: status = %d", Q_FUNC_INFO, response.status);
  438. JobMap::Iterator it = lookupJob(response.requestId);
  439. switch (it.value()->type()) {
  440. case AbstractSftpOperation::ListDir:
  441. handleLsStatus(it, response);
  442. break;
  443. case AbstractSftpOperation::Download:
  444. handleGetStatus(it, response);
  445. break;
  446. case AbstractSftpOperation::UploadFile:
  447. handlePutStatus(it, response);
  448. break;
  449. case AbstractSftpOperation::MakeDir:
  450. handleMkdirStatus(it, response);
  451. break;
  452. case AbstractSftpOperation::StatFile:
  453. case AbstractSftpOperation::RmDir:
  454. case AbstractSftpOperation::Rm:
  455. case AbstractSftpOperation::Rename:
  456. case AbstractSftpOperation::CreateFile:
  457. case AbstractSftpOperation::CreateLink:
  458. handleStatusGeneric(it, response);
  459. break;
  460. }
  461. }
  462. void SftpChannelPrivate::handleStatusGeneric(JobMap::Iterator it,
  463. const SftpStatusResponse &response)
  464. {
  465. AbstractSftpOperation::Ptr op = it.value();
  466. const QString error = errorMessage(response, tr("Unknown error."));
  467. emit finished(op->jobId, sftpStatusToError(response.status), error);
  468. m_jobs.erase(it);
  469. }
  470. void SftpChannelPrivate::handleMkdirStatus(JobMap::Iterator it,
  471. const SftpStatusResponse &response)
  472. {
  473. SftpMakeDir::Ptr op = it.value().staticCast<SftpMakeDir>();
  474. QSharedPointer<SftpUploadDir> parentJob = op->parentJob;
  475. if (parentJob == SftpUploadDir::Ptr()) {
  476. handleStatusGeneric(it, response);
  477. return;
  478. }
  479. if (parentJob->hasError) {
  480. m_jobs.erase(it);
  481. return;
  482. }
  483. typedef QMap<SftpMakeDir::Ptr, SftpUploadDir::Dir>::Iterator DirIt;
  484. DirIt dirIt = parentJob->mkdirsInProgress.find(op);
  485. Q_ASSERT(dirIt != parentJob->mkdirsInProgress.end());
  486. const QString &remoteDir = dirIt.value().remoteDir;
  487. if (response.status == SSH_FX_OK) {
  488. emit dataAvailable(parentJob->jobId,
  489. tr("Created remote directory \"%1\".").arg(remoteDir));
  490. } else if (response.status == SSH_FX_FAILURE) {
  491. emit dataAvailable(parentJob->jobId,
  492. tr("Remote directory \"%1\" already exists.").arg(remoteDir));
  493. } else {
  494. parentJob->setError();
  495. emit finished(parentJob->jobId,
  496. sftpStatusToError(response.status),
  497. tr("Error creating directory \"%1\": %2")
  498. .arg(remoteDir, response.errorString));
  499. m_jobs.erase(it);
  500. return;
  501. }
  502. QDir localDir(dirIt.value().localDir);
  503. const QFileInfoList &dirInfos
  504. = localDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
  505. for (const QFileInfo &dirInfo : dirInfos) {
  506. const QString remoteSubDir = remoteDir + u'/' + dirInfo.fileName();
  507. const SftpMakeDir::Ptr mkdirOp(
  508. new SftpMakeDir(++m_nextJobId, remoteSubDir, parentJob));
  509. parentJob->mkdirsInProgress.insert(mkdirOp,
  510. SftpUploadDir::Dir(dirInfo.absoluteFilePath(), remoteSubDir));
  511. createJob(mkdirOp);
  512. }
  513. const QFileInfoList &fileInfos = localDir.entryInfoList(QDir::Files);
  514. for (const QFileInfo &fileInfo : fileInfos) {
  515. QSharedPointer<QFile> localFile(new QFile(fileInfo.absoluteFilePath()));
  516. if (!localFile->open(QIODevice::ReadOnly)) {
  517. parentJob->setError();
  518. emit finished(parentJob->jobId,
  519. sftpStatusToError(response.status),
  520. tr("Could not open local file \"%1\": %2")
  521. .arg(fileInfo.absoluteFilePath(), localFile->errorString()));
  522. m_jobs.erase(it);
  523. return;
  524. }
  525. const QString remoteFilePath = remoteDir + u'/' + fileInfo.fileName();
  526. SftpUploadFile::Ptr uploadFileOp(new SftpUploadFile(++m_nextJobId,
  527. remoteFilePath, localFile, SftpOverwriteExisting, parentJob));
  528. createJob(uploadFileOp);
  529. parentJob->uploadsInProgress.append(uploadFileOp);
  530. }
  531. parentJob->mkdirsInProgress.erase(dirIt);
  532. if (parentJob->mkdirsInProgress.isEmpty()
  533. && parentJob->uploadsInProgress.isEmpty())
  534. emit finished(parentJob->jobId);
  535. m_jobs.erase(it);
  536. }
  537. void SftpChannelPrivate::handleLsStatus(JobMap::Iterator it,
  538. const SftpStatusResponse &response)
  539. {
  540. SftpListDir::Ptr op = it.value().staticCast<SftpListDir>();
  541. if (op->parentJob && op->parentJob->hasError) {
  542. m_jobs.erase(it);
  543. return;
  544. }
  545. switch (op->state) {
  546. case SftpListDir::OpenRequested:
  547. reportRequestError(op, sftpStatusToError(response.status), errorMessage(response.errorString,
  548. tr("Remote directory could not be opened for reading.")));
  549. m_jobs.erase(it);
  550. break;
  551. case SftpListDir::Open:
  552. if (response.status != SSH_FX_EOF)
  553. reportRequestError(op,
  554. sftpStatusToError(response.status),
  555. errorMessage(response.errorString,
  556. tr("Failed to list remote directory contents.")));
  557. op->state = SftpListDir::CloseRequested;
  558. sendData(m_outgoingPacket.generateCloseHandle(op->remoteHandle,
  559. op->jobId).rawData());
  560. break;
  561. case SftpListDir::CloseRequested:
  562. if (op->hasError || (op->parentJob && op->parentJob->hasError)) {
  563. m_jobs.erase(it);
  564. return;
  565. }
  566. {
  567. const QString error = errorMessage(response,
  568. tr("Failed to close remote directory."));
  569. if (op->parentJob) {
  570. if (!error.isEmpty()) {
  571. op->parentJob->setError();
  572. }
  573. if (op->parentJob->hasError) {
  574. emit finished(op->parentJob->jobId, sftpStatusToError(response.status), error);
  575. } else {
  576. op->parentJob->lsdirsInProgress.remove(op);
  577. if (op->parentJob->lsdirsInProgress.isEmpty() &&
  578. op->parentJob->downloadsInProgress.isEmpty()) {
  579. emit finished(op->parentJob->jobId);
  580. }
  581. }
  582. } else {
  583. emit finished(op->jobId, sftpStatusToError(response.status), error);
  584. }
  585. }
  586. m_jobs.erase(it);
  587. break;
  588. default:
  589. throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
  590. "Unexpected SSH_FXP_STATUS packet.");
  591. }
  592. }
  593. void SftpChannelPrivate::handleGetStatus(JobMap::Iterator it,
  594. const SftpStatusResponse &response)
  595. {
  596. SftpDownload::Ptr op = it.value().staticCast<SftpDownload>();
  597. if (op->parentJob && op->parentJob->hasError) {
  598. m_jobs.erase(it);
  599. return;
  600. }
  601. switch (op->state) {
  602. case SftpDownload::OpenRequested:
  603. reportRequestError(op, sftpStatusToError(response.status), errorMessage(response.errorString,
  604. tr("Failed to open remote file for reading.")));
  605. m_jobs.erase(it);
  606. break;
  607. case SftpDownload::Open:
  608. if (op->statRequested) {
  609. reportRequestError(op, sftpStatusToError(response.status), errorMessage(response.errorString,
  610. tr("Failed to retrieve information on the remote file ('stat' failed).")));
  611. sendTransferCloseHandle(op, response.requestId);
  612. } else {
  613. if ((response.status != SSH_FX_EOF || response.requestId != op->eofId)
  614. && !op->hasError)
  615. reportRequestError(op, sftpStatusToError(response.status), errorMessage(response.errorString,
  616. tr("Failed to read remote file.")));
  617. finishTransferRequest(it);
  618. }
  619. break;
  620. case SftpDownload::CloseRequested:
  621. Q_ASSERT(op->inFlightCount == 1);
  622. if (!op->hasError) {
  623. if (response.status == SSH_FX_OK) {
  624. if (op->parentJob) {
  625. op->parentJob->downloadsInProgress.removeOne(op);
  626. if (op->parentJob->lsdirsInProgress.isEmpty()
  627. && op->parentJob->downloadsInProgress.isEmpty())
  628. emit finished(op->parentJob->jobId);
  629. } else {
  630. emit finished(op->jobId);
  631. }
  632. } else {
  633. const QString error = errorMessage(response.errorString,
  634. tr("Failed to close remote file."));
  635. reportRequestError(op, sftpStatusToError(response.status), error);
  636. }
  637. }
  638. removeTransferRequest(it);
  639. break;
  640. default:
  641. throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
  642. "Unexpected SSH_FXP_STATUS packet.");
  643. }
  644. }
  645. void SftpChannelPrivate::handlePutStatus(JobMap::Iterator it,
  646. const SftpStatusResponse &response)
  647. {
  648. SftpUploadFile::Ptr job = it.value().staticCast<SftpUploadFile>();
  649. switch (job->state) {
  650. case SftpUploadFile::OpenRequested: {
  651. bool emitError = false;
  652. if (job->parentJob) {
  653. if (!job->parentJob->hasError) {
  654. job->parentJob->setError();
  655. emitError = true;
  656. }
  657. } else {
  658. emitError = true;
  659. }
  660. if (emitError) {
  661. emit finished(job->jobId,
  662. sftpStatusToError(response.status),
  663. errorMessage(response.errorString,
  664. tr("Failed to open remote file for writing.")));
  665. }
  666. m_jobs.erase(it);
  667. break;
  668. }
  669. case SftpUploadFile::Open:
  670. if (job->hasError || (job->parentJob && job->parentJob->hasError)) {
  671. job->hasError = true;
  672. finishTransferRequest(it);
  673. return;
  674. }
  675. if (response.status == SSH_FX_OK) {
  676. sendWriteRequest(it);
  677. } else {
  678. if (job->parentJob)
  679. job->parentJob->setError();
  680. reportRequestError(job, sftpStatusToError(response.status), errorMessage(response.errorString,
  681. tr("Failed to write remote file.")));
  682. finishTransferRequest(it);
  683. }
  684. break;
  685. case SftpUploadFile::CloseRequested:
  686. Q_ASSERT(job->inFlightCount == 1);
  687. if (job->hasError || (job->parentJob && job->parentJob->hasError)) {
  688. m_jobs.erase(it);
  689. return;
  690. }
  691. if (response.status == SSH_FX_OK) {
  692. if (job->parentJob) {
  693. job->parentJob->uploadsInProgress.removeOne(job);
  694. if (job->parentJob->mkdirsInProgress.isEmpty()
  695. && job->parentJob->uploadsInProgress.isEmpty())
  696. emit finished(job->parentJob->jobId);
  697. } else {
  698. emit finished(job->jobId);
  699. }
  700. } else {
  701. const QString error = errorMessage(response.errorString,
  702. tr("Failed to close remote file."));
  703. if (job->parentJob) {
  704. job->parentJob->setError();
  705. emit finished(job->parentJob->jobId, sftpStatusToError(response.status), error);
  706. } else {
  707. emit finished(job->jobId, sftpStatusToError(response.status), error);
  708. }
  709. }
  710. m_jobs.erase(it);
  711. break;
  712. default:
  713. throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
  714. "Unexpected SSH_FXP_STATUS packet.");
  715. }
  716. }
  717. void SftpChannelPrivate::handleName()
  718. {
  719. const SftpNameResponse &response = m_incomingPacket.asNameResponse();
  720. JobMap::Iterator it = lookupJob(response.requestId);
  721. switch (it.value()->type()) {
  722. case AbstractSftpOperation::ListDir: {
  723. SftpListDir::Ptr op = it.value().staticCast<SftpListDir>();
  724. if (op->state != SftpListDir::Open) {
  725. throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
  726. "Unexpected SSH_FXP_NAME packet.");
  727. }
  728. QList<SftpFileInfo> fileInfoList;
  729. for (int i = 0; i < response.files.count(); ++i) {
  730. const SftpFile &file = response.files.at(i);
  731. SftpFileInfo fileInfo;
  732. fileInfo.name = file.fileName;
  733. attributesToFileInfo(file.attributes, fileInfo);
  734. fileInfoList << fileInfo;
  735. }
  736. if (op->parentJob) {
  737. handleDownloadDir(op, fileInfoList);
  738. } else {
  739. emit fileInfoAvailable(op->jobId, fileInfoList);
  740. }
  741. sendData(m_outgoingPacket.generateReadDir(op->remoteHandle,
  742. op->jobId).rawData());
  743. break;
  744. }
  745. default:
  746. throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
  747. "Unexpected SSH_FXP_NAME packet.");
  748. }
  749. }
  750. void SftpChannelPrivate::handleReadData()
  751. {
  752. const SftpDataResponse &response = m_incomingPacket.asDataResponse();
  753. JobMap::Iterator it = lookupJob(response.requestId);
  754. if (it.value()->type() != AbstractSftpOperation::Download) {
  755. throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
  756. "Unexpected SSH_FXP_DATA packet.");
  757. }
  758. SftpDownload::Ptr op = it.value().staticCast<SftpDownload>();
  759. if (op->hasError) {
  760. finishTransferRequest(it);
  761. return;
  762. }
  763. if (!op->localFile->isOpen()) {
  764. QFile *fileDevice = qobject_cast<QFile*>(op->localFile.data());
  765. if (fileDevice){
  766. if (!Internal::openFile(fileDevice, op->mode)) {
  767. reportRequestError(op, SftpError::GenericFailure, tr("Cannot open file ") + fileDevice->fileName());
  768. finishTransferRequest(it);
  769. return;
  770. }
  771. } else {
  772. reportRequestError(op, SftpError::GenericFailure, tr("File to upload is not open"));
  773. finishTransferRequest(it);
  774. return;
  775. }
  776. }
  777. if (!op->localFile->seek(op->offsets[response.requestId])) {
  778. reportRequestError(op, SftpError::GenericFailure, op->localFile->errorString());
  779. finishTransferRequest(it);
  780. return;
  781. }
  782. if (op->localFile->write(response.data) != response.data.size()) {
  783. reportRequestError(op, SftpError::GenericFailure, op->localFile->errorString());
  784. finishTransferRequest(it);
  785. return;
  786. }
  787. emit transferProgress(op->jobId, op->localFile->pos(), op->fileSize);
  788. if (op->offset >= op->fileSize && op->fileSize != 0)
  789. finishTransferRequest(it);
  790. else
  791. sendReadRequest(op, response.requestId);
  792. }
  793. void SftpChannelPrivate::handleAttrs()
  794. {
  795. const SftpAttrsResponse &response = m_incomingPacket.asAttrsResponse();
  796. JobMap::Iterator it = lookupJob(response.requestId);
  797. SftpStatFile::Ptr statOp = it.value().dynamicCast<SftpStatFile>();
  798. if (statOp) {
  799. SftpFileInfo fileInfo;
  800. fileInfo.name = QFileInfo(statOp->path).fileName();
  801. attributesToFileInfo(response.attrs, fileInfo);
  802. emit fileInfoAvailable(it.key(), QList<SftpFileInfo>() << fileInfo);
  803. emit finished(it.key());
  804. m_jobs.erase(it);
  805. return;
  806. }
  807. AbstractSftpTransfer::Ptr transfer
  808. = it.value().dynamicCast<AbstractSftpTransfer>();
  809. if (!transfer || transfer->state != AbstractSftpTransfer::Open
  810. || !transfer->statRequested) {
  811. throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
  812. "Unexpected SSH_FXP_ATTRS packet.");
  813. }
  814. Q_ASSERT(transfer->type() == AbstractSftpOperation::UploadFile
  815. || transfer->type() == AbstractSftpOperation::Download);
  816. if (transfer->type() == AbstractSftpOperation::Download) {
  817. SftpDownload::Ptr op = transfer.staticCast<SftpDownload>();
  818. if (response.attrs.sizePresent) {
  819. op->fileSize = response.attrs.size;
  820. } else {
  821. op->fileSize = 0;
  822. op->eofId = op->jobId;
  823. }
  824. op->statRequested = false;
  825. emit transferProgress(op->jobId, op->offset, op->fileSize);
  826. spawnReadRequests(op);
  827. } else {
  828. SftpUploadFile::Ptr op = transfer.staticCast<SftpUploadFile>();
  829. if (op->parentJob && op->parentJob->hasError) {
  830. op->hasError = true;
  831. sendTransferCloseHandle(op, op->jobId);
  832. return;
  833. }
  834. if (response.attrs.sizePresent) {
  835. op->offset = response.attrs.size;
  836. emit transferProgress(op->jobId, op->offset, op->fileSize);
  837. spawnWriteRequests(it);
  838. } else {
  839. if (op->parentJob)
  840. op->parentJob->setError();
  841. reportRequestError(op, SftpError::UnsupportedOperation, tr("Cannot append to remote file: "
  842. "Server does not support the file size attribute."));
  843. sendTransferCloseHandle(op, op->jobId);
  844. }
  845. }
  846. }
  847. void SftpChannelPrivate::handleDownloadDir(SftpListDir::Ptr op,
  848. const QList<SftpFileInfo> &fileInfoList)
  849. {
  850. if (op->parentJob->hasError) {
  851. return;
  852. }
  853. for (SftpFileInfo fileInfo : fileInfoList) {
  854. Internal::SftpDownloadDir::Dir dir = op->parentJob->lsdirsInProgress[op];
  855. QString fullPathRemote = QDir(dir.remoteDir).path() + u'/' + fileInfo.name;
  856. QString fullPathLocal = QDir(dir.localDir).path() + u'/' + fileInfo.name;
  857. if (fileInfo.type == FileTypeRegular) {
  858. QSharedPointer<QFile> localFile(new QFile(fullPathLocal));
  859. Internal::SftpDownload::Ptr downloadJob = Internal::SftpDownload::Ptr(
  860. new Internal::SftpDownload(++m_nextJobId, fullPathRemote, localFile,
  861. op->parentJob->mode, op->parentJob));
  862. op->parentJob->downloadsInProgress.append(downloadJob);
  863. createJob(downloadJob);
  864. } else if (fileInfo.type == FileTypeDirectory) {
  865. if (fileInfo.name == u"." || fileInfo.name == u"..") {
  866. continue;
  867. }
  868. if (!QDir().mkpath(fullPathLocal)) {
  869. reportRequestError(op, SftpError::GenericFailure, tr("Cannot create directory ") + fullPathLocal);
  870. break;
  871. }
  872. Internal::SftpListDir::Ptr lsdir = Internal::SftpListDir::Ptr(
  873. new Internal::SftpListDir(++m_nextJobId, fullPathRemote, op->parentJob));
  874. op->parentJob->lsdirsInProgress.insert(lsdir,
  875. Internal::SftpDownloadDir::Dir(fullPathLocal, fullPathRemote));
  876. createJob(lsdir);
  877. } else {
  878. // andres.pagliano TODO handle?
  879. }
  880. }
  881. }
  882. SftpChannelPrivate::JobMap::Iterator SftpChannelPrivate::lookupJob(SftpJobId id)
  883. {
  884. JobMap::Iterator it = m_jobs.find(id);
  885. if (it == m_jobs.end()) {
  886. throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
  887. "Invalid request id in SFTP packet.");
  888. }
  889. return it;
  890. }
  891. void SftpChannelPrivate::closeHook()
  892. {
  893. for (JobMap::ConstIterator it = m_jobs.constBegin(); it != m_jobs.constEnd(); ++it)
  894. emit finished(it.key(), SftpError::EndOfFile, tr("SFTP channel closed unexpectedly."));
  895. m_jobs.clear();
  896. m_incomingData.clear();
  897. m_incomingPacket.clear();
  898. emit closed();
  899. }
  900. void SftpChannelPrivate::handleOpenSuccessInternal()
  901. {
  902. qCDebug(sshLog, "SFTP session started");
  903. m_sendFacility.sendSftpPacket(remoteChannel());
  904. m_sftpState = SubsystemRequested;
  905. }
  906. void SftpChannelPrivate::handleOpenFailureInternal(const QString &reason)
  907. {
  908. if (channelState() != SessionRequested) {
  909. throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
  910. "Unexpected SSH_MSG_CHANNEL_OPEN_FAILURE packet.");
  911. }
  912. emit channelError(tr("Server could not start session: %1").arg(reason));
  913. }
  914. void SftpChannelPrivate::sendReadRequest(const SftpDownload::Ptr &job,
  915. quint32 requestId)
  916. {
  917. Q_ASSERT(job->eofId == SftpInvalidJob);
  918. sendData(m_outgoingPacket.generateReadFile(job->remoteHandle, job->offset,
  919. AbstractSftpPacket::MaxDataSize, requestId).rawData());
  920. job->offsets[requestId] = job->offset;
  921. job->offset += AbstractSftpPacket::MaxDataSize;
  922. if (job->offset >= job->fileSize)
  923. job->eofId = requestId;
  924. }
  925. void SftpChannelPrivate::reportRequestError(const AbstractSftpOperationWithHandle::Ptr &job,
  926. const SftpError errorType,
  927. const QString &error)
  928. {
  929. // andres.pagliano TODO refactor
  930. // Report list error during download dir
  931. SftpListDir::Ptr lsjob = job.dynamicCast<SftpListDir>();
  932. if (!lsjob.isNull() && lsjob->parentJob) {
  933. if (!lsjob->parentJob->hasError) {
  934. emit finished(lsjob->parentJob->jobId, errorType, error);
  935. lsjob->parentJob->hasError = true;
  936. }
  937. } else {
  938. // Report download error during recursive download dir
  939. SftpDownload::Ptr djob = job.dynamicCast<SftpDownload>();
  940. if (!djob.isNull() && djob->parentJob) {
  941. if (!djob->parentJob->hasError) {
  942. emit finished(djob->parentJob->jobId, errorType, error);
  943. djob->parentJob->hasError = true;
  944. }
  945. } else {
  946. // Other error
  947. emit finished(job->jobId, errorType, error);
  948. }
  949. }
  950. job->hasError = true;
  951. }
  952. void SftpChannelPrivate::finishTransferRequest(JobMap::Iterator it)
  953. {
  954. AbstractSftpTransfer::Ptr job = it.value().staticCast<AbstractSftpTransfer>();
  955. if (job->inFlightCount == 1)
  956. sendTransferCloseHandle(job, it.key());
  957. else
  958. removeTransferRequest(it);
  959. }
  960. void SftpChannelPrivate::sendTransferCloseHandle(const AbstractSftpTransfer::Ptr &job,
  961. quint32 requestId)
  962. {
  963. sendData(m_outgoingPacket.generateCloseHandle(job->remoteHandle,
  964. requestId).rawData());
  965. job->state = SftpDownload::CloseRequested;
  966. }
  967. void SftpChannelPrivate::attributesToFileInfo(const SftpFileAttributes &attributes,
  968. SftpFileInfo &fileInfo) const
  969. {
  970. if (attributes.sizePresent) {
  971. fileInfo.sizeValid = true;
  972. fileInfo.size = attributes.size;
  973. }
  974. if (attributes.permissionsPresent) {
  975. if (attributes.permissions & 0x8000) // S_IFREG
  976. fileInfo.type = FileTypeRegular;
  977. else if (attributes.permissions & 0x4000) // S_IFDIR
  978. fileInfo.type = FileTypeDirectory;
  979. else
  980. fileInfo.type = FileTypeOther;
  981. fileInfo.permissionsValid = true;
  982. fileInfo.permissions = {};
  983. if (attributes.timesPresent) {
  984. fileInfo.atime = attributes.atime;
  985. fileInfo.mtime = attributes.mtime;
  986. fileInfo.timestampsValid = true;
  987. }
  988. if (attributes.permissions & 00001) // S_IXOTH
  989. fileInfo.permissions |= QFile::ExeOther;
  990. if (attributes.permissions & 00002) // S_IWOTH
  991. fileInfo.permissions |= QFile::WriteOther;
  992. if (attributes.permissions & 00004) // S_IROTH
  993. fileInfo.permissions |= QFile::ReadOther;
  994. if (attributes.permissions & 00010) // S_IXGRP
  995. fileInfo.permissions |= QFile::ExeGroup;
  996. if (attributes.permissions & 00020) // S_IWGRP
  997. fileInfo.permissions |= QFile::WriteGroup;
  998. if (attributes.permissions & 00040) // S_IRGRP
  999. fileInfo.permissions |= QFile::ReadGroup;
  1000. if (attributes.permissions & 00100) // S_IXUSR
  1001. fileInfo.permissions |= QFile::ExeUser | QFile::ExeOwner;
  1002. if (attributes.permissions & 00200) // S_IWUSR
  1003. fileInfo.permissions |= QFile::WriteUser | QFile::WriteOwner;
  1004. if (attributes.permissions & 00400) // S_IRUSR
  1005. fileInfo.permissions |= QFile::ReadUser | QFile::ReadOwner;
  1006. }
  1007. }
  1008. void SftpChannelPrivate::removeTransferRequest(JobMap::Iterator it)
  1009. {
  1010. --it.value().staticCast<AbstractSftpTransfer>()->inFlightCount;
  1011. m_jobs.erase(it);
  1012. }
  1013. void SftpChannelPrivate::sendWriteRequest(JobMap::Iterator it)
  1014. {
  1015. SftpUploadFile::Ptr job = it.value().staticCast<SftpUploadFile>();
  1016. emit transferProgress(job->jobId, job->localFile->pos(), job->localFile->size());
  1017. QByteArray data = job->localFile->read(AbstractSftpPacket::MaxDataSize);
  1018. QFileDevice *fileDevice = qobject_cast<QFileDevice*>(job->localFile.data());
  1019. if (fileDevice && fileDevice->error() != QFileDevice::NoError) {
  1020. if (job->parentJob)
  1021. job->parentJob->setError();
  1022. reportRequestError(job, SftpError::GenericFailure, tr("Error reading local file: %1")
  1023. .arg(job->localFile->errorString()));
  1024. finishTransferRequest(it);
  1025. } else if (data.isEmpty()) {
  1026. finishTransferRequest(it);
  1027. } else {
  1028. sendData(m_outgoingPacket.generateWriteFile(job->remoteHandle,
  1029. job->offset, data, it.key()).rawData());
  1030. job->offset += AbstractSftpPacket::MaxDataSize;
  1031. }
  1032. }
  1033. void SftpChannelPrivate::spawnWriteRequests(JobMap::Iterator it)
  1034. {
  1035. SftpUploadFile::Ptr op = it.value().staticCast<SftpUploadFile>();
  1036. op->calculateInFlightCount(AbstractSftpPacket::MaxDataSize);
  1037. sendWriteRequest(it);
  1038. for (int i = 1; !op->hasError && i < op->inFlightCount; ++i)
  1039. sendWriteRequest(m_jobs.insert(++m_nextJobId, op));
  1040. }
  1041. void SftpChannelPrivate::spawnReadRequests(const SftpDownload::Ptr &job)
  1042. {
  1043. job->calculateInFlightCount(AbstractSftpPacket::MaxDataSize);
  1044. sendReadRequest(job, job->jobId);
  1045. for (int i = 1; i < job->inFlightCount; ++i) {
  1046. const quint32 requestId = ++m_nextJobId;
  1047. m_jobs.insert(requestId, job);
  1048. sendReadRequest(job, requestId);
  1049. }
  1050. }
  1051. } // namespace Internal
  1052. } // namespace QSsh