You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
648 lines
18 KiB
648 lines
18 KiB
/* |
|
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.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 fixed size to avoid needing to scan entire file |
|
stbuf->st_size = 1024*1024; |
|
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; |
|
}
|
|
|