7a005aa96b
This is a small optimization, it avoids reading the full file when an early key is requested. In the case of an *invalid* TOML file such as ``` K=80 K=81 ``` this stops the value of K actually returned being 8081 and makes it 80 instead; but as it's a malformed file it doesn't really matter much.
432 lines
14 KiB
C
432 lines
14 KiB
C
/*
|
|
* This file is part of the Micro Python project, http://micropython.org/
|
|
*
|
|
* The MIT License (MIT)
|
|
*
|
|
* SPDX-FileCopyrightText: Copyright (c) 2022 Scott Shawcroft for Adafruit Industries
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
|
|
// These functions are separate from __init__.c so that os.getenv() can be
|
|
// tested in the unix "coverage" build, without bringing in "our" os module
|
|
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
|
|
#include "shared-bindings/os/__init__.h"
|
|
#include "shared-module/os/__init__.h"
|
|
|
|
#include "py/gc.h"
|
|
#include "py/misc.h"
|
|
#include "py/mpstate.h"
|
|
#include "py/mpprint.h"
|
|
#include "py/objstr.h"
|
|
#include "py/parsenum.h"
|
|
#include "py/runtime.h"
|
|
#include "supervisor/filesystem.h"
|
|
#include "supervisor/memory.h"
|
|
|
|
#define GETENV_PATH "/settings.toml"
|
|
|
|
#include "extmod/vfs.h"
|
|
#include "extmod/vfs_fat.h"
|
|
typedef FIL file_arg;
|
|
STATIC bool open_file(const char *name, file_arg *active_file) {
|
|
#if defined(UNIX)
|
|
nlr_buf_t nlr;
|
|
if (nlr_push(&nlr) == 0) {
|
|
mp_obj_t file_obj = mp_call_function_2(MP_OBJ_FROM_PTR(&mp_builtin_open_obj), mp_obj_new_str(name, strlen(name)), MP_ROM_QSTR(MP_QSTR_rb));
|
|
mp_arg_validate_type(file_obj, &mp_type_vfs_fat_fileio, MP_QSTR_file);
|
|
pyb_file_obj_t *file = MP_OBJ_TO_PTR(file_obj);
|
|
*active_file = file->fp;
|
|
nlr_pop();
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
#else
|
|
FATFS *fs = filesystem_circuitpy();
|
|
FRESULT result = f_open(fs, active_file, name, FA_READ);
|
|
return result == FR_OK;
|
|
#endif
|
|
}
|
|
STATIC void close_file(file_arg *active_file) {
|
|
// nothing
|
|
}
|
|
STATIC bool is_eof(file_arg *active_file) {
|
|
return f_eof(active_file);
|
|
}
|
|
|
|
// Return 0 if there is no next character (EOF).
|
|
STATIC uint8_t get_next_byte(FIL *active_file) {
|
|
uint8_t character = 0;
|
|
UINT quantity_read;
|
|
// If there's an error or quantity_read is 0, character will remain 0.
|
|
f_read(active_file, &character, 1, &quantity_read);
|
|
return character;
|
|
}
|
|
STATIC void seek_eof(file_arg *active_file) {
|
|
f_lseek(active_file, f_size(active_file));
|
|
}
|
|
|
|
// For a fixed buffer, record the required size rather than throwing
|
|
STATIC void vstr_add_byte_nonstd(vstr_t *vstr, byte b) {
|
|
if (!vstr->fixed_buf || vstr->alloc > vstr->len) {
|
|
vstr_add_byte(vstr, b);
|
|
} else {
|
|
vstr->len++;
|
|
}
|
|
}
|
|
|
|
// For a fixed buffer, record the required size rather than throwing
|
|
STATIC void vstr_add_char_nonstd(vstr_t *vstr, unichar c) {
|
|
size_t ulen =
|
|
(c < 0x80) ? 1 :
|
|
(c < 0x800) ? 2 :
|
|
(c < 0x10000) ? 3 : 4;
|
|
if (!vstr->fixed_buf || vstr->alloc > vstr->len + ulen) {
|
|
vstr_add_char(vstr, c);
|
|
} else {
|
|
vstr->len += ulen;
|
|
}
|
|
}
|
|
|
|
STATIC void next_line(file_arg *active_file) {
|
|
uint8_t character;
|
|
do {
|
|
character = get_next_byte(active_file);
|
|
} while (character != 0 && character != '\n');
|
|
}
|
|
|
|
// Discard whitespace, except for newlines, returning the next character after the whitespace.
|
|
// Return 0 if there is no next character (EOF).
|
|
STATIC uint8_t consume_whitespace(file_arg *active_file) {
|
|
uint8_t character;
|
|
do {
|
|
character = get_next_byte(active_file);
|
|
} while (character != '\n' && character != 0 && unichar_isspace(character));
|
|
return character;
|
|
}
|
|
|
|
// Starting at the start of a new line, determines if the key matches the given
|
|
// key.
|
|
//
|
|
// If result is true, the key matches and file pointer is pointing just after the "=".
|
|
// If the result is false, the key does NOT match and the file pointer is
|
|
// pointing at the start of the next line, if any
|
|
STATIC bool key_matches(file_arg *active_file, const char *key) {
|
|
uint8_t character;
|
|
character = consume_whitespace(active_file);
|
|
if (character == '[' || character == 0) {
|
|
seek_eof(active_file);
|
|
return false;
|
|
}
|
|
while (*key) {
|
|
if (character != *key++) {
|
|
// A character didn't match the key, so it's not a match
|
|
// If the non-matching char was not the end of the line,
|
|
// then consume the rest of the line
|
|
if (character != '\n') {
|
|
next_line(active_file);
|
|
}
|
|
return false;
|
|
}
|
|
character = get_next_byte(active_file);
|
|
}
|
|
// the next character could be whitespace; consume if necessary
|
|
if (unichar_isspace(character)) {
|
|
character = consume_whitespace(active_file);
|
|
}
|
|
// If we're not looking at the "=" then the key didn't match
|
|
if (character != '=') {
|
|
// A character didn't match the key, so it's not a match
|
|
// If the non-matching char was not the end of the line,
|
|
// then consume the rest of the line
|
|
if (character != '\n') {
|
|
next_line(active_file);
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
STATIC os_getenv_err_t read_unicode_escape(file_arg *active_file, int sz, vstr_t *buf) {
|
|
char hex_buf[sz + 1];
|
|
for (int i = 0; i < sz; i++) {
|
|
hex_buf[i] = get_next_byte(active_file);
|
|
}
|
|
hex_buf[sz] = 0;
|
|
char *end;
|
|
unsigned long c = strtoul(hex_buf, &end, 16);
|
|
if (end != &hex_buf[sz]) {
|
|
return GETENV_ERR_UNEXPECTED | *end;
|
|
}
|
|
if (c >= 0x110000) {
|
|
return GETENV_ERR_UNICODE;
|
|
}
|
|
vstr_add_char_nonstd(buf, c);
|
|
return GETENV_OK;
|
|
}
|
|
|
|
// Read a quoted string
|
|
STATIC os_getenv_err_t read_string_value(file_arg *active_file, vstr_t *buf) {
|
|
while (true) {
|
|
int character = get_next_byte(active_file);
|
|
switch (character) {
|
|
case 0:
|
|
case '\n':
|
|
return GETENV_ERR_UNEXPECTED | character;
|
|
|
|
case '"':
|
|
character = consume_whitespace(active_file);
|
|
switch (character) {
|
|
case '#':
|
|
next_line(active_file);
|
|
MP_FALLTHROUGH;
|
|
case 0:
|
|
case '\n':
|
|
return GETENV_OK;
|
|
default:
|
|
return GETENV_ERR_UNEXPECTED | character;
|
|
}
|
|
|
|
case '\\':
|
|
character = get_next_byte(active_file);
|
|
switch (character) {
|
|
case 0:
|
|
case '\n':
|
|
return GETENV_ERR_UNEXPECTED | character;
|
|
case 'b':
|
|
character = '\b';
|
|
break;
|
|
case 'r':
|
|
character = '\r';
|
|
break;
|
|
case 'n':
|
|
character = '\n';
|
|
break;
|
|
case 't':
|
|
character = '\t';
|
|
break;
|
|
case 'v':
|
|
character = '\v';
|
|
break;
|
|
case 'f':
|
|
character = '\f';
|
|
break;
|
|
case 'U':
|
|
case 'u': {
|
|
int sz = (character == 'u') ? 4 : 8;
|
|
os_getenv_err_t res;
|
|
res = read_unicode_escape(active_file, sz, buf);
|
|
if (res != GETENV_OK) {
|
|
return res;
|
|
}
|
|
continue;
|
|
}
|
|
// default falls through, other escaped characters
|
|
// represent themselves
|
|
}
|
|
MP_FALLTHROUGH;
|
|
default:
|
|
vstr_add_byte_nonstd(buf, character);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Read a numeric value (non-quoted value) as a string
|
|
STATIC os_getenv_err_t read_bare_value(file_arg *active_file, vstr_t *buf, int first_character) {
|
|
int character = first_character;
|
|
while (true) {
|
|
switch (character) {
|
|
case 0:
|
|
case '\n':
|
|
return GETENV_OK;
|
|
case '#':
|
|
next_line(active_file);
|
|
return GETENV_OK;
|
|
default:
|
|
vstr_add_byte_nonstd(buf, character);
|
|
}
|
|
character = get_next_byte(active_file);
|
|
}
|
|
}
|
|
|
|
STATIC mp_int_t read_value(file_arg *active_file, vstr_t *buf, bool *quoted) {
|
|
uint8_t character;
|
|
character = consume_whitespace(active_file);
|
|
*quoted = (character == '"');
|
|
|
|
if (*quoted) {
|
|
return read_string_value(active_file, buf);
|
|
} else {
|
|
return read_bare_value(active_file, buf, character);
|
|
}
|
|
}
|
|
|
|
STATIC os_getenv_err_t os_getenv_vstr(const char *path, const char *key, vstr_t *buf, bool *quoted) {
|
|
file_arg active_file;
|
|
if (!open_file(path, &active_file)) {
|
|
return GETENV_ERR_OPEN;
|
|
}
|
|
|
|
os_getenv_err_t result = GETENV_ERR_NOT_FOUND;
|
|
while (!is_eof(&active_file)) {
|
|
if (key_matches(&active_file, key)) {
|
|
result = read_value(&active_file, buf, quoted);
|
|
break;
|
|
}
|
|
}
|
|
close_file(&active_file);
|
|
return result;
|
|
}
|
|
|
|
STATIC os_getenv_err_t os_getenv_buf_terminated(const char *key, char *value, size_t value_len, bool *quoted) {
|
|
vstr_t buf;
|
|
vstr_init_fixed_buf(&buf, value_len, value);
|
|
os_getenv_err_t result = os_getenv_vstr(GETENV_PATH, key, &buf, quoted);
|
|
|
|
if (result == GETENV_OK) {
|
|
vstr_add_byte_nonstd(&buf, 0);
|
|
memcpy(value, buf.buf, MIN(buf.len, value_len));
|
|
if (buf.len > value_len) { // this length includes trailing NUL
|
|
result = GETENV_ERR_LENGTH;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
STATIC void print_dont_raise(const mp_obj_type_t *exc_type, const compressed_string_t *fmt, ...) {
|
|
va_list argptr;
|
|
va_start(argptr,fmt);
|
|
mp_vcprintf(&mp_plat_print, fmt, argptr);
|
|
mp_printf(&mp_plat_print, "\n");
|
|
va_end(argptr);
|
|
}
|
|
|
|
STATIC void handle_getenv_error(os_getenv_err_t error, void (*handle)(const mp_obj_type_t *exc_type, const compressed_string_t *fmt, ...)) {
|
|
if (error == GETENV_OK) {
|
|
return;
|
|
}
|
|
if (error & GETENV_ERR_UNEXPECTED) {
|
|
byte character = (error & 0xff);
|
|
char buf[8];
|
|
vstr_t vstr;
|
|
vstr_init_fixed_buf(&vstr, sizeof(buf), buf);
|
|
mp_print_t print = { .data = &vstr, .print_strn = (mp_print_strn_t)vstr_add_strn };
|
|
|
|
if (character) {
|
|
mp_str_print_quoted(&print, &character, 1, true);
|
|
} else {
|
|
mp_str_print_quoted(&print, (byte *)"EOF", 3, true);
|
|
}
|
|
handle(&mp_type_ValueError, translate("Invalid byte %.*s"), vstr.len, vstr.buf);
|
|
} else {
|
|
switch (error) {
|
|
case GETENV_ERR_OPEN:
|
|
handle(&mp_type_ValueError, translate("%S"), translate("File not found"));
|
|
break;
|
|
case GETENV_ERR_UNICODE:
|
|
handle(&mp_type_ValueError, translate("%S"), translate("Invalid unicode escape"));
|
|
break;
|
|
case GETENV_ERR_NOT_FOUND:
|
|
handle(&mp_type_ValueError, translate("%S"), translate("Key not found"));
|
|
break;
|
|
default:
|
|
handle(&mp_type_RuntimeError, translate("%S"), translate("Internal error"));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
STATIC void common_hal_os_getenv_showerr(const char *key, os_getenv_err_t result) {
|
|
if (result != GETENV_OK && result != GETENV_ERR_OPEN && result != GETENV_ERR_NOT_FOUND) {
|
|
mp_cprintf(&mp_plat_print, translate("An error occurred while retrieving '%s':\n"), key);
|
|
handle_getenv_error(result, print_dont_raise);
|
|
}
|
|
}
|
|
|
|
STATIC
|
|
os_getenv_err_t common_hal_os_getenv_str_inner(const char *key, char *value, size_t value_len) {
|
|
bool quoted;
|
|
os_getenv_err_t result = os_getenv_buf_terminated(key, value, value_len, "ed);
|
|
if (result == GETENV_OK && !quoted) {
|
|
result = GETENV_ERR_UNEXPECTED | value[0];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
os_getenv_err_t common_hal_os_getenv_str(const char *key, char *value, size_t value_len) {
|
|
os_getenv_err_t result = common_hal_os_getenv_str_inner(key, value, value_len);
|
|
common_hal_os_getenv_showerr(key, result);
|
|
return result;
|
|
}
|
|
|
|
mp_obj_t common_hal_os_getenv_path(const char *path, const char *key, mp_obj_t default_) {
|
|
vstr_t buf;
|
|
bool quoted;
|
|
|
|
vstr_init(&buf, 64);
|
|
os_getenv_err_t result = os_getenv_vstr(path, key, &buf, "ed);
|
|
if (result == GETENV_ERR_NOT_FOUND || result == GETENV_ERR_OPEN) {
|
|
return default_;
|
|
}
|
|
handle_getenv_error(result, mp_raise_msg_varg);
|
|
|
|
if (quoted) {
|
|
return mp_obj_new_str_from_vstr(&mp_type_str, &buf);
|
|
} else {
|
|
return mp_parse_num_integer(buf.buf, buf.len, 0, NULL);
|
|
}
|
|
}
|
|
|
|
mp_obj_t common_hal_os_getenv(const char *key, mp_obj_t default_) {
|
|
return common_hal_os_getenv_path(GETENV_PATH, key, default_);
|
|
}
|
|
|
|
STATIC os_getenv_err_t common_hal_os_getenv_int_inner(const char *key, mp_int_t *value) {
|
|
char buf[16];
|
|
bool quoted;
|
|
os_getenv_err_t result = os_getenv_buf_terminated(key, buf, sizeof(buf), "ed);
|
|
if (result != GETENV_OK) {
|
|
return result;
|
|
}
|
|
if (quoted) {
|
|
return GETENV_ERR_UNEXPECTED | '"';
|
|
}
|
|
char *end;
|
|
long num = strtol(buf, &end, 0);
|
|
while (unichar_isspace(*end)) {
|
|
end++;
|
|
}
|
|
if (end == buf || *end) { // If the whole buffer was not consumed it's an error
|
|
return GETENV_ERR_UNEXPECTED | *end;
|
|
}
|
|
*value = (mp_int_t)num;
|
|
return GETENV_OK;
|
|
}
|
|
|
|
os_getenv_err_t common_hal_os_getenv_int(const char *key, mp_int_t *value) {
|
|
os_getenv_err_t result = common_hal_os_getenv_int_inner(key, value);
|
|
common_hal_os_getenv_showerr(key, result);
|
|
return result;
|
|
}
|