Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Syntax definitions #8

Open
dmke opened this issue Aug 7, 2019 · 1 comment
Open

Syntax definitions #8

dmke opened this issue Aug 7, 2019 · 1 comment
Labels
feature New feature or request help wanted Extra attention is needed

Comments

@dmke
Copy link
Member

dmke commented Aug 7, 2019

Curently, go-uci is a "best effort" parser and does not strictly follow the rules imposed on UCI files.

This is mainly due to the fact, that the parser function in the original UCI C source does not declare the syntax in an obvious way (i.e. I could not find such a declaration).

In PR #7, I've accepted a change, which expands the accepted characters for identifiers from _a-zA-Z to -_a-zA-Z0-9. A valid UCI config file is /etc/config/wireless, which contains entries like

config wifi-device 'radio0'
	option type 'mac80211'
	...

Contrast this with this statement from the OpenWRT wiki on the file syntax (last paragraph in that section):

It is important to know that UCI identifiers and config file names may contain only the characters a-z, 0-9 and _. E.g. no hyphens (-) are allowed. Option values may contain any character (as long they are properly quoted).

The identifier wifi-device contains a hyphen, but does actually work and the uci binary accepts it without error.

It would be nice for the parser to be closer to uci/libuci and reject invalid identifiers (also the setter/getter methods should only accept valid identifiers).

A first step would be documenting the actually accepted file syntax, based on the C sources.

@dmke dmke added feature New feature or request help wanted Extra attention is needed labels Aug 7, 2019
@dmke
Copy link
Member Author

dmke commented Aug 7, 2019

📑 DRAFT / NOTES

The parsing is handled in file.c by the function uci_parse_line (l. 494). One stack trace leading to this function call looks something like this:

exec "uci export":
└┬ cli.c:767 in function main
 └┬ cli.c:674 in function uci_cmd
  └┬ cli.c:430 in function uci_do_package_cmd
   └┬ cli.c:312 in function package_cmd
    └┬ list.c:420 in function uci_lookup_ptr
     └┬ uci_internal.h:231 in macro UCI_INTERNAL
      └┬ libuci.c:216 in function uci_load
       └┬ file.c:910 in function uci_file_load
        └┬ uci_internal.h:231 in macro UCI_INTERNAL
         └┬ file.c:680 in function uci_import
          └─ file.c:494 found function uci_parse_line

🔭 Observation: For the four keywords (package, config, option, and list) uci_parse_line also accepts just the initials (i.e. p, c, o, and l respectively).

For each keyword exists a corresponding uci_parse_* function:

  • uci_parse_package (file.c:380)

    Can be called in "single" or "import mode"; the latter allows reading multiple packages in the same input stream.

    1. name := next_arg(required: true, name: true, package: true)
    2. assert end-of-line
    3. if in single mode, return; else switch package
  • uci_parse_config (file.c:402)

    Begins reading a new section.

    1. ensure the package context is correct
    2. type := next_arg(required: true, name: false, package: false)
    3. name := next_arg(required: false, name: true, package: false)
    4. check if section with name exists, warn if types mismatch
  • uci_parse_option (file.c:456)

    Parses both options and lists.

    1. fail if not in package/section context
    2. name := next_arg(required: true, name: true, package: false)
    3. value := next_arg(required: false, name: false, package: false)
    4. assert end-of-line
    5. if list, uci_add_list; else uci_set

    It is unclear, if uci_set will discard existing options (see list.c:695).

Some auxillary functions called on the way:

  • next_arg(required, name, package bool) (file.c:236)

    The function extracts a string from the input stream.

    1. skip whitespace (as in isspace(3))
    2. if the next input character is a ; skip it;
      else val := parse_str (a ; skips parsing the string)
    3. fail if val was skipped and required == true
    4. fail if uci_validate_str(val, name, package) returns negative
    5. return val
  • parse_str (file.c:190)

    TODO

    • calls parse_single_quote (file.c:161)
    • calls parse_double_quote (file.c:126)
    • calls parse_backslash (file.c:81)
  • uci_validate_str(str string, name, package bool) (util.c:72)

    Validates str for names and types. Characters of str must match:

    name package character set
    false false [_0-9a-zA-Z]
    true false [ord(33)-ord(126)] ([!-~])
    false true [-_0-9a-zA-Z]
    true true [_0-9a-zA-Z]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature or request help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

1 participant