I am just beginning to toy around with combining ncurses and C to develop a very minimal TUI. The purpose of the TUI is to greet users with a basic login/welcome screen. The goal would be to display basic system information like the operating system, available memory, IP address, etc. Nothing beyond read-only.
What would be the best way to go about doing this? The part I'm struggling with is interfacing the shell commands like df
, ls
, ifconfig
, etc with variables that I can then display or print in ncurses and C. I know something like this can be done, as well as calling the system command with a string, but this seems somewhat bulky:
#include <ncurses.h>
#include <stdlib.h>
#include <stdio.h>
int main(void) {
FILE *pp;
initscr();
cbreak();
if ((pp = popen("df", "r")) != 0) {
char buffer[BUFSIZ];
while (fgets(buffer, sizeof(buffer), pp) != 0) {
addstr(buffer);
}
pclose(pp);
}
getch();
return EXIT_SUCCESS;
}
Are there any methods to execute a command in the command line from within a C program and then selectively access the output of that command for later display? Or is this information generally stored somewhere in a parseable file on the machine? I'm new to trying to pull system information/use the command line in a "TUI" sense and any help would be appreciated. Thanks so much in advance!
The idea with the pipe is good and simple, anything else would lack at least one of "good and simple", most likely both. But your other question is about the availability of certain system informations. Well, these informations are wildly dispersed and the exact place depends on the operating system actually in use.
For the common, non-specialized Linux system: for the filesystem it is /etc/mtab
(and use statfs()
for the details) and many system informations are in /proc
. If you need more, it gets complicated.
It is already quite complicated even if you want to build a simplified version of df
for example (original code of df
in $COREUTILS/src/df.c
). Instead of just running df
and read from a pipe, you have to do
/etc/mtab
and find the mount points (system might not have /etc/mtab
although it should)statfs()
on every mountpoint and print the resultyou'll need over 100 error-prone lines of C-code for this, even if you skip everything fancy. And that for printing the filesystem alone.
No, just read the output of the old and well tested programs from a pipe, it's the easiest way.
EDIT:
You use the full shell if you use the pipe. That means that you can use all other tools, too.
To make it simpler to test, here is a simplified version without ncurses (making it much more complicated ;-) ), just for playing at the commandline.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUF_SIZE 256
// ALL CHECKS OMMITTED!
char *sub_exec(const char *command)
{
char buf[BUF_SIZE];
char *ret;
size_t mem_alloc, mem_needed;
ret = malloc(4 * BUF_SIZE * sizeof(char));
// or use calloc or set \0 manually
memset(ret, '\0', 4 * BUF_SIZE);
// memory allocated
mem_alloc = 4 * BUF_SIZE;
// memory needed
mem_needed = 0;
// open the pipe read-only
FILE *out_pipe = popen(command, "r");
// until end of the output of the pipe (EOF)
while (!feof(out_pipe)) {
// read a chunk of the output
if (fgets(buf, BUF_SIZE, out_pipe) != NULL) {
mem_needed += BUF_SIZE;
if (mem_alloc < mem_needed) {
// no fancy algorithms
ret = realloc(ret, mem_needed + 4 * BUF_SIZE);
mem_alloc = mem_needed * 2;
}
// and conatenate it to the result
strncat(ret, buf, BUF_SIZE);
}
}
pclose(out_pipe);
// You may or may not readjust the memory used
// ret = realloc(ret, strlen(ret) + 1);
return ret;
}
int main(int argc, char **argv)
{
char *str_from_pipe;
if (argc < 2)
fprintf(stderr, "Usage: %s command\n", argv[0]);
str_from_pipe = sub_exec(argv[1]);
printf("%s\n", str_from_pipe);
free(str_from_pipe);
exit(EXIT_SUCCESS);
}
You can do simple things here, like
./readpipe "cat win*c | tr -d '\015' | perl -0777 -pe 's{/\*.*?\*/}{}gs' | indent -"
(concatenate all C-files, strip the \r
s, strip most comments and run it through indent(1)
)
Or with df
. Say you want the file systems with actual data in it, not tempfs
or alike, so:
./readpipe "df -P -h -t ext4"
That prints here:
Filesystem Size Used Avail Use% Mounted on
/dev/sda2 48G 33G 14G 71% /
/dev/sda3 861G 761G 56G 94% /home
(quite full, as it seems)
You can use it as it is or massage it further:
./readpipe "df -h -t ext4 --output=target,fstype,size,used,avail|awk '{if(NR>1)print}'"
Prints:
/ ext4 48G 33G 14G
/home ext4 861G 761G 56G
Caveat:
./readpipe "df -h -t ext4 --output=target,fstype,size,used,avail| sed -n '1!p'"
does not work, you need to exchange the kind of quotes (but it is not always that simple)
./readpipe 'df -h -t ext4 --output=target,fstype,size,used,avail | sed -n "1!p"'
To be able to split the entries with e.g.: strtok(3)
replace all whitespace with single tabs
./readpipe 'df -h -t ext4 --output=target,fstype,size,used,avail | sed -n "1!p" | sed -e "s/[ ]\+/\t/g"'
(yes, there are much more elegant ways to do it, but it is good enough)
More useful information in the files/directories about the CPU(s)
/sys/devices/system/cpu/cpu*/cpufreq/
/sys/devices/system/cpu/cpu*/cache/
Or by lscpu
. There are many ls*
programs that are useful, like lspci
, lsusb
, lskat
…no, wait, that's something different, and not to forget lsblk
(lists the block devices incl. partitions if avail.). A quite complete list of the installed hardware (some info need root-rights but it is already quite extensive without) is available with the help of lshw
and uname
is for information about the OS, also: free
for the memory consumption, and many, many more. Most, if not all allow for some kind of formatting, see the respective manpages for the gory details.
If you tell me what you you need, I can tell you where to find it (he says boldly ;-) ). Just ask in a comment below, I'll add it.