PX4 Firmware
PX4 Autopilot Software http://px4.io
mavlink_ftp.cpp
Go to the documentation of this file.
1 /****************************************************************************
2  *
3  * Copyright (c) 2014-2016 PX4 Development Team. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  * notice, this list of conditions and the following disclaimer in
13  * the documentation and/or other materials provided with the
14  * distribution.
15  * 3. Neither the name PX4 nor the names of its contributors may be
16  * used to endorse or promote products derived from this software
17  * without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
24  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
25  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
26  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
27  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
29  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30  * POSSIBILITY OF SUCH DAMAGE.
31  *
32  ****************************************************************************/
33 
34 /// @file mavlink_ftp.cpp
35 /// @author px4dev, Don Gagne <don@thegagnes.com>
36 
37 #include <crc32.h>
38 #include <unistd.h>
39 #include <stdio.h>
40 #include <fcntl.h>
41 #include <sys/stat.h>
42 #include <errno.h>
43 #include <cstring>
44 
45 #include "mavlink_ftp.h"
46 #include "mavlink_main.h"
48 
49 constexpr const char MavlinkFTP::_root_dir[];
50 
52  _mavlink(mavlink)
53 {
54  // initialize session
55  _session_info.fd = -1;
56 }
57 
59 {
60  delete[] _work_buffer1;
61  delete[] _work_buffer2;
62 }
63 
64 unsigned
66 {
67  if (_session_info.stream_download) {
68  return MAVLINK_MSG_ID_FILE_TRANSFER_PROTOCOL_LEN + MAVLINK_NUM_NON_PAYLOAD_BYTES;
69 
70  } else {
71  return 0;
72  }
73 }
74 
75 #ifdef MAVLINK_FTP_UNIT_TEST
76 void
77 MavlinkFTP::set_unittest_worker(ReceiveMessageFunc_t rcvMsgFunc, void *worker_data)
78 {
79  _utRcvMsgFunc = rcvMsgFunc;
80  _worker_data = worker_data;
81 }
82 #endif
83 
84 uint8_t
86 {
87 #ifdef MAVLINK_FTP_UNIT_TEST
88  // We use fake ids when unit testing
90 #else
91  // Not unit testing, use the real thing
92  return _mavlink->get_system_id();
93 #endif
94 }
95 
96 uint8_t
98 {
99 #ifdef MAVLINK_FTP_UNIT_TEST
100  // We use fake ids when unit testing
102 #else
103  // Not unit testing, use the real thing
104  return _mavlink->get_component_id();
105 #endif
106 }
107 
108 uint8_t
110 {
111 #ifdef MAVLINK_FTP_UNIT_TEST
112  // We use fake ids when unit testing
114 #else
115  // Not unit testing, use the real thing
116  return _mavlink->get_channel();
117 #endif
118 }
119 
120 void
121 MavlinkFTP::handle_message(const mavlink_message_t *msg)
122 {
123  //warnx("MavlinkFTP::handle_message %d %d", buf_size_1, buf_size_2);
124 
125  if (msg->msgid == MAVLINK_MSG_ID_FILE_TRANSFER_PROTOCOL) {
126  mavlink_file_transfer_protocol_t ftp_request;
127  mavlink_msg_file_transfer_protocol_decode(msg, &ftp_request);
128 
129 #ifdef MAVLINK_FTP_DEBUG
130  PX4_INFO("FTP: received ftp protocol message target_system: %d target_component: %d",
131  ftp_request.target_system, ftp_request.target_component);
132 #endif
133 
134  if ((ftp_request.target_system == _getServerSystemId() || ftp_request.target_system == 0) &&
135  (ftp_request.target_component == _getServerComponentId() || ftp_request.target_component == 0)) {
136  _process_request(&ftp_request, msg->sysid, msg->compid);
137  }
138  }
139 }
140 
141 /// @brief Processes an FTP message
142 void
144  mavlink_file_transfer_protocol_t *ftp_req,
145  uint8_t target_system_id,
146  uint8_t target_comp_id)
147 {
148  bool stream_send = false;
149  PayloadHeader *payload = reinterpret_cast<PayloadHeader *>(&ftp_req->payload[0]);
150 
151  ErrorCode errorCode = kErrNone;
152 
153  if (!_ensure_buffers_exist()) {
154  PX4_ERR("Failed to allocate buffers");
155  errorCode = kErrFailErrno;
156  errno = ENOMEM;
157  goto out;
158  }
159 
160  // basic sanity checks; must validate length before use
161  if (payload->size > kMaxDataLength) {
162  errorCode = kErrInvalidDataSize;
163  goto out;
164  }
165 
166  // check the sequence number: if this is a resent request, resend the last response
167  if (_last_reply_valid) {
168  mavlink_file_transfer_protocol_t *last_reply = reinterpret_cast<mavlink_file_transfer_protocol_t *>(_last_reply);
169  PayloadHeader *last_payload = reinterpret_cast<PayloadHeader *>(&last_reply->payload[0]);
170 
171  if (payload->seq_number + 1 == last_payload->seq_number) {
172  // this is the same request as the one we replied to last. It means the (n)ack got lost, and the GCS
173  // resent the request
174  mavlink_msg_file_transfer_protocol_send_struct(_mavlink->get_channel(), last_reply);
175  return;
176  }
177  }
178 
179 
180 
181 #ifdef MAVLINK_FTP_DEBUG
182  PX4_INFO("ftp: channel %u opc %u size %u offset %u", _getServerChannel(), payload->opcode, payload->size,
183  payload->offset);
184 #endif
185 
186  switch (payload->opcode) {
187  case kCmdNone:
188  break;
189 
191  errorCode = _workTerminate(payload);
192  break;
193 
194  case kCmdResetSessions:
195  errorCode = _workReset(payload);
196  break;
197 
198  case kCmdListDirectory:
199  errorCode = _workList(payload);
200  break;
201 
202  case kCmdOpenFileRO:
203  errorCode = _workOpen(payload, O_RDONLY);
204  break;
205 
206  case kCmdCreateFile:
207  errorCode = _workOpen(payload, O_CREAT | O_EXCL | O_WRONLY);
208  break;
209 
210  case kCmdOpenFileWO:
211  errorCode = _workOpen(payload, O_CREAT | O_WRONLY);
212  break;
213 
214  case kCmdReadFile:
215  errorCode = _workRead(payload);
216  break;
217 
218  case kCmdBurstReadFile:
219  errorCode = _workBurst(payload, target_system_id, target_comp_id);
220  stream_send = true;
221  break;
222 
223  case kCmdWriteFile:
224  errorCode = _workWrite(payload);
225  break;
226 
227  case kCmdRemoveFile:
228  errorCode = _workRemoveFile(payload);
229  break;
230 
231  case kCmdRename:
232  errorCode = _workRename(payload);
233  break;
234 
235  case kCmdTruncateFile:
236  errorCode = _workTruncateFile(payload);
237  break;
238 
239  case kCmdCreateDirectory:
240  errorCode = _workCreateDirectory(payload);
241  break;
242 
243  case kCmdRemoveDirectory:
244  errorCode = _workRemoveDirectory(payload);
245  break;
246 
247  case kCmdCalcFileCRC32:
248  errorCode = _workCalcFileCRC32(payload);
249  break;
250 
251  default:
252  errorCode = kErrUnknownCommand;
253  break;
254  }
255 
256 out:
257  payload->seq_number++;
258 
259  // handle success vs. error
260  if (errorCode == kErrNone) {
261  payload->req_opcode = payload->opcode;
262  payload->opcode = kRspAck;
263 
264  } else {
265  int r_errno = errno;
266  payload->req_opcode = payload->opcode;
267  payload->opcode = kRspNak;
268  payload->size = 1;
269 
270  if (r_errno == EEXIST) {
271  errorCode = kErrFailFileExists;
272  }
273 
274  payload->data[0] = errorCode;
275 
276  if (errorCode == kErrFailErrno) {
277  payload->size = 2;
278  payload->data[1] = r_errno;
279  }
280  }
281 
282  _last_reply_valid = false;
283 
284  // Stream download replies are sent through mavlink stream mechanism. Unless we need to Nack.
285  if (!stream_send || errorCode != kErrNone) {
286  // respond to the request
287  ftp_req->target_system = target_system_id;
288  ftp_req->target_network = 0;
289  ftp_req->target_component = target_comp_id;
290  _reply(ftp_req);
291  }
292 }
293 
295 {
297 
298  if (!_work_buffer1) {
299  _work_buffer1 = new char[_work_buffer1_len];
300  }
301 
302  if (!_work_buffer2) {
303  _work_buffer2 = new char[_work_buffer2_len];
304  }
305 
306  return _work_buffer1 && _work_buffer2;
307 }
308 
309 /// @brief Sends the specified FTP response message out through mavlink
310 void
311 MavlinkFTP::_reply(mavlink_file_transfer_protocol_t *ftp_req)
312 {
313  PayloadHeader *payload = reinterpret_cast<PayloadHeader *>(&ftp_req->payload[0]);
314 
315  // keep a copy of the last sent response ((n)ack), so that if it gets lost and the GCS resends the request,
316  // we can simply resend the response.
317  // we only keep small responses to reduce RAM usage and avoid large memcpy's. The larger responses are all data
318  // retrievals without side-effects, meaning it's ok to reexecute them if a response gets lost
319  if (payload->size <= sizeof(uint32_t)) {
320  _last_reply_valid = true;
321  memcpy(_last_reply, ftp_req, sizeof(_last_reply));
322  }
323 
324 #ifdef MAVLINK_FTP_DEBUG
325  PX4_INFO("FTP: %s seq_number: %d", payload->opcode == kRspAck ? "Ack" : "Nak", payload->seq_number);
326 #endif
327 
328 #ifdef MAVLINK_FTP_UNIT_TEST
329  // Unit test hook is set, call that instead
330  _utRcvMsgFunc(ftp_req, _worker_data);
331 #else
332  mavlink_msg_file_transfer_protocol_send_struct(_mavlink->get_channel(), ftp_req);
333 #endif
334 
335 }
336 
337 /// @brief Responds to a List command
339 MavlinkFTP::_workList(PayloadHeader *payload, bool list_hidden)
340 {
343  // ensure termination
344  _work_buffer1[_work_buffer1_len - 1] = '\0';
345 
346  ErrorCode errorCode = kErrNone;
347  unsigned offset = 0;
348 
349  DIR *dp = opendir(_work_buffer1);
350 
351  if (dp == nullptr) {
352  PX4_WARN("File open failed %s", _work_buffer1);
353  // this is not an FTP error, abort directory by setting errno to ENOENT "No such file or directory"
354  errno = ENOENT;
355  return kErrFailErrno;
356  }
357 
358 #ifdef MAVLINK_FTP_DEBUG
359  PX4_INFO("FTP: list %s offset %d", _work_buffer1, payload->offset);
360 #endif
361 
362  struct dirent *result = nullptr;
363 
364  // move to the requested offset
365  int requested_offset = payload->offset;
366 
367  while (requested_offset-- > 0 && readdir(dp)) {}
368 
369  for (;;) {
370  errno = 0;
371  result = readdir(dp);
372 
373  // read the directory entry
374  if (result == nullptr) {
375  if (errno) {
376  PX4_WARN("readdir failed");
377  payload->data[offset++] = kDirentSkip;
378  *((char *)&payload->data[offset]) = '\0';
379  offset++;
380  payload->size = offset;
381  closedir(dp);
382 
383  return errorCode;
384  }
385 
386  // no more entries?
387  if (payload->offset != 0 && offset == 0) {
388  // User is requesting subsequent dir entries but there were none. This means the user asked
389  // to seek past EOF.
390  errorCode = kErrEOF;
391  }
392 
393  // Otherwise we are just at the last directory entry, so we leave the errorCode at kErrorNone to signal that
394  break;
395  }
396 
397  uint32_t fileSize = 0;
398  char direntType;
399 
400  // Determine the directory entry type
401  switch (result->d_type) {
402 #ifdef __PX4_NUTTX
403 
404  case DTYPE_FILE: {
405 #else
406 
407  case DT_REG: {
408 #endif
409  // For files we get the file size as well
410  direntType = kDirentFile;
411  int ret = snprintf(_work_buffer2, _work_buffer2_len, "%s/%s", _work_buffer1, result->d_name);
412  bool buf_is_ok = ((ret > 0) && (ret < _work_buffer2_len));
413 
414  if (buf_is_ok) {
415  struct stat st;
416 
417  if (stat(_work_buffer2, &st) == 0) {
418  fileSize = st.st_size;
419  }
420  }
421 
422  break;
423  }
424 
425 #ifdef __PX4_NUTTX
426 
427  case DTYPE_DIRECTORY:
428 #else
429  case DT_DIR:
430 #endif
431  if ((!list_hidden && (strncmp(result->d_name, ".", 1) == 0)) ||
432  strcmp(result->d_name, ".") == 0 || strcmp(result->d_name, "..") == 0) {
433  // Don't bother sending these back
434  direntType = kDirentSkip;
435 
436  } else {
437  direntType = kDirentDir;
438  }
439 
440  break;
441 
442  default:
443  // We only send back file and diretory entries, skip everything else
444  direntType = kDirentSkip;
445  }
446 
447  if (direntType == kDirentSkip) {
448  // Skip send only dirent identifier
449  _work_buffer2[0] = '\0';
450 
451  } else if (direntType == kDirentFile) {
452  // Files send filename and file length
453  int ret = snprintf(_work_buffer2, _work_buffer2_len, "%s\t%d", result->d_name, fileSize);
454  bool buf_is_ok = ((ret > 0) && (ret < _work_buffer2_len));
455 
456  if (!buf_is_ok) {
457  _work_buffer2[_work_buffer2_len - 1] = '\0';
458  }
459 
460  } else {
461  // Everything else just sends name
462  strncpy(_work_buffer2, result->d_name, _work_buffer2_len);
463  _work_buffer2[_work_buffer2_len - 1] = '\0';
464  }
465 
466  size_t nameLen = strlen(_work_buffer2);
467 
468  // Do we have room for the name, the one char directory identifier and the null terminator?
469  if ((offset + nameLen + 2) > kMaxDataLength) {
470  break;
471  }
472 
473  // Move the data into the buffer
474  payload->data[offset++] = direntType;
475  strcpy((char *)&payload->data[offset], _work_buffer2);
476 #ifdef MAVLINK_FTP_DEBUG
477  PX4_INFO("FTP: list %s %s", _work_buffer1, (char *)&payload->data[offset - 1]);
478 #endif
479  offset += nameLen + 1;
480  }
481 
482  closedir(dp);
483  payload->size = offset;
484 
485  return errorCode;
486 }
487 
488 /// @brief Responds to an Open command
490 MavlinkFTP::_workOpen(PayloadHeader *payload, int oflag)
491 {
492  if (_session_info.fd >= 0) {
493  PX4_ERR("FTP: Open failed - out of sessions\n");
495  }
496 
499 
500 #ifdef MAVLINK_FTP_DEBUG
501  PX4_INFO("FTP: open '%s'", _work_buffer1);
502 #endif
503 
504  uint32_t fileSize = 0;
505  struct stat st;
506 
507  if (stat(_work_buffer1, &st) != 0) {
508  // fail only if requested open for read
509  if (oflag & O_RDONLY) {
510  return kErrFailErrno;
511 
512  } else {
513  st.st_size = 0;
514  }
515  }
516 
517  fileSize = st.st_size;
518 
519  // Set mode to 666 incase oflag has O_CREAT
520  int fd = ::open(_work_buffer1, oflag, PX4_O_MODE_666);
521 
522  if (fd < 0) {
523  return kErrFailErrno;
524  }
525 
526  _session_info.fd = fd;
527  _session_info.file_size = fileSize;
528  _session_info.stream_download = false;
529 
530  payload->session = 0;
531  payload->size = sizeof(uint32_t);
532  std::memcpy(payload->data, &fileSize, payload->size);
533 
534  return kErrNone;
535 }
536 
537 /// @brief Responds to a Read command
539 MavlinkFTP::_workRead(PayloadHeader *payload)
540 {
541  if (payload->session != 0 || _session_info.fd < 0) {
542  return kErrInvalidSession;
543  }
544 
545 #ifdef MAVLINK_FTP_DEBUG
546  PX4_INFO("FTP: read offset:%d", payload->offset);
547 #endif
548 
549  // We have to test seek past EOF ourselves, lseek will allow seek past EOF
550  if (payload->offset >= _session_info.file_size) {
551  PX4_ERR("request past EOF");
552  return kErrEOF;
553  }
554 
555  if (lseek(_session_info.fd, payload->offset, SEEK_SET) < 0) {
556  PX4_ERR("seek fail");
557  return kErrFailErrno;
558  }
559 
560  int bytes_read = ::read(_session_info.fd, &payload->data[0], kMaxDataLength);
561 
562  if (bytes_read < 0) {
563  // Negative return indicates error other than eof
564  PX4_ERR("read fail %d", bytes_read);
565  return kErrFailErrno;
566  }
567 
568  payload->size = bytes_read;
569 
570  return kErrNone;
571 }
572 
573 /// @brief Responds to a Stream command
575 MavlinkFTP::_workBurst(PayloadHeader *payload, uint8_t target_system_id, uint8_t target_component_id)
576 {
577  if (payload->session != 0 && _session_info.fd < 0) {
578  return kErrInvalidSession;
579  }
580 
581 #ifdef MAVLINK_FTP_DEBUG
582  PX4_INFO("FTP: burst offset:%d", payload->offset);
583 #endif
584  // Setup for streaming sends
585  _session_info.stream_download = true;
586  _session_info.stream_offset = payload->offset;
587  _session_info.stream_chunk_transmitted = 0;
588  _session_info.stream_seq_number = payload->seq_number + 1;
589  _session_info.stream_target_system_id = target_system_id;
590  _session_info.stream_target_component_id = target_component_id;
591 
592  return kErrNone;
593 }
594 
595 /// @brief Responds to a Write command
597 MavlinkFTP::_workWrite(PayloadHeader *payload)
598 {
599  if (payload->session != 0 && _session_info.fd < 0) {
600  return kErrInvalidSession;
601  }
602 
603  if (lseek(_session_info.fd, payload->offset, SEEK_SET) < 0) {
604  // Unable to see to the specified location
605  PX4_ERR("seek fail");
606  return kErrFailErrno;
607  }
608 
609  int bytes_written = ::write(_session_info.fd, &payload->data[0], payload->size);
610 
611  if (bytes_written < 0) {
612  // Negative return indicates error other than eof
613  PX4_ERR("write fail %d", bytes_written);
614  return kErrFailErrno;
615  }
616 
617  payload->size = sizeof(uint32_t);
618  std::memcpy(payload->data, &bytes_written, payload->size);
619 
620  return kErrNone;
621 }
622 
623 /// @brief Responds to a RemoveFile command
625 MavlinkFTP::_workRemoveFile(PayloadHeader *payload)
626 {
629  // ensure termination
630  _work_buffer1[_work_buffer1_len - 1] = '\0';
631 
632  if (unlink(_work_buffer1) == 0) {
633  payload->size = 0;
634  return kErrNone;
635 
636  } else {
637  return kErrFailErrno;
638  }
639 }
640 
641 /// @brief Responds to a TruncateFile command
643 MavlinkFTP::_workTruncateFile(PayloadHeader *payload)
644 {
647  // ensure termination
648  _work_buffer1[_work_buffer1_len - 1] = '\0';
649  payload->size = 0;
650 
651 #ifdef __PX4_NUTTX
652 
653  // emulate truncate(_work_buffer1, payload->offset) by
654  // copying to temp and overwrite with O_TRUNC flag (NuttX does not support truncate()).
655  const char temp_file[] = PX4_STORAGEDIR"/.trunc.tmp";
656 
657  struct stat st;
658 
659  if (stat(_work_buffer1, &st) != 0) {
660  return kErrFailErrno;
661  }
662 
663  if (!S_ISREG(st.st_mode)) {
664  errno = EISDIR;
665  return kErrFailErrno;
666  }
667 
668  // check perms allow us to write (not romfs)
669  if (!(st.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))) {
670  errno = EROFS;
671  return kErrFailErrno;
672  }
673 
674  if (payload->offset == (unsigned)st.st_size) {
675  // nothing to do
676  return kErrNone;
677 
678  } else if (payload->offset == 0) {
679  // 1: truncate all data
680  int fd = ::open(_work_buffer1, O_TRUNC | O_WRONLY);
681 
682  if (fd < 0) {
683  return kErrFailErrno;
684  }
685 
686  ::close(fd);
687  return kErrNone;
688 
689  } else if (payload->offset > (unsigned)st.st_size) {
690  // 2: extend file
691  int fd = ::open(_work_buffer1, O_WRONLY);
692 
693  if (fd < 0) {
694  return kErrFailErrno;
695  }
696 
697  if (lseek(fd, payload->offset - 1, SEEK_SET) < 0) {
698  ::close(fd);
699  return kErrFailErrno;
700  }
701 
702  bool ok = 1 == ::write(fd, "", 1);
703  ::close(fd);
704 
705  return (ok) ? kErrNone : kErrFailErrno;
706 
707  } else {
708  // 3: truncate
709  if (_copy_file(_work_buffer1, temp_file, payload->offset) != 0) {
710  return kErrFailErrno;
711  }
712 
713  if (_copy_file(temp_file, _work_buffer1, payload->offset) != 0) {
714  return kErrFailErrno;
715  }
716 
717  if (::unlink(temp_file) != 0) {
718  return kErrFailErrno;
719  }
720 
721  return kErrNone;
722  }
723 
724 #else
725  int ret = truncate(_work_buffer1, payload->offset);
726 
727  if (ret == 0) {
728  return kErrNone;
729  }
730 
731  return kErrFailErrno;
732 #endif /* __PX4_NUTTX */
733 }
734 
735 /// @brief Responds to a Terminate command
737 MavlinkFTP::_workTerminate(PayloadHeader *payload)
738 {
739  if (payload->session != 0 || _session_info.fd < 0) {
740  return kErrInvalidSession;
741  }
742 
743  ::close(_session_info.fd);
744  _session_info.fd = -1;
745  _session_info.stream_download = false;
746 
747  payload->size = 0;
748 
749  return kErrNone;
750 }
751 
752 /// @brief Responds to a Reset command
754 MavlinkFTP::_workReset(PayloadHeader *payload)
755 {
756  if (_session_info.fd != -1) {
757  ::close(_session_info.fd);
758  _session_info.fd = -1;
759  _session_info.stream_download = false;
760  }
761 
762  payload->size = 0;
763 
764  return kErrNone;
765 }
766 
767 /// @brief Responds to a Rename command
769 MavlinkFTP::_workRename(PayloadHeader *payload)
770 {
771  char *ptr = _data_as_cstring(payload);
772  size_t oldpath_sz = strlen(ptr);
773 
774  if (oldpath_sz == payload->size) {
775  // no newpath
776  errno = EINVAL;
777  return kErrFailErrno;
778  }
779 
782  _work_buffer1[_work_buffer1_len - 1] = '\0'; // ensure termination
783 
785  strncpy(_work_buffer2 + _root_dir_len, ptr + oldpath_sz + 1, _work_buffer2_len - _root_dir_len);
786  _work_buffer2[_work_buffer2_len - 1] = '\0'; // ensure termination
787 
788  if (rename(_work_buffer1, _work_buffer2) == 0) {
789  payload->size = 0;
790  return kErrNone;
791 
792  } else {
793  return kErrFailErrno;
794  }
795 }
796 
797 /// @brief Responds to a RemoveDirectory command
799 MavlinkFTP::_workRemoveDirectory(PayloadHeader *payload)
800 {
803  // ensure termination
804  _work_buffer1[_work_buffer1_len - 1] = '\0';
805 
806  if (rmdir(_work_buffer1) == 0) {
807  payload->size = 0;
808  return kErrNone;
809 
810  } else {
811  return kErrFailErrno;
812  }
813 }
814 
815 /// @brief Responds to a CreateDirectory command
817 MavlinkFTP::_workCreateDirectory(PayloadHeader *payload)
818 {
821  // ensure termination
822  _work_buffer1[_work_buffer1_len - 1] = '\0';
823 
824  if (mkdir(_work_buffer1, S_IRWXU | S_IRWXG | S_IRWXO) == 0) {
825  payload->size = 0;
826  return kErrNone;
827 
828  } else {
829  return kErrFailErrno;
830  }
831 }
832 
833 /// @brief Responds to a CalcFileCRC32 command
835 MavlinkFTP::_workCalcFileCRC32(PayloadHeader *payload)
836 {
837  uint32_t checksum = 0;
838  ssize_t bytes_read;
841  // ensure termination
842  _work_buffer2[_work_buffer2_len - 1] = '\0';
843 
844  int fd = ::open(_work_buffer2, O_RDONLY);
845 
846  if (fd < 0) {
847  return kErrFailErrno;
848  }
849 
850  do {
851  bytes_read = ::read(fd, _work_buffer2, _work_buffer2_len);
852 
853  if (bytes_read < 0) {
854  int r_errno = errno;
855  ::close(fd);
856  errno = r_errno;
857  return kErrFailErrno;
858  }
859 
860  checksum = crc32part((uint8_t *)_work_buffer2, bytes_read, checksum);
861  } while (bytes_read == _work_buffer2_len);
862 
863  ::close(fd);
864 
865  payload->size = sizeof(uint32_t);
866  std::memcpy(payload->data, &checksum, payload->size);
867  return kErrNone;
868 }
869 
870 /// @brief Guarantees that the payload data is null terminated.
871 /// @return Returns a pointer to the payload data as a char *
872 char *
873 MavlinkFTP::_data_as_cstring(PayloadHeader *payload)
874 {
875  // guarantee nul termination
876  if (payload->size < kMaxDataLength) {
877  payload->data[payload->size] = '\0';
878 
879  } else {
880  payload->data[kMaxDataLength - 1] = '\0';
881  }
882 
883  // and return data
884  return (char *) & (payload->data[0]);
885 }
886 
887 /// @brief Copy file (with limited space)
888 int
889 MavlinkFTP::_copy_file(const char *src_path, const char *dst_path, size_t length)
890 {
891  int src_fd = -1, dst_fd = -1;
892  int op_errno = 0;
893 
894  src_fd = ::open(src_path, O_RDONLY);
895 
896  if (src_fd < 0) {
897  return -1;
898  }
899 
900  dst_fd = ::open(dst_path, O_CREAT | O_TRUNC | O_WRONLY
901 // POSIX requires the permissions to be supplied if O_CREAT passed
902 #ifdef __PX4_POSIX
903  , 0666
904 #endif
905  );
906 
907  if (dst_fd < 0) {
908  op_errno = errno;
909  ::close(src_fd);
910  errno = op_errno;
911  return -1;
912  }
913 
914  while (length > 0) {
915  ssize_t bytes_read, bytes_written;
916  size_t blen = (length > _work_buffer2_len) ? _work_buffer2_len : length;
917 
918  bytes_read = ::read(src_fd, _work_buffer2, blen);
919 
920  if (bytes_read == 0) {
921  // EOF
922  break;
923 
924  } else if (bytes_read < 0) {
925  PX4_ERR("cp: read");
926  op_errno = errno;
927  break;
928  }
929 
930  bytes_written = ::write(dst_fd, _work_buffer2, bytes_read);
931 
932  if (bytes_written != bytes_read) {
933  PX4_ERR("cp: short write");
934  op_errno = errno;
935  break;
936  }
937 
938  length -= bytes_written;
939  }
940 
941  ::close(src_fd);
942  ::close(dst_fd);
943 
944  errno = op_errno;
945  return (length > 0) ? -1 : 0;
946 }
947 
949 {
950 
951  if (_work_buffer1 || _work_buffer2) {
952  // free the work buffers if they are not used for a while
953  if (hrt_elapsed_time(&_last_work_buffer_access) > 2000000) {
954  if (_work_buffer1) {
955  delete[] _work_buffer1;
956  _work_buffer1 = nullptr;
957  }
958 
959  if (_work_buffer2) {
960  delete[] _work_buffer2;
961  _work_buffer2 = nullptr;
962  }
963  }
964  }
965 
966  // Anything to stream?
967  if (!_session_info.stream_download) {
968  return;
969  }
970 
971 #ifndef MAVLINK_FTP_UNIT_TEST
972  // Skip send if not enough room
973  unsigned max_bytes_to_send = _mavlink->get_free_tx_buf();
974 #ifdef MAVLINK_FTP_DEBUG
975  PX4_INFO("MavlinkFTP::send max_bytes_to_send(%d) get_free_tx_buf(%d)", max_bytes_to_send, _mavlink->get_free_tx_buf());
976 #endif
977 
978  if (max_bytes_to_send < get_size()) {
979  return;
980  }
981 
982 #endif
983 
984  // Send stream packets until buffer is full
985 
986  bool more_data;
987 
988  do {
989  more_data = false;
990 
991  ErrorCode error_code = kErrNone;
992 
993  mavlink_file_transfer_protocol_t ftp_msg;
994  PayloadHeader *payload = reinterpret_cast<PayloadHeader *>(&ftp_msg.payload[0]);
995 
996  payload->seq_number = _session_info.stream_seq_number;
997  payload->session = 0;
998  payload->opcode = kRspAck;
999  payload->req_opcode = kCmdBurstReadFile;
1000  payload->offset = _session_info.stream_offset;
1001  _session_info.stream_seq_number++;
1002 
1003 #ifdef MAVLINK_FTP_DEBUG
1004  PX4_INFO("stream send: offset %d", _session_info.stream_offset);
1005 #endif
1006 
1007  // We have to test seek past EOF ourselves, lseek will allow seek past EOF
1008  if (_session_info.stream_offset >= _session_info.file_size) {
1009  error_code = kErrEOF;
1010 #ifdef MAVLINK_FTP_DEBUG
1011  PX4_INFO("stream download: sending Nak EOF");
1012 #endif
1013  }
1014 
1015  if (error_code == kErrNone) {
1016  if (lseek(_session_info.fd, payload->offset, SEEK_SET) < 0) {
1017  error_code = kErrFailErrno;
1018 #ifdef MAVLINK_FTP_DEBUG
1019  PX4_WARN("stream download: seek fail");
1020 #endif
1021  }
1022  }
1023 
1024  if (error_code == kErrNone) {
1025  int bytes_read = ::read(_session_info.fd, &payload->data[0], kMaxDataLength);
1026 
1027  if (bytes_read < 0) {
1028  // Negative return indicates error other than eof
1029  error_code = kErrFailErrno;
1030 #ifdef MAVLINK_FTP_DEBUG
1031  PX4_WARN("stream download: read fail");
1032 #endif
1033 
1034  } else {
1035  payload->size = bytes_read;
1036  _session_info.stream_offset += bytes_read;
1037  _session_info.stream_chunk_transmitted += bytes_read;
1038  }
1039  }
1040 
1041  if (error_code != kErrNone) {
1042  payload->opcode = kRspNak;
1043  payload->size = 1;
1044  uint8_t *pData = &payload->data[0];
1045  *pData = error_code; // Straight reference to data[0] is causing bogus gcc array subscript error
1046 
1047  if (error_code == kErrFailErrno) {
1048  int r_errno = errno;
1049  payload->size = 2;
1050  payload->data[1] = r_errno;
1051  }
1052 
1053  _session_info.stream_download = false;
1054 
1055  } else {
1056 #ifndef MAVLINK_FTP_UNIT_TEST
1057 
1058  if (max_bytes_to_send < (get_size() * 2)) {
1059  more_data = false;
1060 
1061  /* perform transfers in 35K chunks - this is determined empirical */
1062  if (_session_info.stream_chunk_transmitted > 35000) {
1063  payload->burst_complete = true;
1064  _session_info.stream_download = false;
1065  _session_info.stream_chunk_transmitted = 0;
1066  }
1067 
1068  } else {
1069 #endif
1070  more_data = true;
1071  payload->burst_complete = false;
1072 #ifndef MAVLINK_FTP_UNIT_TEST
1073  max_bytes_to_send -= get_size();
1074  }
1075 
1076 #endif
1077  }
1078 
1079  ftp_msg.target_system = _session_info.stream_target_system_id;
1080  ftp_msg.target_network = 0;
1081  ftp_msg.target_component = _session_info.stream_target_component_id;
1082  _reply(&ftp_msg);
1083  } while (more_data);
1084 }
static char msg[NUM_MSG][CONFIG_USART1_TXBUFSIZE]
Definition: px4io.c:89
static void read(bootloader_app_shared_t *pshared)
static hrt_abstime hrt_elapsed_time(const hrt_abstime *then)
Compute the delta between a timestamp taken in the past and now.
Definition: drv_hrt.h:102
__BEGIN_DECLS typedef uint64_t hrt_abstime
Absolute time, in microsecond units.
Definition: drv_hrt.h:58
int fd
Definition: dataman.cpp:146
static void write(bootloader_app_shared_t *pshared)
__EXPORT hrt_abstime hrt_absolute_time(void)
Get absolute time in [us] (does not wrap).