Preferred method for retrieving hostname in shell script?

Discussion in 'Tomato Firmware' started by HunterZ, Apr 11, 2014.

  1. HunterZ

    HunterZ Network Guru Member

    So I noticed while hacking on some shell scripts under Toastman Tomato to behave when called from different routers that there is no environment variable (e.g. $HOSTNAME) containing the host name.

    I also noticed that there is no 'hostname' shell command or busybox command (although Google searches show that some other versions of busybox do have such a thing).

    The interesting thing is that the shell prompt, system logger, etc., all seem to be aware of the hostname.

    I was eventually able to find two methods to retrieve the hostname that do work:
    1. uname -n
    2. nvram get lan_hostname

    Is one method considered better than the other? Is there a method I don't know about that's even better than either of those?
  2. koitsu

    koitsu Network Guru Member

    How does this affect you? Please be verbose.

    If this is for some particular piece of software that needs to know what FQDN to use in a string fashion for certain protocols (e.g. SMTP protocol's HELO or possibly MAIL FROM), then I would suggest using the wan_hostname and wan_domain NVRAM variables.

    You should not be treating Tomato routers like Linux servers or workstations in this regard. They aren't really intended for this type of thing, nor do they have the init scripts + related bits to "make everything as proper as it should". OpenWRT does a better job of this by far.

    However, for whatever reason the Linux world (embedded, consumer, and server) have never, ever been able to consistently and coherently comprehend the concept of a hostname vs. a domain name and so on. It's remarkable how utterly broken distros are with what "hostname" vs. "hostname -f" return, vs. what's in /etc/hosts, vs. "domainname", and a whole slew of other nonsense. Every distro does these differently and they all seem to do it wrong in one way or another. It's been like this since I exclusively ran Linux back in the early 90s -- no joke.

    You learn what the Proper Way(tm) is the instant you use BSD. The system should have an FQDN, which should always be or, but never bar.blat. In, bar.blat is the domain, foo is the hostname. hostname on a BSD box is identical to hostname -f which always shows the FQDN. hostname -s shows the host name (foo). What ends in /etc/hosts is very easy to understand (and the order of these arguments matters greatly): ip.address FQDN hostname, ex. foo

    Like I said, blows my mind.
    Last edited: Apr 13, 2014
  3. HunterZ

    HunterZ Network Guru Member

    Thanks for the reply and info.

    As I mentioned before, there is no hostname command on Toastman's Tomato. As far as I can tell, it's not even a command offered by the version of busybox that it ships with :(

    It does look like Entware has an inetutils package that I suppose I could install, but that won't help if I need the hostname before I can mount an Entware install on a cifs share.


    1. I recently replaced my RT-N16 with an RT-N66U because the former stopped working and had to be RMA'd. I just got the replacement RT-N16 back from Asus and decided to put Tomato on it as well and use it as an additional wireless AP (and 4-port switch) on the other end of the house from the RT-N66U (connected via a gigabit Cat5e run that I installed under the house a few months ago).

    2. I've got a collection of scripts on a cifs share (on a Linux workstation on my LAN) that do various things with my gateway that are not provided by the Tomato GUI, including:
    • Running/maintaining Entware's ntpd.
    • Ad-blocking via dnsmasq, iptables and pixelserv.
    • Restoring system time from a once-per-minute checkpoint in the unlikely event that there are no WAN or LAN ntp hosts available (which is really pretty silly, actually, since the cifs server on which the checkpoint is stored also runs ntpd and itself has an RTC, but this gives me a fallback in case I have to disable ntpd on the two other RTC-equipped LAN boxes that usually also run it 24/7).
    • Saving a copy of /var/log/messages to the cifs share on shutdown (I should probably do this on a once-per-minute schedule too, but not have it log directly to the cifs share or else I might miss everything that happens before the mount becomes active).

    I decided that since the RT-N16 was running Tomato, I may as well put it to use as another ntpd node and such. This requires mounting the cifs share and setting up the Entware environment just like I do on the RT-N66U. However, I also needed to give it a different ntpd config (mainly because I just want the RT-N16 to sync with the RT-N66U and the 2 LAN machines) and have it store other stuff in a different location than the RT-N66U. I decided that the easiest way to do this would be to create subdirectories on the cifs share, whose names are the LAN hostnames of the routers; the scripts would then be modified to use the hostname of whatever device they're running on to access the appropriate directory.

    As I mentioned in my OP, this all broke down (temporarily) when none of the obvious ways to find the LAN hostname of the router(s) were available. Fortunately, I found the 2 previously mentioned solutions, but I was wondering if this is something people have run into before.
  4. koitsu

    koitsu Network Guru Member

    Wow, when I asked for verbose, you definitely met my needs. Thank you! This is the exact kind of detail I wanted to know. It sheds perfect light on why you need the hostname equivalent (the short of which is: in your scripts which are used on both routers, you need a way to distinguish the difference between both).

    I would suggest using the lan_hostname NVRAM variable, but uname -n would work just fine as well. You might asking: "which should I use, nvram get lan_hostname or uname -n?"

    The answer would be uname -n. The reasoning here is simple: upon boot-up (probably when services starts up), there is a piece of C code that calls sethostname({contents of lan_hostname}) (sethostname(2) is a syscall which Busybox does implement) that the kernel retains in memory (RAM) (and if you change it via the Tomato GUI, it does get changed). There's no sense in banging on NVRAM (even for reads) when the same information is already available in kernel memory space. (I really try to push people to not access NVRAM excessively).

    You could alternately key off of the NVRAM variable t_model_name but that wouldn't work if you had two routers of the same model.

    So in summary: at the top of your scripts, just do something like export HOSTNAME="$(uname -n)" and you should be good to go.

    If you want $HOSTNAME to be provided by the default shell (Busybox sh, which is its own weird thing kinda like Debian ash), then I'd suggest discussing it with the Busybox authors + provide a patch. It's not very many lines of C code, or they could just write it themselves; it's pretty simple, you just rely on the uname(2) syscall (the field is called nodename).

    Here's an alternate idea that might make you happier though: if your CIFS mounts are already in place by the time these scripts run and you can use Entware tools (i.e. /opt is up and available by the time your scripts run), then you can install the bash package for Entware and use a hashbang line in your scripts of #!/opt/bin/bash and use $HOSTNAME like usual (bash does set it correctly; run bash and use set to see for yourself). Otherwise the above export line I provided should be perfectly sufficient.

    P.S. -- Sorry for making this post longer than it needs to be, but I wanted to offer you multiple solutions and cover lots of bases + provide lots of insight to how everything works.
    Last edited: Apr 13, 2014
    HunterZ and eviltone like this.
  5. HunterZ

    HunterZ Network Guru Member

    Yeah, on further reflection the same argument that it would be better to pull from RAM than NVRAM occurred to me as well. Thanks for the confirmation.

    I had already added something similar that was using the lan_hostname method as a test before I found the uname -n method, so I'll be changing it momentarily.

    I've opened an enhancement ticket on their issue tracker, but this is not likely to bear fruit for me for multiple reasons:
    • They have no incentive to add this, even if I were to provide a patch.
    • (Toastman's) Tomato appears to be using an older version of busybox, so any enhancements would potentially never (or at least not quickly) roll into the firmware.

    This is an interesting idea, but would be a bit of a pain since I currently reference the hostname in parts of my cifs on-mount script. I could probably break the hostname-dependent piece(s) out and call them from the on-mount script, but that's a silly amount of effort to avoid a one-liner that calls a utility that runs from RAM/EEPROM/whatever anyways :)

    That also got me wondering if there might be some way to configure busybox's ash shell after startup, but it looks like a no-go. According to some random ash documentation I read, it's possible to cause a script to be executed whenever ash is invoked (regardless of whether it is in interactive mode) by setting the ENV environment variable. This only seems to work for sub-shells of the one in which ENV was set, however, so it appears that there's no way to set it globally within the limitations of Tomato.

    The verbosity is much appreciated - in my experience, your posts are usually a goldmine of useful information and ideas.
  6. HunterZ

    HunterZ Network Guru Member

    Holy smokes, they actually implemented my BusyBox suggestion:

    I think that may be the first time in almost 20 years of working with open source in which a bug/feature ticket that I opened was actually implemented. Usually they just go ignored, or sometimes get argued with or dismissed out of hand.

    Edit: Looks like it will only take effect if ash is built in bash compat mode. No idea how it is generally built for Tomato.

    Sent from my Nexus 7 using Tapatalk
  7. koitsu

    koitsu Network Guru Member

    I'm glad they took your request seriously and implemented it. It's easy to implement and convenient.

    That said, the code is shameful; no error checking on the uname(2) call, for example, which can in fact fail (man page documents such). Sigh, Busybox... Oh well, here's to hoping it never fails, otherwise the HOSTNAME environment variable will end up being set to a bunch of random data (the uts struct is not zeroed or sanitised before uname() call, although that also won't necessarily fix anything, depends on what setvar2() and set_local_var_from_halves() do). They should really change it to be:

    if (uname(&uts) == 0) { the relevant set call here...
    They might also want to be aware of implementation differences between GNU libc and other libcs (TomatoUSB uses uClibc):

    So I'm crossing my fingers that the uname() call will actually suffice. Edit: Ignore my previous comment about DNS etc. -- I've been doing DNS things all day, so I have DNS-on-the-brain. I was thinking of gethostbyname() (not the same thing as gethostname()).

    The change was committed to master/head, which means it will (hopefully) be part of 1.22 when that gets released (no idea when; they'd know). After that release, it becomes a matter of when Toastman and/or others have the time to merge/pull Busybox 1.22 into Tomato, and then release new firmwares. All the TomatoUSB authors are very good about announcing changes, especially when Busybox gets updated.

    As for your question about ENABLE_ASH_BASH_COMPAT and ENABLE_HUSH_BASH_COMPAT: all the Busybox settings (for compile-time) are stored in release/src/router/busybox/config_base (linking to the Toastman RT-N branch just because that's what I'm used to). Busybox "abstracts out" a lot of the config variables, and the one you're looking for is CONFIG_ASH_BASH_COMPAT=y; see line 968. The definition of the variable being y means that yes, TomatoUSB offers/uses that feature, so you should see it kick in once the tasks in the above paragraph take place in the future.

    Just remember that if you ever release your scripts publicly and rely on $HOSTNAME that you need to be very adamant about stating they require real bash or Busybox 1.22/newer. :)
    Last edited: Apr 16, 2014
  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice