Code Monkey home page Code Monkey logo

go-uci's Introduction

go-uci

WORK IN PROGRESS

You're looking at the pre-release documentation for the next major v2 version.

Some things aren't properly flushed out yet and may break at any moment. Use at your own peril.

UCI is OpenWrt's Unified Configuration Interface. It is used to configure OpenWrt router hardware using a simple DSL (and acompanying CLI tools). Configuration files are written into a central directory (/etc/config/*) which basically represents a key/value store.

This project makes it easy to interact with such a config tree by providing a native interface to that KV store. It has no external runtime dependencies.

For now, we only implements a superset of the actual UCI DSL, but improvements (patches or PRs) are very welcome. Refer to Rob Pike's Lexical Scanning in Go for implementation details on the parser/lexer.

Why?

We're currently experimenting with Go binaries on OpenWrt router hardware and need a way to interact with the system configuration. We could have created bindings for libuci, but the turnaround cycle in developing with CGO is a bit tedious. Also, since Go does not compile for our target platforms, we need to resort to GCCGO, which has other quirks.

The easiest solution therefore is a plain Go library, which can be used in Go (with or without CGO) and GCCGO without worrying about interoperability. A library also allows UCI to be used outside of OpenWrt systems (e.g. for provisioning).

Usage

TODO: update example

import "github.com/digineo/go-uci"

func main() {
    // use the default tree (/etc/config)
    if values, ok := uci.Get("system", "@system[0]", "hostname"); ok {
        fmt.Println("hostanme", values)
        //=> hostname [OpenWrt]
    }

    // use a custom tree
    u := uci.NewTree("/path/to/config")
    if values, ok := u.Get("network", "lan", "ifname"); ok {
        fmt.Println("network.lan.ifname", values)
        //=> network.lan.ifname [eth0.2]
    }
    if sectionExists := u.Set("network", "lan", "ipaddr", "192.168.7.1"); !sectionExists {
        _ = u.AddSection("network", "lan", "interface")
        _ = u.Set("network", "lan", "ipaddr", "192.168.7.1")
    }
    u.Commit() // or uci.Revert()
}

See [API documentation][godoc] for more details.

Contributing

Pull requests are welcome, especially if they increase test coverage.

Before submitting changes, please make sure the tests still pass:

$ go test github.com/digineo/go-uci/...

License

MIT License. Copyright (c) 2019 Dominik Menke, Digineo GmbH

https://www.digineo.de

See LICENSE file for details.

go-uci's People

Contributors

0x5a17ed avatar corny avatar dmke avatar longhairedhacker avatar nicolas-martin avatar perbu avatar stgnet avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

go-uci's Issues

Syntax definitions

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.

Empty list?

Parser seems to be unable to handle empty lists
E.g., list example ''
Try it out

uci doesn't like dashes in section names

Hi.

I noticed that the parser is a bit more relaxed than the uci parser in OpenWRT. Would you be interested in a patch that would fail is we see something other than a-z, A-Z, 0-9 or "_"?

@relet made me aware of this.

saveConfig fails if config file location and /tmp/ are on different filesystems

This is another problem I've noticed, while debugging #4.

On many OpenWRT device /etc/config is a read-only filesystem overlaid with a writable filesystem, while/tmp/ is usually an actual tmpfs/ramdisk.

Consider saveConfig() being called for network.
The call to ioutil.TempFile in saveConfig creates a temporary file in\tmp\.
A subsequent call to os.Rename() tries to hardlink the file to /etc/network.
While this a considered best practice to ensure atomic file changes it will fail for this case as the hardlink crosses filesystem boundaries.
A typical error looks like this:
rename /tmp/network012772557 /etc/config/network: invalid cross link

As the user can't choose a different location for the temporary files, this bug renders this library useless on many OpenWRT installations.

Commit() ignores errors

Seems like I found another bug.
I've just noticed that in case of an error writing to the config file (e.g. permission problem, out of disk space ...) Commit() still returns nil.

go-uci/uci.go

Lines 110 to 121 in a970c4b

func (t *tree) Commit() error {
t.Lock()
defer t.Unlock()
for _, config := range t.configs {
if !config.tainted {
continue
}
t.saveConfig(config)
}
return nil
}

It ignores any errors returned by t.saveConfig(config).

I'll have a pull request ready in a few moments.

Del is unable to delete the last Option in a Section

I just found an off-by-one-error in Del.

Consider the following Config-snippet from /etc/config/system:

config timeserver 'ntp'
	option enabled '1'
	option enable_server '0'
	list server '0.lede.pool.ntp.org'
	list server '1.lede.pool.ntp.org'
	list server '2.lede.pool.ntp.org'
	list server '3.lede.pool.ntp.org'

When I execute

	r := NewTree("/etc/config")
	r.Del("system", "ntp", "server")
        r.Commit()

I expect the all the list server options to be be gone.
However the file /etc/config/system remains unchanged.

After some poking, I found an of -by-one-error in types.go:240.
Slicing a list with [:l] where l is the length of the list returns the same list without changes.
[:l-1] is correct.

Moreover I would suggest simplifying the switch statement to s.Options = append(s.Options[:i], s.Options[i+1:]...) as it is cleaner, more idiomatic and covers all the cases.

Arguably it has some performance overhead if the first or last option is deleted (append with empty lists), but I don't think anyone would want to change their configuration frequently enough that it becomes a real problem. (Also I would expect append have its own optimizations for those two corner cases.)

ensureConfigLoaded masks errors

The following code is used quite a bit. The problem is that it will mask all sort of errors behind "section not found". It probably not possible to change this without breaking the API, though. Not sure if you want a suggested fix.

func (t *tree) ensureConfigLoaded(config string) (*config, bool) {
	cfg, loaded := t.configs[config]
	if !loaded {
		if err := t.loadConfig(config); err != nil {
			return nil, false
		}
		cfg = t.configs[config]
	}
	return cfg, true
}

Cheers,

Per.

s/OpenWRT/OpenWrt/g

Just nitpicking but in theory OpenWrt is written with uppercase O and W only :)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.