Best way to get UCI unnamed section CFGID value

I frequently need to update an existing section in the OpenWRT configuration or create a new one. For example I need to update a host IP address in /etc/config/dhcp. My approach is to find the CFGID of a
section, delete it with:

 uci delete dhcp.<cfgid>

and then create new section with the same name and different IP.

I have my approach to search for unnamed section CFGID with the specific name but it is a complex sh/grep/sed script. It dumps the whole configuration:

uci show dhcp

greps for:

dhcp.@domain[3].name='foobar'

and gets the CFGID with:

uci show entry dhcp.@domain[3]

I suspect that I somehow missed something like that:

uci find <config>.<section>.<option>=<string>

that would return a CFGID. Any other ways to do it?

uci naming scheme for unnamed sections is a combination of a counter and a hash, see: https://github.com/openwrt/uci/blob/master/list.c#L153-L176

You can implement the algorithm on your own, but you will still need to parse the file to obtain both values.

If you know the order of your sections, you can refer to a specific one by index @section-type[index]. Or, you can set an explicit name to your section config section-type 'my-section' and refer to it by 'my-section'.

1 Like

After re-reading what Cthulhu88 is saying with the explicit name of the section this is basically what I'm writing in long form below.....

I did some reading of https://openwrt.org/docs/guide-user/base-system/uci
It wasn't initially aparrent to me that you could do it with the anonymously named sections under dhcp. But I tried it and it worked.

You can still set the section name in the host category for dhcp to get from an anonymous name. i.e. get a named section.

Still can't get around some parsing logic and looping if you need to check the existing configuration.
But if you have a script to update one named section dynamically when required should be straightforward as you have a set human friendly name.

For example:

root@OpenWrt:~# uci show dhcp | grep amt0
dhcp.amt0=host
dhcp.amt0.duid='<random>'
dhcp.amt0.name='myamt'
root@OpenWrt:~# 

This would be from:

config host 'amt0'
	option duid '<random>'
	option name 'myamt'

So you than rename. For example:

uci rename dhcp.amt0=amt1

Or from an anonymous name to begin with:

uci rename dhcp.@host[11]=mydebianserver

Just making sure you know of the following options when creating arbitrary new cfg?

Creating a new network interface under known name immediately:

uci rename network.$(uci add network interface)=legacy

Storing the arbitrary created cfg name on creation in a variable:

rule_name=$(uci add firewall redirect)
uci set firewall.$rule_name.<option>

@Cthulhu88 - Your suggestion #1 of implementing hash algorithms goes the opposite way of my needs - I am pointing out the lack of support for a basic use case which is "find section by name". Suggestion #2 of knowing the indexes of my sections is not realistic - I would have to write them down somewhere. Suggestion #3 of naming my sections is just like #2 of knowing the indexes somehow. Also GUI interface does not support it.
I did a lot of searching of that basic UCI use case and I am surprised to find out that there is not even one discussion on google about it. LUCI somehow does it and you can see with 'oci changes' that it locates CFGID and uses it to delete a section.
The process that I had to cook up of finding that CFGI for given UCI configuration, section and name is here:

#!/bin/ash
# Find named config section cfgid.
# cfgid can be used to delete the config.section with: uci delete $config.$cfgid

config=$1 section=$2 name=$3

entry=$(uci show $config | grep $config.@$section | \
        grep .name=\'$name\' | sed 's/\.name.*$//')

set $entry
uci show $1 | grep .name | sed 's/^'$config'\.//' | sed 's/\.name.*$//'

@evs That naming of sections that are already named does not work for me. I think we need a feature request to implement: uci find

No worries.

Yeah if you must find an existing item then I guess loops and/or string parsing with sed is the way to go.

Sounds like it's something to go in the feature request then?
Although I'm happy just doing my own search algorithm with a loop.
If we're talking huge amounts of items then linear search might not scale. Then i'd have to look at UCI's code there.

For example on my primary router.

 uci show dhcp.@host[11] 
dhcp.mydebianserver=host
dhcp.mydebianserver.name='debian'
dhcp.mydebianserver.ip='<ip>'
dhcp.mydebianserver.mac='<mac>'

Gives same output as

uci show dhcp.mydebianserver
dhcp.mydebianserver=host
dhcp.mydebianserver.name='debian'
dhcp.mydebianserver.ip='<ip>'
dhcp.mydebianserver.mac='<mac>'

So loop positively. Or find a way to identify [-1] and then count up?. but A while uci loop would work too as uci will error out when you get to the end.

The only way to figure out the id is by counter + hash, directly or through another script/program, such as uci.

If you import /lib/functions.sh, you've access to some utility shell functions.

#!/bin/sh

. /lib/functions.sh

do_something() {
    local value

    config_get value "$1" name

    echo "dhcp.$1.name=$value"
}

config_load dhcp

config_foreach do_something domain

It's not "find section by name", but rather "find section by type" that you're looking for.
Since there can be multiple sections with the same type, if they are not explicitly named, you need to iterate over all sections of that type to figure out which one you want.

1 Like

Yes!
A long time ago I figured out that uci changes reports the cfgid. You can probe uci very simply.

Here is an example of the basic uci commands that can be built into a script:

root@meshnode-c525:/# uci set dhcp.@dnsmasq[0].test0='testvalue'
root@meshnode-c525:/# uci set dhcp.@dnsmasq[1].test1='testvalue'
uci: Invalid argument
root@meshnode-c525:/# uci changes dhcp
dhcp.cfg01411c.test0='testvalue'
root@meshnode-c525:/#  uci revert dhcp
root@meshnode-c525:/# 

From this you can see that in this example, dnsmasq[0] is cfg01411c

This is easy for dnsmasq[0] because you know there is only one section with index 0. What about the sections that you have a lot of like DHCP static reservations with dhcp.@domain[78] ? To get CFGID for that you somehow need to know that the index is 78.

Did you actually try to run the example script above? That's how most official shell scripts dealing with unnamed sections do it. In fact, I actually use this same method for one of my application's service, where it accepts both unnamed and named sections.

root@router1:~# cat > example.sh
#!/bin/sh

. /lib/functions.sh

do_something() {
    local value

    config_get value "$1" name

    echo "dhcp.$1.name=$value"
}

config_load dhcp

config_foreach do_something domain
^C
root@router1:~# chmod +x example.sh
root@router1:~# ./example.sh
dhcp.cfg06f37d.name=router2
dhcp.cfg07f37d.name=router3
root@router1:~#

config_load invokes uci export once, dumping the returned data into shell variables. Then config_* functions that perform reading merely read from those variables.

If you don't want to do any sort of iteration while also refusing to explicitly name your sections, I don't think you understand how the uci format works.
option name DOES NOT name the section, name is simply a generic attribute, just like option ip in the case of dhcp.domain.

Had uci added an option for iterating through sections and return a section based on the value of an attribute, it would be ambiguous on which one to return if more than one section had the same value for the same attribute, in the same way if you wanted a key-value pair from an associative array (map, dictionary, etc) without knowing the key and the same value was shared between different entries.
Besides, as shown in the example above, there are utility functions for you to do it yourself with your own custom/arbitrary rules through config_foreach.

Either way, your approach feels like bad design to me (likely due to a lack of understanding of the format). If you always want to find a section by a name, why not just name it explicitly to begin with as pointed in my first post here?

# insertion (use batch, if this needs to be atomic)
uci add dhcp domain
uci rename dhcp.@domain[-1]='foobar'

# modification
uci set dhcp.foobar.name='new name'
uci set dhcp.foobar.ip='new ip' 

# deletion
uci delete dhcp.foobar
1 Like

You kind of missed the point. I only gave a command line to probe uci, not a working script.

In practice you would use a while loop with an incrementing index that stops looping when uci errors.
You will then have a full list of [index, cfgid, option, value] to use as you wish. A very simple script to implement.

@Cthulhu88 this is interesting. It still hinges on naming the sections and later using that section name to operate on the section. Getting a raw CFGID for a section is the solution. It should be a part of the C code in the UCI. I have no idea how they missed the need for it. My 2 line sed/grep solution that produces CFGID for arbitrary tuple of config/section/name is the solution.