mirror of
https://github.com/ArduPilot/ardupilot
synced 2025-01-07 00:18:29 -04:00
744a741b99
The actual filesize of the parameter downloadfile is around 15200 bytes. The indicated filesize is used in QGC for the progressbar. This patch does not try to compute the exact filesize but I try a better estimate. Only the full download off all parameters is considered to avoid more complexity.
656 lines
18 KiB
C++
656 lines
18 KiB
C++
/*
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
/*
|
|
ArduPilot filesystem interface for parameters
|
|
*/
|
|
|
|
#include "AP_Filesystem_config.h"
|
|
|
|
#if AP_FILESYSTEM_PARAM_ENABLED
|
|
|
|
#include "AP_Filesystem.h"
|
|
#include "AP_Filesystem_Param.h"
|
|
#include <AP_Param/AP_Param.h>
|
|
#include <AP_Math/AP_Math.h>
|
|
#include <ctype.h>
|
|
|
|
#define PACKED_NAME "param.pck"
|
|
|
|
extern const AP_HAL::HAL& hal;
|
|
extern int errno;
|
|
|
|
int AP_Filesystem_Param::open(const char *fname, int flags, bool allow_absolute_path)
|
|
{
|
|
if (!check_file_name(fname)) {
|
|
errno = ENOENT;
|
|
return -1;
|
|
}
|
|
bool read_only = ((flags & O_ACCMODE) == O_RDONLY);
|
|
uint8_t idx;
|
|
for (idx=0; idx<max_open_file; idx++) {
|
|
if (!file[idx].open) {
|
|
break;
|
|
}
|
|
}
|
|
if (idx == max_open_file) {
|
|
errno = ENFILE;
|
|
return -1;
|
|
}
|
|
struct rfile &r = file[idx];
|
|
if (read_only) {
|
|
r.cursors = new cursor[num_cursors];
|
|
if (r.cursors == nullptr) {
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
}
|
|
r.file_ofs = 0;
|
|
r.open = true;
|
|
r.with_defaults = false;
|
|
r.start = 0;
|
|
r.count = 0;
|
|
r.read_size = 0;
|
|
r.file_size = 0;
|
|
r.writebuf = nullptr;
|
|
if (!read_only) {
|
|
// setup for upload
|
|
r.writebuf = new ExpandingString();
|
|
if (r.writebuf == nullptr) {
|
|
close(idx);
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
allow for URI style arguments param.pck?start=N&count=C
|
|
*/
|
|
const char *c = strchr(fname, '?');
|
|
while (c && *c) {
|
|
c++;
|
|
if (strncmp(c, "start=", 6) == 0) {
|
|
uint32_t v = strtoul(c+6, nullptr, 10);
|
|
if (v >= UINT16_MAX) {
|
|
goto failed;
|
|
}
|
|
r.start = v;
|
|
c += 6;
|
|
c = strchr(c, '&');
|
|
continue;
|
|
}
|
|
if (strncmp(c, "count=", 6) == 0) {
|
|
uint32_t v = strtoul(c+6, nullptr, 10);
|
|
if (v >= UINT16_MAX) {
|
|
goto failed;
|
|
}
|
|
r.count = v;
|
|
c += 6;
|
|
c = strchr(c, '&');
|
|
continue;
|
|
}
|
|
#if AP_PARAM_DEFAULTS_ENABLED
|
|
if (strncmp(c, "withdefaults=", 13) == 0) {
|
|
uint32_t v = strtoul(c+13, nullptr, 10);
|
|
if (v > 1) {
|
|
goto failed;
|
|
}
|
|
r.with_defaults = v == 1;
|
|
c += 13;
|
|
c = strchr(c, '&');
|
|
continue;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return idx;
|
|
|
|
failed:
|
|
delete [] r.cursors;
|
|
r.open = false;
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
int AP_Filesystem_Param::close(int fd)
|
|
{
|
|
if (fd < 0 || fd >= max_open_file || !file[fd].open) {
|
|
errno = EBADF;
|
|
return -1;
|
|
}
|
|
struct rfile &r = file[fd];
|
|
int ret = 0;
|
|
if (r.writebuf != nullptr && !finish_upload(r)) {
|
|
errno = EINVAL;
|
|
ret = -1;
|
|
}
|
|
r.open = false;
|
|
delete [] r.cursors;
|
|
r.cursors = nullptr;
|
|
delete r.writebuf;
|
|
r.writebuf = nullptr;
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
packed format:
|
|
file header:
|
|
uint16_t magic = 0x671b or 0x671c for included default values
|
|
uint16_t num_params
|
|
uint16_t total_params
|
|
|
|
per-parameter:
|
|
|
|
uint8_t type:4; // AP_Param type NONE=0, INT8=1, INT16=2, INT32=3, FLOAT=4
|
|
uint8_t flags:4; // bit 0: includes default value for this param
|
|
uint8_t common_len:4; // number of name bytes in common with previous entry, 0..15
|
|
uint8_t name_len:4; // non-common length of param name -1 (0..15)
|
|
uint8_t name[name_len]; // name
|
|
uint8_t data[]; // value, length given by variable type, data length doubled if default is included
|
|
|
|
Any leading zero bytes after the header should be discarded as pad
|
|
bytes. Pad bytes are used to ensure that a parameter data[] field
|
|
does not cross a read packet boundary
|
|
*/
|
|
|
|
/*
|
|
pack a single parameter. The buffer must be at least of size max_pack_len
|
|
*/
|
|
uint8_t AP_Filesystem_Param::pack_param(const struct rfile &r, struct cursor &c, uint8_t *buf)
|
|
{
|
|
char name[AP_MAX_NAME_SIZE+1];
|
|
name[AP_MAX_NAME_SIZE] = 0;
|
|
enum ap_var_type ptype;
|
|
AP_Param *ap;
|
|
float default_val;
|
|
|
|
if (c.token_ofs == 0) {
|
|
c.idx = 0;
|
|
ap = AP_Param::first(&c.token, &ptype, &default_val);
|
|
uint16_t idx = 0;
|
|
while (idx < r.start && ap) {
|
|
ap = AP_Param::next_scalar(&c.token, &ptype, &default_val);
|
|
idx++;
|
|
}
|
|
} else {
|
|
c.idx++;
|
|
ap = AP_Param::next_scalar(&c.token, &ptype, &default_val);
|
|
}
|
|
if (ap == nullptr || (r.count && c.idx >= r.count)) {
|
|
return 0;
|
|
}
|
|
ap->copy_name_token(c.token, name, AP_MAX_NAME_SIZE, true);
|
|
|
|
uint8_t common_len = 0;
|
|
const char *last_name = c.last_name;
|
|
const char *pname = name;
|
|
while (*pname == *last_name && *pname) {
|
|
common_len++;
|
|
pname++;
|
|
last_name++;
|
|
}
|
|
uint8_t name_len = strlen(pname);
|
|
if (name_len == 0) {
|
|
name_len = 1;
|
|
common_len--;
|
|
pname--;
|
|
}
|
|
#if AP_PARAM_DEFAULTS_ENABLED
|
|
const bool add_default = r.with_defaults && !is_equal(ap->cast_to_float(ptype), default_val);
|
|
#else
|
|
const bool add_default = false;
|
|
#endif
|
|
const uint8_t type_len = AP_Param::type_size(ptype);
|
|
uint8_t packed_len = type_len + name_len + 2 + (add_default ? type_len : 0);
|
|
const uint8_t flags = add_default;
|
|
|
|
/*
|
|
see if we need to add padding to ensure that a data field never
|
|
crosses a block boundary. This ensures that re-reading a block
|
|
won't get a corrupt value for a parameter
|
|
*/
|
|
if (type_len > 1) {
|
|
const uint32_t ofs = c.token_ofs + sizeof(struct header) + packed_len;
|
|
const uint32_t ofs_mod = ofs % r.read_size;
|
|
if (ofs_mod > 0 && ofs_mod < type_len) {
|
|
const uint8_t pad = type_len - ofs_mod;
|
|
memset(buf, 0, pad);
|
|
buf += pad;
|
|
packed_len += pad;
|
|
}
|
|
}
|
|
|
|
buf[0] = uint8_t(ptype) | (flags<<4);
|
|
buf[1] = common_len | ((name_len-1)<<4);
|
|
memcpy(&buf[2], pname, name_len);
|
|
memcpy(&buf[2+name_len], ap, type_len);
|
|
#if AP_PARAM_DEFAULTS_ENABLED
|
|
if (add_default) {
|
|
switch (ptype) {
|
|
case AP_PARAM_NONE:
|
|
case AP_PARAM_GROUP:
|
|
// should never happen...
|
|
break;
|
|
case AP_PARAM_INT8: {
|
|
const int32_t int8_default = default_val;
|
|
memcpy(&buf[2+name_len+type_len], &int8_default, type_len);
|
|
break;
|
|
}
|
|
case AP_PARAM_INT16: {
|
|
const int16_t int16_default = default_val;
|
|
memcpy(&buf[2+name_len+type_len], &int16_default, type_len);
|
|
break;
|
|
}
|
|
case AP_PARAM_INT32: {
|
|
const int32_t int32_default = default_val;
|
|
memcpy(&buf[2+name_len+type_len], &int32_default, type_len);
|
|
break;
|
|
}
|
|
case AP_PARAM_FLOAT:
|
|
case AP_PARAM_VECTOR3F: {
|
|
memcpy(&buf[2+name_len+type_len], &default_val, type_len);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
strcpy(c.last_name, name);
|
|
|
|
return packed_len;
|
|
}
|
|
|
|
/*
|
|
seek the token to match file offset
|
|
*/
|
|
bool AP_Filesystem_Param::token_seek(const struct rfile &r, const uint32_t data_ofs, struct cursor &c)
|
|
{
|
|
if (data_ofs == 0) {
|
|
memset(&c, 0, sizeof(c));
|
|
return true;
|
|
}
|
|
if (c.token_ofs > data_ofs) {
|
|
memset(&c, 0, sizeof(c));
|
|
}
|
|
|
|
if (c.trailer_len > 0) {
|
|
uint8_t n = MIN(c.trailer_len, data_ofs - c.token_ofs);
|
|
if (n != c.trailer_len) {
|
|
memmove(&c.trailer[0], &c.trailer[n], c.trailer_len - n);
|
|
}
|
|
c.trailer_len -= n;
|
|
c.token_ofs += n;
|
|
}
|
|
|
|
while (data_ofs != c.token_ofs) {
|
|
uint32_t available = data_ofs - c.token_ofs;
|
|
uint8_t tbuf[max_pack_len];
|
|
uint8_t len = pack_param(r, c, tbuf);
|
|
if (len == 0) {
|
|
// no more parameters
|
|
break;
|
|
}
|
|
uint8_t n = MIN(len, available);
|
|
if (len > available) {
|
|
c.trailer_len = len - available;
|
|
memcpy(c.trailer, &tbuf[n], c.trailer_len);
|
|
}
|
|
c.token_ofs += n;
|
|
}
|
|
return data_ofs == c.token_ofs;
|
|
}
|
|
|
|
int32_t AP_Filesystem_Param::read(int fd, void *buf, uint32_t count)
|
|
{
|
|
if (fd < 0 || fd >= max_open_file || !file[fd].open) {
|
|
errno = EBADF;
|
|
return -1;
|
|
}
|
|
|
|
struct rfile &r = file[fd];
|
|
if (r.writebuf != nullptr) {
|
|
// no read on upload
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
size_t header_total = 0;
|
|
|
|
/*
|
|
we only allow for a single read size. This ensures that pad
|
|
bytes placed to avoid a data value crossing a block boundary in
|
|
the ftp protocol do not change when filling in lost packets
|
|
*/
|
|
if (r.read_size == 0 && count > 0) {
|
|
r.read_size = count;
|
|
}
|
|
if (r.read_size != 0 && r.read_size != count) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
if (r.file_size != 0) {
|
|
// ensure we don't try to read past EOF
|
|
if (r.file_ofs > r.file_size) {
|
|
count = 0;
|
|
} else {
|
|
count = MIN(count, r.file_size - r.file_ofs);
|
|
}
|
|
}
|
|
|
|
if (r.file_ofs < sizeof(struct header)) {
|
|
struct header hdr;
|
|
hdr.total_params = AP_Param::count_parameters();
|
|
if (hdr.total_params <= r.start) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
hdr.num_params = hdr.total_params - r.start;
|
|
if (r.count > 0 && hdr.num_params > r.count) {
|
|
hdr.num_params = r.count;
|
|
}
|
|
uint8_t n = MIN(sizeof(hdr) - r.file_ofs, count);
|
|
if (r.with_defaults) {
|
|
hdr.magic = pmagic_with_default;
|
|
}
|
|
const uint8_t *b = (const uint8_t *)&hdr;
|
|
memcpy(buf, &b[r.file_ofs], n);
|
|
count -= n;
|
|
header_total += n;
|
|
r.file_ofs += n;
|
|
buf = (void *)(n + (const uint8_t *)buf);
|
|
if (count == 0) {
|
|
return header_total;
|
|
}
|
|
}
|
|
|
|
uint32_t data_ofs = r.file_ofs - sizeof(struct header);
|
|
uint8_t best_i = 0;
|
|
uint32_t best_ofs = r.cursors[0].token_ofs;
|
|
size_t total = 0;
|
|
|
|
// find the first cursor that is positioned after the file offset
|
|
for (uint8_t i=1; i<num_cursors; i++) {
|
|
struct cursor &c = r.cursors[i];
|
|
if (c.token_ofs >= data_ofs && c.token_ofs < best_ofs) {
|
|
best_i = i;
|
|
best_ofs = c.token_ofs;
|
|
}
|
|
}
|
|
struct cursor &c = r.cursors[best_i];
|
|
|
|
if (data_ofs != c.token_ofs) {
|
|
if (!token_seek(r, data_ofs, c)) {
|
|
// must be EOF
|
|
return header_total;
|
|
}
|
|
}
|
|
if (count == 0) {
|
|
return header_total;
|
|
}
|
|
uint8_t *ubuf = (uint8_t *)buf;
|
|
|
|
if (c.trailer_len > 0) {
|
|
uint8_t n = MIN(c.trailer_len, count);
|
|
memcpy(ubuf, c.trailer, n);
|
|
count -= n;
|
|
ubuf += n;
|
|
if (n != c.trailer_len) {
|
|
memmove(&c.trailer[0], &c.trailer[n], c.trailer_len - n);
|
|
}
|
|
c.trailer_len -= n;
|
|
total += n;
|
|
c.token_ofs += n;
|
|
}
|
|
|
|
while (count > 0) {
|
|
uint8_t tbuf[max_pack_len];
|
|
uint8_t len = pack_param(r, c, tbuf);
|
|
if (len == 0) {
|
|
// no more params, use this to trigger EOF in later reads
|
|
const uint32_t size = r.file_ofs + total;
|
|
if (r.file_size == 0) {
|
|
r.file_size = size;
|
|
} else {
|
|
r.file_size = MIN(size, r.file_size);
|
|
}
|
|
break;
|
|
}
|
|
uint8_t n = MIN(len, count);
|
|
if (len > count) {
|
|
c.trailer_len = len - count;
|
|
memcpy(c.trailer, &tbuf[count], c.trailer_len);
|
|
}
|
|
memcpy(ubuf, tbuf, n);
|
|
count -= n;
|
|
ubuf += n;
|
|
total += n;
|
|
c.token_ofs += n;
|
|
}
|
|
r.file_ofs += total;
|
|
return total + header_total;
|
|
}
|
|
|
|
int32_t AP_Filesystem_Param::lseek(int fd, int32_t offset, int seek_from)
|
|
{
|
|
if (fd < 0 || fd >= max_open_file || !file[fd].open) {
|
|
errno = EBADF;
|
|
return -1;
|
|
}
|
|
struct rfile &r = file[fd];
|
|
switch (seek_from) {
|
|
case SEEK_SET:
|
|
r.file_ofs = offset;
|
|
break;
|
|
case SEEK_CUR:
|
|
r.file_ofs += offset;
|
|
break;
|
|
case SEEK_END:
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
return r.file_ofs;
|
|
}
|
|
|
|
int AP_Filesystem_Param::stat(const char *name, struct stat *stbuf)
|
|
{
|
|
if (!check_file_name(name)) {
|
|
errno = ENOENT;
|
|
return -1;
|
|
}
|
|
memset(stbuf, 0, sizeof(*stbuf));
|
|
// give size estimation to avoid needing to scan entire file
|
|
stbuf->st_size = AP_Param::count_parameters() * 12;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
check for the right file name
|
|
*/
|
|
bool AP_Filesystem_Param::check_file_name(const char *name)
|
|
{
|
|
const uint8_t packed_len = strlen(PACKED_NAME);
|
|
if (strncmp(name, PACKED_NAME, packed_len) == 0 &&
|
|
(name[packed_len] == 0 || name[packed_len] == '?')) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
support param upload
|
|
*/
|
|
int32_t AP_Filesystem_Param::write(int fd, const void *buf, uint32_t count)
|
|
{
|
|
if (fd < 0 || fd >= max_open_file || !file[fd].open) {
|
|
errno = EBADF;
|
|
return -1;
|
|
}
|
|
struct rfile &r = file[fd];
|
|
if (r.writebuf == nullptr) {
|
|
errno = EBADF;
|
|
return -1;
|
|
}
|
|
struct header hdr;
|
|
if (r.file_ofs == 0 && count >= sizeof(hdr)) {
|
|
// pre-expand the buffer to the full size when we get the header
|
|
memcpy(&hdr, buf, sizeof(hdr));
|
|
if (hdr.magic == pmagic) {
|
|
const uint32_t flen = hdr.total_params;
|
|
if (flen > r.writebuf->get_length()) {
|
|
if (!r.writebuf->append(nullptr, flen - r.writebuf->get_length())) {
|
|
// not enough memory
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (r.file_ofs + count > r.writebuf->get_length()) {
|
|
if (!r.writebuf->append(nullptr, r.file_ofs + count - r.writebuf->get_length())) {
|
|
return -1;
|
|
}
|
|
}
|
|
uint8_t *b = (uint8_t *)r.writebuf->get_writeable_string();
|
|
memcpy(&b[r.file_ofs], buf, count);
|
|
r.file_ofs += count;
|
|
return count;
|
|
}
|
|
|
|
/*
|
|
parse incoming parameters
|
|
*/
|
|
bool AP_Filesystem_Param::param_upload_parse(const rfile &r, bool &need_retry)
|
|
{
|
|
need_retry = false;
|
|
|
|
const uint8_t *b = (const uint8_t *)r.writebuf->get_string();
|
|
uint32_t length = r.writebuf->get_length();
|
|
struct header hdr;
|
|
if (length < sizeof(hdr)) {
|
|
return false;
|
|
}
|
|
memcpy(&hdr, b, sizeof(hdr));
|
|
if (hdr.magic != pmagic) {
|
|
return false;
|
|
}
|
|
if (length != hdr.total_params) {
|
|
return false;
|
|
}
|
|
b += sizeof(hdr);
|
|
|
|
char last_name[17] {};
|
|
|
|
for (uint16_t i=0; i<hdr.num_params; i++) {
|
|
enum ap_var_type ptype = (enum ap_var_type)(b[0]&0x0F);
|
|
uint8_t flags = (enum ap_var_type)(b[0]>>4);
|
|
if (flags != 0) {
|
|
return false;
|
|
}
|
|
uint8_t common_len = b[1]&0xF;
|
|
uint8_t name_len = (b[1]>>4)+1;
|
|
if (common_len + name_len > 16) {
|
|
return false;
|
|
}
|
|
char name[17];
|
|
memcpy(name, last_name, common_len);
|
|
memcpy(&name[common_len], &b[2], name_len);
|
|
name[common_len+name_len] = 0;
|
|
|
|
memcpy(last_name, name, sizeof(name));
|
|
enum ap_var_type ptype2 = AP_PARAM_NONE;
|
|
uint16_t flags2;
|
|
|
|
b += 2 + name_len;
|
|
|
|
AP_Param *p = AP_Param::find(name, &ptype2, &flags2);
|
|
if (p == nullptr) {
|
|
if (ptype == AP_PARAM_INT8) {
|
|
b++;
|
|
} else if (ptype == AP_PARAM_INT16) {
|
|
b += 2;
|
|
} else {
|
|
b += 4;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
if we are enabling a subsystem we need a small delay between
|
|
parameters to allow main thread to perform any allocation of
|
|
backends
|
|
*/
|
|
bool need_delay = ((flags2 & AP_PARAM_FLAG_ENABLE) != 0 &&
|
|
ptype2 == AP_PARAM_INT8 &&
|
|
((AP_Int8 *)p)->get() == 0);
|
|
|
|
if (ptype == ptype2 && ptype == AP_PARAM_INT32) {
|
|
// special handling of int32_t to preserve all bits
|
|
int32_t v;
|
|
memcpy(&v, b, sizeof(v));
|
|
((AP_Int32 *)p)->set(v);
|
|
b += 4;
|
|
} else if (ptype == AP_PARAM_INT8) {
|
|
if (need_delay && b[0] == 0) {
|
|
need_delay = false;
|
|
}
|
|
p->set_float((int8_t)b[0], ptype2);
|
|
b += 1;
|
|
} else if (ptype == AP_PARAM_INT16) {
|
|
int16_t v;
|
|
memcpy(&v, b, sizeof(v));
|
|
p->set_float(float(v), ptype2);
|
|
b += 2;
|
|
} else if (ptype == AP_PARAM_INT32) {
|
|
int32_t v;
|
|
memcpy(&v, b, sizeof(v));
|
|
p->set_float(float(v), ptype2);
|
|
b += 4;
|
|
} else if (ptype == AP_PARAM_FLOAT) {
|
|
float v;
|
|
memcpy(&v, b, sizeof(v));
|
|
p->set_float(v, ptype2);
|
|
b += 4;
|
|
}
|
|
|
|
p->save_sync(false, false);
|
|
|
|
if (need_delay) {
|
|
// let main thread have some time to init backends
|
|
need_retry = true;
|
|
hal.scheduler->delay(100);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
parse incoming parameters
|
|
*/
|
|
bool AP_Filesystem_Param::finish_upload(const rfile &r)
|
|
{
|
|
uint8_t loops = 0;
|
|
while (loops++ < 4) {
|
|
bool need_retry;
|
|
if (!param_upload_parse(r, need_retry)) {
|
|
return false;
|
|
}
|
|
if (!need_retry) {
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#endif // AP_FILESYSTEM_PARAM_ENABLED
|