Read binary data from QProcess in Windows - windows

I have some .exe file (say some.exe) that writes to the standard output binary data. I have no sources of this program. I need to run some.exe from my C++/Qt application and read standard output of the process I created. When I'm trying to do this with QProcess::readAll someone replaces byte \n (0x0d) to \r\n (0x0a 0x0d).
Here is a code:
QProcess some;
some.start( "some.exe", QStringList() << "-I" << "inp.txt" );
// some.setTextModeEnabled( false ); // no effect at all
some.waitForFinished();
QByteArray output = some.readAll();
I tried in cmd.exe to redirect output to file like this:
some.exe -I inp.txt > out.bin
and viewed out.bin with hexedit there was 0a 0d in the place where should be 0d.
Edit:
Here is a simple program to emulate some.exe behaviour:
#include <stdio.h>
int main() {
char buf[] = { 0x00, 0x11, 0x0a, 0x33 };
fwrite( buf, sizeof( buf[ 0 ] ), sizeof( buf ), stdout );
}
run:
a.exe > out.bin
//out.bin
00 11 0d 0a 33
Note, that I can't modify some.exe that's why I shouldn't modify my example like _setmode( _fileno( stdout, BINARY ) )
The question is: how can I say to QProcess or to Windows or to console do not change CR with LF CR?
OS: Windows 7
Qt: 5.6.2

how can I say to QProcess or to Windows or to console do not change CR with LF CR?
They don't change anything. some.exe is broken. That's all. It outputs the wrong thing. Whoever made it output brinary data in text mode has messed up badly.
There's a way to recover, though. You have to implement a decoder that will fix the broken output of some.exe. You know that every 0a has to be preceded by 0d. So you have to parse the output, and if you find a 0a, and there's 0d before it, remove the 0d, and continue. Optionally, you can abort if a 0a is not preceded by 0d - some.exe should not produce such output since it's broken.
The appendBinFix function takes the corrupted data and appends the fixed version to a buffer.
// https://github.com/KubaO/stackoverflown/tree/master/questions/process-fix-binary-crlf-51519654
#include <QtCore>
#include <algorithm>
bool appendBinFix(QByteArray &buf, const char *src, int size) {
bool okData = true;
if (!size) return okData;
constexpr char CR = '\x0d';
constexpr char LF = '\x0a';
bool hasCR = buf.endsWith(CR);
buf.resize(buf.size() + size);
char *dst = buf.end() - size;
const char *lastSrc = src;
for (const char *const end = src + size; src != end; src++) {
char const c = *src;
if (c == LF) {
if (hasCR) {
std::copy(lastSrc, src, dst);
dst += (src - lastSrc);
dst[-1] = LF;
lastSrc = src + 1;
} else
okData = false;
}
hasCR = (c == CR);
}
dst = std::copy(lastSrc, src, dst);
buf.resize(dst - buf.constData());
return okData;
}
bool appendBinFix(QByteArray &buf, const QByteArray &src) {
return appendBinFix(buf, src.data(), src.size());
}
The following test harness ensures that it does the right thing, including emulating the output of some.exe (itself):
#include <QtTest>
#include <cstdio>
#ifdef Q_OS_WIN
#include <fcntl.h>
#include <io.h>
#endif
const auto dataFixed = QByteArrayLiteral("\x00\x11\x0d\x0a\x33");
const auto data = QByteArrayLiteral("\x00\x11\x0d\x0d\x0a\x33");
int writeOutput() {
#ifdef Q_OS_WIN
_setmode(_fileno(stdout), _O_BINARY);
#endif
auto size = fwrite(data.data(), 1, data.size(), stdout);
qDebug() << size << data.size();
return (size == data.size()) ? 0 : 1;
}
class AppendTest : public QObject {
Q_OBJECT
struct Result {
QByteArray d;
bool ok;
bool operator==(const Result &o) const { return ok == o.ok && d == o.d; }
};
static Result getFixed(const QByteArray &src, int split) {
Result f;
f.ok = appendBinFix(f.d, src.data(), split);
f.ok = appendBinFix(f.d, src.data() + split, src.size() - split) && f.ok;
return f;
}
Q_SLOT void worksWithLFCR() {
const auto lf_cr = QByteArrayLiteral("\x00\x11\x0a\x0d\x33");
for (int i = 0; i < lf_cr.size(); ++i)
QCOMPARE(getFixed(lf_cr, i), (Result{lf_cr, false}));
}
Q_SLOT void worksWithCRLF() {
const auto cr_lf = QByteArrayLiteral("\x00\x11\x0d\x0a\x33");
const auto cr_lf_fixed = QByteArrayLiteral("\x00\x11\x0a\x33");
for (int i = 0; i < cr_lf.size(); ++i)
QCOMPARE(getFixed(cr_lf, i), (Result{cr_lf_fixed, true}));
}
Q_SLOT void worksWithCRCRLF() {
for (int i = 0; i < data.size(); ++i) QCOMPARE(getFixed(data, i).d, dataFixed);
}
Q_SLOT void worksWithQProcess() {
QProcess proc;
proc.start(QCoreApplication::applicationFilePath(), {"output"},
QIODevice::ReadOnly);
proc.waitForFinished(5000);
QCOMPARE(proc.exitCode(), 0);
QCOMPARE(proc.exitStatus(), QProcess::NormalExit);
QByteArray out = proc.readAllStandardOutput();
QByteArray fixed;
appendBinFix(fixed, out);
QCOMPARE(out, data);
QCOMPARE(fixed, dataFixed);
}
};
int main(int argc, char *argv[]) {
QCoreApplication app(argc, argv);
if (app.arguments().size() > 1) return writeOutput();
AppendTest test;
QTEST_SET_MAIN_SOURCE_PATH
return QTest::qExec(&test, argc, argv);
}
#include "main.moc"

Unfortunately it has nothing to do with QProcess or Windows or console. It's all about CRT. Functions like printf or fwrite are taking into account _O_TEXT flag to add an additional 0x0D (true only for Windows). So the only solution is to modify stdout's fields of your some.exe with WriteProcessMemory or call the _setmode inside an address space of your some.exe with DLL Injection technique or patch the lib. But it's a tricky job.

Related

Shell Program in C, running executable in background

I am writing a simple shell program in C and I believe I have it just about finished. The program should continually print "Prompt>" and wait for a user to either enter the name of an executable along with any parameters the executable needs. The shell only has one built in function, quit, which ends the program. If the user were to put an '&' at the end of the line then the given executable should be run in the background. (Built-in functions and commands without the '&' should run in the foreground and wait for the child process to finish.) However when I run my code and put an '&' at the end of my line, the executable runs and finishes but I no longer see the "prompt>" show up. I can still enter the name of an executable or quit and it runs and everything but I don't understand why the prompt isn't showing up.
Also as a side question. Is my program properly handling child processes? Basically, am I not leaving zombie processes with this code?
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#define MAXBUFF 100
#define MAXLINE 200
int parse_line(char *buffer, char **arg_array);
void evaluate_commandline(char *commandline);
int builtin_command();
int parse_line(char *buffer, char **arg_array){
char *delimiter;
int num_args;
int run_background;
buffer[strlen(buffer)-1] = ' ';
while(*buffer && (*buffer == ' '))
buffer++;
num_args = 0;
while((delimiter = strchr(buffer, ' '))){
arg_array[num_args++] = buffer;
*delimiter = '\0';
buffer = delimiter + 1;
while(*buffer && (*buffer == ' '))
buffer++;
}
arg_array[num_args] = NULL;
if(num_args == 0)
return 1;
if((run_background = (*arg_array[num_args-1] == '&')) != 0)
arg_array[--num_args] = NULL;
return run_background;
}
void evaluate_commandline(char *commandline){
char *arg_array[MAXBUFF];
char buffer[MAXLINE];
int run_background;
pid_t pid;
strcpy(buffer, commandline);
run_background = parse_line(buffer, arg_array);
if(arg_array[0] == NULL)
return;
if(!builtin_command(arg_array)){
if((pid = fork())== 0){
if(execvp(arg_array[0],arg_array)< 0){
printf("%s: Command not found.\n", arg_array[0]);
exit(0);
}
}
if(!run_background){
int child_status;
wait(&child_status);
}
}
return;
}
int builtin_command(char **arg_array){
if(!strcmp(arg_array[0],"quit"))
exit(0);
return 0;
}
int main(){
char commandline[MAXLINE];
while(1){
printf("prompt> ");
fgets(commandline, MAXLINE, stdin);
if(feof(stdin))
exit(0);
evaluate_commandline(commandline);
}
}
i think where you say:
if(!run_background){
you forget a "else"
else if(!run_background){

As one MPI process executes MPI_Barrier(), other processes hang

I have an MPI program for having multiple processes read from a file that contains list of file names and based on the file names read - it reads the corresponding file and counts the frequency of words.
If one of the processes completes this and returns - to block executing MPI_Barrier(), the other processes also hang. On debugging, it could be seen that the readFile() function is not entered by the processes currently in process_files() Unable to figure out why this happens. Please find the code below:
#include <stdio.h>
#include <stdlib.h>
#include <mpi.h>
#include <ctype.h>
#include <string.h>
#include "hash.h"
void process_files(char*, int* , int, hashtable_t* );
void initialize_word(char *c,int size)
{
int i;
for(i=0;i<size;i++)
c[i]=0;
return;
}
char* readFilesList(MPI_File fh, char* file,int rank, int nprocs, char* block, const int overlap, int* length)
{
char *text;
int blockstart,blockend;
MPI_Offset size;
MPI_Offset blocksize;
MPI_Offset begin;
MPI_Offset end;
MPI_Status status;
MPI_File_open(MPI_COMM_WORLD,file,MPI_MODE_RDONLY,MPI_INFO_NULL,&fh);
MPI_File_get_size(fh,&size);
/*Block size calculation*/
blocksize = size/nprocs;
begin = rank*blocksize;
end = begin+blocksize-1;
end+=overlap;
if(rank==nprocs-1)
end = size;
blocksize = end-begin+1;
text = (char*)malloc((blocksize+1)*sizeof(char));
MPI_File_read_at_all(fh,begin,text,blocksize,MPI_CHAR, &status);
text[blocksize+1]=0;
blockstart = 0;
blockend = blocksize;
if(rank!=0)
{
while(text[blockstart]!='\n' && blockstart!=blockend) blockstart++;
blockstart++;
}
if(rank!=nprocs-1)
{
blockend-=overlap;
while(text[blockend]!='\n'&& blockend!=blocksize) blockend++;
}
blocksize = blockend-blockstart;
block = (char*)malloc((blocksize+1)*sizeof(char));
block = memcpy(block, text + blockstart, blocksize);
block[blocksize]=0;
*length = strlen(block);
MPI_File_close(&fh);
return block;
}
void calculate_term_frequencies(char* file, char* text, hashtable_t *hashtable,int rank)
{
printf("Start File %s, rank %d \n\n ",file,rank);
fflush(stdout);
if(strlen(text)!=0||strlen(file)!=0)
{
int i,j;
char w[100];
i=0,j=0;
while(text[i]!=0)
{
if((text[i]>=65&&text[i]<=90)||(text[i]>=97&&text[i]<=122))
{
w[j]=text[i];
j++; i++;
}
else
{
w[j] = 0;
if(j!=0)
{
//ht_set( hashtable, strcat(strcat(w,"#"),file),1);
}
j=0;
i++;
initialize_word(w,100);
}
}
}
return;
}
void readFile(char* filename, hashtable_t *hashtable,int rank)
{
MPI_Status stat;
MPI_Offset size;
MPI_File fx;
char* textFromFile=0;
printf("Start File %d, rank %d \n\n ",strlen(filename),rank);
fflush(stdout);
if(strlen(filename)!=0)
{
MPI_File_open(MPI_COMM_WORLD,filename,MPI_MODE_RDONLY,MPI_INFO_NULL,&fx);
MPI_File_get_size(fx,&size);
printf("Start File %s, rank %d \n\n ",filename,rank);
fflush(stdout);
textFromFile = (char*)malloc((size+1)*sizeof(char));
MPI_File_read_at_all(fx,0,textFromFile,size,MPI_CHAR, &stat);
textFromFile[size]=0;
calculate_term_frequencies(filename, textFromFile, hashtable,rank);
MPI_File_close(&fx);
}
printf("Done File %s, rank %d \n\n ",filename,rank);
fflush(stdout);
return;
}
void process_files(char* block, int* length, int rank,hashtable_t *hashtable)
{
char s[2];
s[0] = '\n';
s[1] = 0;
char *file;
if(*length!=0)
{
/* get the first file */
file = strtok(block, s);
/* walk through other tokens */
while( file != NULL )
{
readFile(file,hashtable,rank);
file = strtok(NULL, s);
}
}
return;
}
void execute_process(MPI_File fh, char* file, int rank, int nprocs, char* block, const int overlap, int * length, hashtable_t *hashtable)
{
block = readFilesList(fh,file,rank,nprocs,block,overlap,length);
process_files(block,length,rank,hashtable);
}
int main(int argc, char *argv[]){
/*Initialization*/
MPI_Init(&argc, &argv);
MPI_File fh=0;
int rank,nprocs,namelen;
char *block=0;
const int overlap = 70;
char* file = "filepaths.txt";
int *length = (int*)malloc(sizeof(int));
hashtable_t *hashtable = ht_create( 65536 );
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &nprocs);
char processor_name[MPI_MAX_PROCESSOR_NAME];
MPI_Get_processor_name(processor_name, &namelen);
printf("Rank %d is on processor %s\n",rank,processor_name);
fflush(stdout);
execute_process(fh,file,rank,nprocs,block,overlap,length,hashtable);
printf("Rank %d returned after processing\n",rank);
MPI_Barrier(MPI_COMM_WORLD);
MPI_Finalize();
return 0;
}
The filepaths.txt is a file that contain the absolute file names of normal text files:
eg:
/home/mpiuser/mpi/MPI_Codes/code/test1.txt
/home/mpiuser/mpi/MPI_Codes/code/test2.txt
/home/mpiuser/mpi/MPI_Codes/code/test3.txt
Your readFilesList function is pretty confusing, and I believe it doesn't do what you want it to do, but maybe I just do not understand it correctly. I believe it is supposed to collect a bunch of filenames out of the list file for each process. A different set for each process. It does not do that, but this is not the problem, even if this would do what you want it to, the subsequent MPI IO would not work.
When reading files, you use MPI_File_read_all with MPI_COMM_WORLD as communicator. This requires all processes to participate in reading this file. Now, if each process should read a different file, this obviously is not going to work.
So there are several issues with your implementation, though I can not really explain your described behavior, I would rather first start off and try to fix them, before debugging in detail, what might go wrong.
I am under the impression, you want to have an algorithm along these lines:
Read a list of file names
Distribute that list of files equally to all processes
Have each process work on its own set of files
Do something with the data from this processing
And I would suggest to try this with the following approach:
Read the list on a single process (no MPI IO)
Scatter the list of files to all processes, such that all get around the same amount of work
Have each process work on its list of files independently and in serial (serial file access and processing)
Some data reduction with MPI, as needed
I believe, this would be the best (easiest and fastest) strategy in your scenario. Note, that no MPI IO is involved here at all. I don't think doing some complicated distributed reading of the file list in the first step would result in any advantage here, and in the actual processing it would actually be harmful. The more independent your processes are, the better your scalability usually.

Passing input with netcat to a simple server

I am trying to write an Ruby script to pass strings to a simple server running in a VM and I am stuck at passing the strings without creating inifinite loops in my server program.
The Content of the Server(written in C):
#include <stdio.h>
#include <strings.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define PORTNO 12346
int h=0,b=0,p=0;
#define BUFFER_SIZE 512
int checksec(FILE* f){
FILE* key;
char buf[1024];
if(h&b&p){
key=fopen("easy_key","r");
fread(buf,1024,1,key);
fprintf(f,"%s",buf);
fclose(key);
return 0;
}
return 1;
}
void hekers(FILE* f){
volatile int zeroWeekend;
char buf[32];
fprintf(f,"So you want to be an 31337 Hax0r?\n");
fgets(buf,40,f);
switch(strcmp("y3$\n",buf)){
case 0:
fprintf(f,"First you must get power\n");
break;
default:
fprintf(f,"Well then go away\n");
break;
}
if(zeroWeekend==0xcafebabe){
h=1;
}
return;
}
void batmenss(FILE* f){
volatile int batsignet;
char buf[32];
fprintf(f,"So you want to be the batman?\n");
fgets(buf,40,f);
switch(strcmp("YESSSSSSS\n",buf)){
case 0:
fprintf(f,"First you must get rich\n");
break;
default:
fprintf(f,"Well then go away\n");
break;
}
if(batsignet==0x12345678){
b=1;
}
return;
}
void pokemans(FILE* f){
volatile int pikachy;
char buf[32];
fprintf(f,"So you want to be the best there ever was?\n");
fgets(buf,40,f);
switch(strcmp("catchemall\n",buf)){
case 0:
fprintf(f,"First you must get respect\n\n");
break;
default:
fprintf(f,"Well then go away\n");
break;
}
if(pikachy==0xfa75beef){
p=1;
}
return;
}
void readInput(int sock){
int msg;
char choice[4];
char buffer[BUFFER_SIZE];
FILE* fptr = fdopen(sock, "r+");
char* prompt="Do you want to be a?\n"
"1.) Pokemon Master\n"
"2.) Elite Hacker\n"
"3.) The Batman\n";
while(checksec(fptr)){
fprintf(fptr,"%s",prompt);
fgets(choice,4,fptr);
switch(choice[0]){
case '1':
pokemans(fptr);
break;
case '2':
hekers(fptr);
break;
case '3':
batmenss(fptr);
break;
default:
fprintf(fptr,"\nThat is not one of the choices\n");
fflush(fptr);
}
}
fprintf(fptr, "%s", buffer);
fflush(fptr);
fclose(fptr);
return;
}
int main(int argc, char *argv[])
{
char buffer[BUFFER_SIZE];
int sockfd, newsockfd, portno, pid;
socklen_t clilen;
struct sockaddr_in serv_addr, cli_addr;
/*
if (argc < 2) {
fprintf(stderr,"ERROR, no port provided\n");
exit(1);
}
*/
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0){
perror("ERROR opening socket");
exit(1);
}
bzero((char *) &serv_addr, sizeof(serv_addr));
//portno = atoi(argv[1]);
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr("0.0.0.0");
serv_addr.sin_port = htons(PORTNO);
if (bind(sockfd, (struct sockaddr *) &serv_addr,
sizeof(serv_addr)) < 0){
perror("ERROR on binding");
exit(1);
}
listen(sockfd,5);
clilen = sizeof(cli_addr);
while (1) {
newsockfd = accept(sockfd,
(struct sockaddr *) &cli_addr, &clilen);
if (newsockfd < 0)
perror("ERROR on accept");
pid = fork();
if (pid < 0)
perror("ERROR on fork");
if (pid == 0) {
close(sockfd);
readInput(newsockfd);
return;
}
else close(newsockfd);
waitpid(-1,NULL,WNOHANG);
} /* end of while */
close(sockfd);
return 0; /* we never get here */
}
When I connect to the server, it looks like this:
user#DESKTOP-LINUX:~/Documents/tob/ctf/exploits/binary1_workshop/easy$ nc 192.168.178.40 12346
Do you want to be a?
1.) Pokemon Master
2.) Elite Hacker
3.) The Batman
Now the Program waits for an input and another string will be printed and then the Program waits for another input and so on...
Now the real problem comes when I try to use a Ruby Script that should dictate the input that the Program should get.
I tried it with this Ruby Script (Filename: script.rb):
#!/usr/bin/env ruby
firstinput = "1"
puts select + "\r\n"
secondinput = "2"
puts secondinput + "\r\n"
And executed it with this command:
user#DESKTOP-LINUX:~/Documents/Code/binary1_workshop_exploits$ ./script.rb | nc 192.168.178.40 12346
But the output is just an infinite loop of the "main menu"...
How do I fix this problem?
P.S. I am running Ubuntu 14.04 64-Bit and the VM with the Server is running Ubuntu 14.04 32-Bit

Concatenation tchar variables

I work with WinAPI and I have a function get_disk_drives() for retrieves available disk drives and a helper function get_current_disk_drive() for retrieves the full path and file name of the specified file.
void get_current_disk_drive(TCHAR dirname[]) {
TCHAR *fileExt = NULL;
TCHAR szDir[MAX_PATH];
GetFullPathName(dirname, MAX_PATH, szDir, &fileExt);
_tprintf(_T("Full path: %s \nFilename: %s\n"), szDir, fileExt);
}
void get_disk_drives() {
DWORD drives_bitmask = GetLogicalDrives();
for (int i = 0; i < 26; i++) {
if ((drives_bitmask >> i) & 1) {
TCHAR drive_name = (char)(65 + i);
TCHAR drive_path[] = drive_name + "\\";
get_current_disk_drive(drive_path);
}
}
}
int _tmain(int argc, _TCHAR* argv[]) {
get_disk_drives();
return 0;
}
Here I can't make concatenation:
TCHAR drive_name = (char)(65 + i);
TCHAR drive_path[] = drive_name + "\\";
get_current_disk_drive(drive_path);
Why? Where is my mistake?
operator+ cannot be used for C-strings, string literals, or characters. The effect (for legal expressions anyway) is pointer arithmetic. For concatenation you have to either explicitly call one of the strcat functions, or use std::basic_string instead:
typedef std::basic_string<TCHAR> tstring;
tstring drive_name;
drive_name += TCHAR( 65 + i );
tstring drive_path = drive_name + _T( '\\' );
You can access a C-string from a std::basic_string by invoking its c_str() member. Since this is a C-string represented as a pointer, you would have to change the signature of get_current_disk_drive to void get_current_disk_drive(const TCHAR* dirname), or pass a const tstring&.
It's also a good idea to stop using Code::Blocks. Defaulting to MBCS character encoding in 2015 is a crime.

Symbolic errno to String

Is there a command-line tool that will take a symbolic errno such as EINVAL and print the corresponding string, Invalid argument?
I would like to avoid having to find that EINVAL is value 22 on my system and then using$ perror 22.
Ideally I could write something like
$ errorcommand EINVAL
Invalid argument
$
AFAIK, there isn't a standard tool that does the job. At one level, it wouldn't be particularly hard to write one - the messiest parts are finding the correct file to parse (is is often, but by no means always, /usr/include/sys/errno.h) and then taking the data from that to do the mapping of names to numbers. I have not found a system that uses enum values rather than #define values, but it is probably only a matter of time. It is also a moot point whether to generate a triple consisting of token number (EINTR, etc), token name ("EINTR", etc) and error message ("Interrupted system call", etc), or whether to use just the number and name and leave it to 'strerror()' to supply the message.
As I said, it isn't particularly hard. I already had a program called 'errno' that accepted pure numeric values and printed the corresponding error messages:
$ errno 1:10 20
1: Operation not permitted
2: No such file or directory
3: No such process
4: Interrupted system call
5: Input/output error
6: Device not configured
7: Argument list too long
8: Exec format error
9: Bad file descriptor
10: No child processes
20: Not a directory
$
I've written a Perl script and hacked the program to handle symbolic error numbers too:
$ errno 1:4 EINTR ENOTDIR
1 (EPERM): Operation not permitted
2 (ENOENT): No such file or directory
3 (ESRCH): No such process
4 (EINTR): Interrupted system call
EINTR (4): Interrupted system call
ENOTDIR (20): Not a directory
$
It does not handle ranges of symbolic error numbers (exercise for the reader).
generrno.pl
#!/usr/bin/perl -w
#
# #(#)$Id: generrno.pl,v 1.1 2010/02/07 18:39:18 jleffler Exp jleffler $
#
# Generate table of error number constants from given file(s)
use strict;
my %symlist;
my $maxsymlen = 0;
my $maxmsglen = 0;
while (<>)
{
next unless m%^\s*#\s*define\s+(E[A-Z0-9a-z]+)\s+(\d+)\s*/\*\s*([A-Za-z].*\S)\s*\*/%;
$symlist{$1} = { number => $2, message => $3 };
$maxsymlen = length($1) if length($1) > $maxsymlen;
$maxmsglen = length($3) if length($3) > $maxmsglen;
}
my $format = sprintf " { %%-%ds %%-%ds %%-5s %%-%ds },\n", $maxsymlen + 3, $maxsymlen + 1, $maxmsglen + 2;
foreach my $key (sort keys %symlist)
{
my $name = qq{"$key",};
my $symbol = qq{$key,};
my $number = qq{$symlist{$key}->{number},};
my $message = qq{"$symlist{$key}->{message}"};
printf $format, $name, $symbol, $number, $message;
}
errno.c
/*
#(#)File: $RCSfile: errno.c,v $
#(#)Version: $Revision: 2.2 $
#(#)Last changed: $Date: 2010/02/07 19:22:37 $
#(#)Purpose: Print messages corresponding to errno values or name
#(#)Author: J Leffler
#(#)Copyright: (C) JLSS 2003,2005,2008,2010
*/
/*TABSTOP=4*/
#define MAIN_PROGRAM
/* Need O/S specific messages as well as POSIX messages */
//#if __STDC_VERSION__ >= 199901L
//#define _XOPEN_SOURCE 600
//#else
//#define _XOPEN_SOURCE 500
//#endif /* __STDC_VERSION__ */
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> /* getopt() on MacOS X 10.2 */
#include "stderr.h"
#include "range.h"
typedef struct err_info
{
const char *errsym; /* Error symbol - "EINTR" */
int errnum; /* Error number - EINTR */
int errdef; /* Error define - 4 */
const char *errmsg; /* Error message - Interrupted system call */
} err_info;
/*
** Generate generrno.h using:
** perl generrno.pl /usr/include/sys/errno.h > generrno.h
** NB: list must be sorted alphabetically on symbol name
*/
static const err_info err_msgs[] =
{
#include "generrno.h"
};
static const char usestr[] = "[-qV] [--] lo[:hi] ...";
#define DIM(x) (sizeof(x)/sizeof(*(x)))
static const err_info *err_nums[DIM(err_msgs)];
#ifndef lint
/* Prevent over-aggressive optimizers from eliminating ID string */
const char jlss_id_errno_c[] = "#(#)$Id: errno.c,v 2.2 2010/02/07 19:22:37 jleffler Exp $";
#endif /* lint */
static int cmp_err_number(const void *v1, const void *v2)
{
int e1 = (*((const err_info * const *)v1))->errnum;
int e2 = (*((const err_info * const *)v2))->errnum;
return(e1 - e2);
}
static void map_numbers(void)
{
int i;
for (i = 0; i < DIM(err_msgs); i++)
err_nums[i] = &err_msgs[i];
qsort(err_nums, DIM(err_nums), sizeof(*err_nums), cmp_err_number);
}
static const char *err_symbol(int num)
{
const char *sym = "<UNKNOWN>";
err_info lookfor = { 0, num, 0, 0 };
err_info *lookptr = &lookfor;
const err_info **found = bsearch(&lookptr, err_nums, DIM(err_nums), sizeof(*err_nums), cmp_err_number);
if (found != 0)
sym = (*found)->errsym;
return(sym);
}
static int cmp_err_symbol(const void *v1, const void *v2)
{
const char *s1 = ((const err_info *)v1)->errsym;
const char *s2 = ((const err_info *)v2)->errsym;
return(strcmp(s1, s2));
}
static int pr_string_errno(const char *arg, int qflag)
{
int estat = EXIT_SUCCESS;
err_info lookfor = { arg, 0, 0, 0 };
const err_info *found = bsearch(&lookfor, err_msgs, DIM(err_msgs), sizeof(*err_msgs), cmp_err_symbol);
if (found == 0)
{
err_remark("unrecognized symbol %s\n", arg);
estat = EXIT_FAILURE;
}
else if (qflag == 0)
printf("%s (%d): %s\n", arg, found->errnum, found->errmsg);
return(estat);
}
static int pr_number_errno(const char *arg, int qflag)
{
int estat = EXIT_SUCCESS;
long lo;
long hi;
const char *endp;
long msg;
endp = numeric_range(arg, &lo, &hi);
if (endp == arg)
err_remark("Invalid range specified (%s) - should be lo[:hi]\n", arg);
else if (*endp != '\0')
err_remark("Non-numeric character (%c) after range '%s'\n",
(isprint((unsigned char)*endp) ? *endp : '?'), arg);
else
{
for (msg = lo; msg <= hi; msg++)
{
char *msgtxt = strerror(msg);
if (msgtxt == 0)
{
err_remark("no message for errno = %ld\n", msg);
estat = EXIT_FAILURE;
}
else if (qflag == 0)
printf("%ld (%s): %s\n", msg, err_symbol(msg), msgtxt);
}
}
return(estat);
}
static int pr_errno(char *arg, int qflag)
{
int estat;
if (isalpha(*arg))
estat = pr_string_errno(arg, qflag);
else
estat = pr_number_errno(arg, qflag);
return(estat);
}
int main(int argc, char **argv)
{
int i;
int opt;
int nstat;
int estat = EXIT_SUCCESS;
int qflag = 0;
int nflag = 0;
err_setarg0(argv[0]);
map_numbers();
while ((opt = getopt(argc, argv, "qV0:1:2:3:4:5:6:7:8:9:")) != EOF)
{
switch (opt)
{
case 'q':
qflag = 1;
break;
case 'V':
err_version("ERRNO", "$Revision: 2.2 $ ($Date: 2010/02/07 19:22:37 $)");
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
/* GETOPT() is probably not the right tool for this job! */
nstat = pr_errno(optarg-2, qflag);
if (estat == EXIT_SUCCESS)
estat = nstat;
nflag = 1;
break;
default:
err_usage(usestr);
break;
}
}
if (optind >= argc && nflag == 0)
err_usage(usestr);
for (i = optind; i < argc; i++)
{
nstat = pr_errno(argv[i], qflag);
if (estat == EXIT_SUCCESS)
estat = nstat;
}
return(estat);
}
The code needs some supporting files - stderr.h, range.h, range2.c and stderrmin.c (a simpler version of the stderr.c I normally use, which has extra bells and whistles for handling syslog and writing to file descriptors instead of file pointers.).
stderr.h
/*
#(#)File: $RCSfile: stderr.h,v $
#(#)Version: $Revision: 9.2 $
#(#)Last changed: $Date: 2009/03/06 06:52:26 $
#(#)Purpose: Header file for standard error functions
#(#)Author: J Leffler
#(#)Copyright: (C) JLSS 1989-93,1996-99,2003,2005-09
#(#)Product: :PRODUCT:
*/
#ifndef STDERR_H
#define STDERR_H
#ifdef MAIN_PROGRAM
#ifndef lint
/* Prevent over-aggressive optimizers from eliminating ID string */
const char jlss_id_stderr_h[] = "#(#)$Id: stderr.h,v 9.2 2009/03/06 06:52:26 jleffler Exp $";
#endif /* lint */
#endif
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */
#include <stdio.h>
#include <stdarg.h>
#ifdef __GNUC__
#define PRINTFLIKE(n,m) __attribute__((format(printf,n,m)))
#define NORETURN() __attribute__((noreturn))
#else
#define PRINTFLIKE(n,m) /* If only */
#define NORETURN() /* If only */
#endif /* __GNUC__ */
/* -- Definitions for error handling */
enum { ERR_STAT = 1 }; /* Default exit status */
enum { ERR_DEFAULT = 0x0000 }; /* Default flag */
enum { ERR_NOFLUSH = 0x0001 }; /* Do not flush open files */
enum { ERR_EXIT = 0x0004 }; /* Exit -- do not return */
enum { ERR_ABORT = 0x0008 }; /* Abort -- do not return */
enum { ERR_STAMP = 0x0020 }; /* Timestamp messages */
enum { ERR_NOARG0 = 0x0040 }; /* Do not print arg0 prefix */
enum { ERR_PID = 0x0080 }; /* Include pid=nnnnn info */
enum { ERR_ERRNO = 0x0100 }; /* Include system error */
#ifdef USE_STDERR_SYSLOG
/* Definitions related to using syslog */
enum { ERR_LOG_EMERG = 0x01000 }; /* system is unusable */
enum { ERR_LOG_ALERT = 0x02000 }; /* action must be taken immediately */
enum { ERR_LOG_CRIT = 0x04000 }; /* critical conditions */
enum { ERR_LOG_ERR = 0x08000 }; /* error conditions */
enum { ERR_LOG_WARNING = 0x10000 }; /* warning conditions */
enum { ERR_LOG_NOTICE = 0x20000 }; /* normal but signification condition */
enum { ERR_LOG_INFO = 0x40000 }; /* informational */
enum { ERR_LOG_DEBUG = 0x80000 }; /* debug-level messages */
enum { ERR_LOG_LEVEL_HI = ERR_LOG_EMERG|ERR_LOG_ALERT|ERR_LOG_CRIT|ERR_LOG_ERR };
enum { ERR_LOG_LEVEL_LO = ERR_LOG_WARNING|ERR_LOG_NOTICE|ERR_LOG_INFO|ERR_LOG_DEBUG };
enum { ERR_LOG_LEVEL = ERR_LOG_LEVEL_HI|ERR_LOG_LEVEL_LO };
#endif /* USE_STDERR_SYSLOG */
/* -- Standard combinations of flags */
enum { ERR_REM = ERR_DEFAULT };
enum { ERR_ERR = ERR_EXIT };
enum { ERR_ABT = ERR_ABORT };
enum { ERR_LOG = ERR_STAMP|ERR_PID };
enum { ERR_SYSREM = ERR_REM|ERR_ERRNO };
enum { ERR_SYSERR = ERR_ERR|ERR_ERRNO };
/* -- Maximum recorded length of argv[0]; extra is truncated */
enum { ERR_MAXLEN_ARGV0 = 63 };
/* -- Global definitions */
extern const char err_format1[]; /* "%s\n" - for one string argument */
extern const char err_format2[]; /* "%s %s\n" - for two string arguments */
extern const char *err_getarg0(void);
extern void err_setarg0(const char *argv0);
extern FILE *err_stderr(FILE *fp);
extern const char *err_rcs_string(const char *s, char *buffer, size_t buflen);
extern void err_abort(const char *format, ...) PRINTFLIKE(1,2) NORETURN();
extern void err_error(const char *format, ...) PRINTFLIKE(1,2) NORETURN();
extern void err_error1(const char *s1) NORETURN();
extern void err_error2(const char *s1, const char *s2) NORETURN();
extern void err_help(const char *use_str, const char *hlp_str) NORETURN();
extern void err_helplist(const char *use_str, const char * const *help_list) NORETURN();
extern void err_internal(const char *function, const char *msg) NORETURN();
extern void err_logmsg(FILE *fp, int flags, int estat, const char *format, ...) PRINTFLIKE(4,5);
extern void err_print(int flags, int estat, const char *format, va_list args);
extern void err_printversion(const char *program, const char *verinfo);
extern void err_remark(const char *format, ...) PRINTFLIKE(1,2);
extern void err_remark1(const char *s1);
extern void err_remark2(const char *s1, const char *s2);
extern void err_report(int flags, int estat, const char *format, ...) PRINTFLIKE(3,4);
extern void err_syserr(const char *format, ...) PRINTFLIKE(1,2) NORETURN();
extern void err_syserr1(const char *s1) NORETURN();
extern void err_syserr2(const char *s1, const char *s2) NORETURN();
extern void err_sysrem(const char *format, ...) PRINTFLIKE(1,2);
extern void err_sysrem1(const char *s1);
extern void err_sysrem2(const char *s1, const char *s2);
extern void err_usage(const char *usestr) NORETURN();
extern void err_version(const char *program, const char *verinfo) NORETURN();
extern int err_getlogopts(void); /* Get default log options */
extern int err_setlogopts(int new_opts); /* Set default log options */
#ifdef USE_STDERR_FILEDESC
extern int err_use_fd(int fd); /* Use file descriptor */
#endif /* USE_STDERR_FILEDESC */
#ifdef USE_STDERR_SYSLOG
/* In case of doubt, use zero for both logopts and facility */
extern int err_use_syslog(int logopts, int facility); /* Configure/use syslog() */
#endif /* USE_STDERR_SYSLOG */
/*
** JL 2003-07-31: Security Note.
** Question: given that err_remark("abc\n") and err_remark1("abc")
** produce the same output, when should you use err_remark1()
** instead of err_remark()?
** Answer 1: trivia - when you can't put the newline in the string.
** Answer 2: security - when the argument contains user input and could,
** therefore, contain conversion specifiers, etc. The code in
** err_remark() does not (and cannot) verify that you have
** passed correct arguments for the conversion specifiers in
** the format string.
** Answer 3: inertia - when migrating code that uses remark().
**
** Generalizing: when you use a function that has 'const char *format'
** in the prototype above, make sure your code is fully in charge of the
** format string to avoid security lapses. Do not allow the user to
** provide that string unless you stringently check it beforehand.
*/
#endif /* STDERR_H */
range.h
/*
#(#)File: $RCSfile: range.h,v $
#(#)Version: $Revision: 1.8 $
#(#)Last changed: $Date: 2008/02/11 07:39:36 $
#(#)Purpose: Declaration of range parsing functions
#(#)Author: J Leffler
#(#)Copyright: (C) JLSS 1997,2005,2007-08
#(#)Product: :PRODUCT:
*/
/*TABSTOP=4*/
#ifndef RANGE_H
#define RANGE_H
#ifdef MAIN_PROGRAM
#ifndef lint
/* Prevent over-aggressive optimizers from eliminating ID string */
const char jlss_id_range_h[] = "#(#)$Id: range.h,v 1.8 2008/02/11 07:39:36 jleffler Exp $";
#endif /* lint */
#endif /* MAIN_PROGRAM */
/*
** parse_range(): parse range of non-negative numbers.
**
** Given a string, parse_range() returns the lo and hi values corresponding
** to the range specified by the string. For example:
** Input: Low High
** 23 23 23
** 23-25 23 25
** 23- 23 0
** -23 0 23
** Any delimiter other than '-' before or after a number terminates the
** scan, but commas are skipped. Returns pointer to character after
** last character parsed (which may or may not be '\0') if successful.
** Otherwise, returns null.
**
** Idiomatic use:
**
** const char *ptr = source_string;
** const char *nxt;
** while ((nxt = parse_range(ptr, &lo, &hi)) != 0)
** {
** if (nxt == ptr)
** err_error("invalid range string (%s)\n", source_string);
** use_range(lo, hi);
** ptr = nxt;
** }
*/
extern const char *parse_range(const char *str, long *lo, long *hi);
/*
** numeric_range(): parse range of numbers, positive or negative.
**
** Input: Low High
** 23 23 23
** -23 -23 -23
** 23:25 23 25
** 23..25 23 25
** -23..-25 -25 -23
** -23..25 -23 25
** 23..-25 -25 23
** Returns pointer to '\0' at end of string if OK, sets *lo and *hi,
** and guarantees *lo <= *hi.
** Otherwise, returns pointer to start of string and does not set *lo or *hi.
**
** Idiomatic use:
**
** const char *ptr = source_string;
** const char *nxt;
** while ((nxt = numeric_range(ptr, &lo, &hi)) != 0)
** {
** if (nxt == ptr)
** err_error("invalid range string (%s)\n", source_string);
** use_range(lo, hi);
** ptr = nxt;
** }
*/
extern const char *numeric_range(const char *str, long *lo, long *hi);
#endif /* RANGE_H */
range2.c
/*
#(#)File: $RCSfile: range2.c,v $
#(#)Version: $Revision: 1.8 $
#(#)Last changed: $Date: 2008/02/11 08:44:50 $
#(#)Purpose: Decode string into range of integers.
#(#)Author: J Leffler
#(#)Copyright: (C) JLSS 1997,2002,2005,2007-08
#(#)Product: :PRODUCT:
*/
/*TABSTOP=4*/
/*
** Parse number ranges, dealing with positive and negative values,
** and ranges separated by either colon or double-dot.
**
** Input: Low High
** 23 23 23
** -23 -23 -23
** 23:25 23 25
** 23..25 23 25
** -23..-25 -25 -23
** -23..25 -23 25
** 23..-25 -25 23
** -23..+25 -23 25
** Any other delimiter after number (or before number) terminates
** input. NB: a leading colon (or dot) is not a valid range. If
** there is a format error, the returned pointer points to the
** start of the string (and lo and hi are unchanged). If there is
** no error, then the returned pointer points to the ASCII NUL at
** the end of the string.
*/
#include "range.h"
#include <stdlib.h>
#ifndef lint
/* Prevent over-aggressive optimizers from eliminating ID string */
const char jlss_id_range2_c[] = "#(#)$Id: range2.c,v 1.8 2008/02/11 08:44:50 jleffler Exp $";
#endif /* lint */
/*
** Parse numeric range.
** Return pointer to trailing '\0' if OK, else pointer to input string
*/
const char *numeric_range(const char *str, long *lo, long *hi)
{
const char *s = str;
char *t;
long l;
long h;
l = strtol(s, &t, 10);
if (*t == '\0')
{
/* Just one number */
*lo = *hi = l;
return(t);
}
if (*t == ':')
t += 1;
else if (t[0] == '.' && t[1] == '.')
t += 2;
else
{
/* Format error */
return(str);
}
h = strtol(t, &t, 10);
if (*t != '\0')
{
/* Format error */
return(str);
}
if (h < l)
{
long x = h;
h = l;
l = x;
}
*lo = l;
*hi = h;
return(t);
}
#ifdef TEST
#include <stdio.h>
#include "stderr.h"
int main(int argc, char **argv)
{
int i;
long lo;
long hi;
const char *t;
const char *s;
err_setarg0(argv[0]);
if (argc <= 1)
err_usage("range [...]");
for (i = 1; i < argc; i++)
{
t = argv[i];
if (t != 0 && *t != '\0')
{
printf("Parse: %15s (addr = 0x%08lX) ", t, (unsigned long)t);
fflush(stdout);
s = numeric_range(t, &lo, &hi);
printf("Range: %2ld -> %2ld (addr = 0x%08lX; trailer = <<%s>>)\n", lo, hi, (unsigned long)s, s);
fflush(stdout);
}
}
return(0);
}
#endif /* TEST */
stderrmin.c
This is about 400 lines, instead of about 700 lines. Yes, it is overkill for this program; I don't use it only in this program.
/*
#(#)File: $RCSfile: stderrmin.c,v $
#(#)Version: $Revision: 9.6 $
#(#)Last changed: $Date: 2009/03/02 20:27:38 $
#(#)Purpose: Minimal implementation of error reporting routines
#(#)Author: J Leffler
#(#)Copyright: (C) JLSS 1988-91,1996-99,2001,2003,2005-09
#(#)Product: :PRODUCT:
*/
/*TABSTOP=4*/
#undef STDERR_EXTENDED
#include "stderr.h"
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#else
extern int getpid(void);
#endif /* HAVE_UNISTD_H */
enum { MAX_MSGLEN = 2048 };
/* Global format strings */
const char err_format1[] = "%s\n";
const char err_format2[] = "%s %s\n";
static const char def_format[] = "%Y-%m-%d %H:%M:%S";
static const char *tm_format = def_format;
static char arg0[ERR_MAXLEN_ARGV0+1] = "**undefined**";
/* Permitted default error flags */
enum { ERR_LOGOPTS = ERR_NOFLUSH | ERR_EXIT | ERR_ABORT | ERR_STAMP |
ERR_NOARG0 | ERR_PID | ERR_ERRNO };
static int err_flags = 0; /* Default error flags (ERR_STAMP, ERR_PID, etc) */
static FILE *errout = 0;
/*
** err_???_print() functions are named systematically, and are all static.
**
** err_[ev][crx][fn]_print():
** -- e takes ellipsis argument
** -- v takes va_list argument
** -- c conditionally exits
** -- r returns (no exit)
** -- x exits (no return)
** -- f takes file pointer
** -- n no file pointer (use errout)
**
** NB: no-return and printf-like can only be attached to declarations, not definitions.
*/
static void err_vxf_print(FILE *fp, int flags, int estat, const char *format, va_list args)
NORETURN();
static void err_vxn_print(int flags, int estat, const char *format, va_list args)
NORETURN();
static void err_exn_print(int flags, int estat, const char *format, ...)
NORETURN() PRINTFLIKE(3,4);
static void err_terminate(int flags, int estat) NORETURN();
#ifndef lint
/* Prevent over-aggressive optimizers from eliminating ID string */
const char jlss_id_stderrmin_c[] = "#(#)$Id: stderrmin.c,v 9.6 2009/03/02 20:27:38 jleffler Exp $";
#endif /* lint */
/*
** Set default log options, returning old value.
** Setting ERR_EXIT and ERR_ABORT is permitted but not recommended.
*/
int err_setlogopts(int new_opts)
{
int old_opts = err_flags;
err_flags = new_opts & ERR_LOGOPTS;
return(old_opts);
}
/* Return default log options */
int err_getlogopts(void)
{
return(err_flags);
}
/* Change the definition of 'stderr', reporting on the old one too */
/* NB: using err_stderr((FILE *)0) simply reports the current 'stderr' */
FILE *(err_stderr)(FILE *newerr)
{
FILE *old;
if (errout == 0)
errout = stderr;
old = errout;
if (newerr != 0)
errout = newerr;
return(old);
}
/* Return stored basename of command */
const char *(err_getarg0)(void)
{
return(arg0);
}
/* Store basename of command, excluding trailing slashes */
void (err_setarg0)(const char *argv0)
{
/* Ignore three pathological program names -- NULL, "/" and "" */
if (argv0 != 0 && *argv0 != '\0' && (*argv0 != '/' || *(argv0 + 1) != '\0'))
{
const char *cp;
size_t nbytes = sizeof(arg0) - 1;
if ((cp = strrchr(argv0, '/')) == 0)
{
/* Basename of file only */
cp = argv0;
}
else if (*(cp + 1) != '\0')
{
/* Regular pathname containing slashes but not trailing slashes */
cp++;
}
else
{
/* Skip backwards over trailing slashes */
const char *ep = cp;
while (ep > argv0 && *ep == '/')
ep--;
/* Skip backwards over non-slashes */
cp = ep;
while (cp > argv0 && *cp != '/')
cp--;
assert(ep >= cp);
cp++;
nbytes = (size_t)(ep - cp) + 1;
if (nbytes > sizeof(arg0) - 1)
nbytes = sizeof(arg0) - 1;
}
strncpy(arg0, cp, nbytes);
arg0[nbytes] = '\0';
}
}
const char *(err_rcs_string)(const char *s2, char *buffer, size_t buflen)
{
const char *src = s2;
char *dst = buffer;
char *end = buffer + buflen - 1;
/*
** Bother RCS! We've probably been given something like:
** "$Revision: 9.6 $ ($Date: 2009/03/02 20:27:38 $)"
** We only want to emit "7.5 (2001/08/11 06:25:48)".
** Skip the components between '$' and ': ', copy up to ' $',
** repeating as necessary. And we have to test for overflow!
** Also work with the unexpanded forms of keywords ($Keyword$).
** Never needed this with SCCS!
*/
while (*src != '\0' && dst < end)
{
while (*src != '\0' && *src != '$')
{
*dst++ = *src++;
if (dst >= end)
break;
}
if (*src == '$')
src++;
while (*src != '\0' && *src != ':' && *src != '$')
src++;
if (*src == '\0')
break;
if (*src == '$')
{
/* Unexpanded keyword '$Keyword$' notation */
src++;
continue;
}
if (*src == ':')
src++;
if (*src == ' ')
src++;
while (*src != '\0' && *src != '$')
{
/* Map / in 2009/02/15 to dash */
/* Heuristic - maps slashes surrounded by digits to dashes */
char c = *src++;
if (c == '/' && isdigit(*src) && isdigit(*(src-2)))
c = '-';
*dst++ = c;
if (dst >= end)
break;
}
if (*src == '$')
{
if (*(dst-1) == ' ')
dst--;
src++;
}
}
*dst = '\0';
return(buffer);
}
/* Format a time string for now (using ISO8601 format) */
/* Allow for future settable time format with tm_format */
static char *err_time(char *buffer, size_t buflen)
{
time_t now;
struct tm *tp;
now = time((time_t *)0);
tp = localtime(&now);
strftime(buffer, buflen, tm_format, tp);
return(buffer);
}
/* Most fundamental (and flexible) error message printing routine - always returns */
static
At least for Ubuntu (12.04 and later, to my certain knowledge), there's an errno utility you can easily install via apt-get install moreutils. (Thanks to #kevinoid and #leo for the update.)
$ errno 98
EADDRINUSE 98 Address already in use
$ errno EINVAL
EINVAL 22 Invalid argument
This works on Ubuntu 9.04:
user#host:~$ grep EINVAL /usr/include/asm-generic/errno*.h
/usr/include/asm-generic/errno-base.h:#define EINVAL 22 /* Invalid argument */
You can also try a Python script:
import errno
from os import strerror
from sys import argv
print strerror(errno.__dict__[argv[1]]
The function
strerror()
Is possibly what you're looking for, but I don't know of a command that exposes that to any shell offhand.
MKS exposes the command line strerror
Tried
grep EINVAL /usr/include/sys/errno.h
and seen what comes back?
#! /bin/bash -f
errorDir="/usr/include/asm-generic"
strError="$1"
numericVal=awk -v pat="$strError" '$0 ~ pat{print $3}' $errorDir/errno-base.h $errorDir/errno.h
perror $numericVal
Caution: As this script uses the location of ERROR MACROS,this might not be portable although it works on my system.
Rob Wells is partially correct. Unfortunately /usr/include/asm/errno.h is nonstandard. You really need to grep /usr/include/errno.h and /usr/include/*/errno.h.
To make this errorcommand, try adding this to your .bashrc file:
function errorcommand
{
grep "${1}" /usr/include/errno.h /usr/include/*/errno.h
}
Which works like this:
Rob Wells is partially correct. Unfortunately /usr/include/asm/errno.h is nonstandard. You really need to grep /usr/include/errno.h and /usr/include/*/errno.h.
To make this errorcommand, try adding this to your .bashrc file:
function errorcommand
{
grep "${1}" /usr/include/errno.h /usr/include/*/errno.h
}
Which works like this:
$ errorcommand EINV
/usr/include/sys/errno.h:#define EINVAL 22 /* Invalid argument */
$
A compact bash script that exactly does what you want:
#!/bin/bash -f
file="/tmp/prog$$.c"
out="/tmp/prog$$"
if [ $# -ne 1 ]
then
echo "Usage: $0 ERROR-NO"
exit 1
fi
echo "#include <stdio.h>" >> $file
echo "#include <errno.h>" >> $file
echo "int main(){" >> $file
echo "printf(\"$1:%s\n\",strerror($1));" >> $file
echo "}" >> $file
gcc $file -o $out &> /dev/null
if [ $? == "0" ]
then
$out
rm -f $out
else
echo "Syntax Error: $1 Unknown"
fi
# cleanup the file
rm -f $file
On my corporate box /usr/include wasn't available. So I put this portable simple solution (if you have Python) into my init files. You can torture it into a one-liner if you wish:
function strerror () {
python -c "import os, errno; print(os.strerror(errno.$1))"
}
There's no standard utility to do this. I believe your best bet is to write such a utility yourself. Use strerror() to print the associated error message.
For people that want a quick, on-liner:
find /usr/include/ -name errno*.h -exec grep ERRNO {} +
e.g.
[x#localhost]$ find /usr/include/ -name errno*.h -exec grep EINVAL {} +
/usr/include/asm-generic/errno-base.h:#define EINVAL 22 /* Invalid argument */
[x#localhost]$ find /usr/include/ -name errno*.h -exec grep 111 {} +
/usr/include/asm-generic/errno.h:#define ECONNREFUSED 111 /* Connection refused */
Here is a short C++ program that handles both numeric and symbolic errors. Given the option -l as its very first argument, it lists all known error symbols and then exits. Otherwise iterates over all arguments and prints the error text, and nothing else, for each argument.
There is nothing much here that has not been mentioned earlier, but it does it all in a single source file, not counting table.h, which is generated.
To build:
./table.sh > table.h
g++ -O2 -Wall -W -Wextra -o errno errno.cc
The program is in C, except for the C++ iteration in list_and_exit().
errno.cc
# include <string.h>
# include <stdio.h>
# include <stdlib.h>
# include <errno.h>
# include <ctype.h>
struct ErrSym {
char const *name;
unsigned char value; // 0..255
};
static ErrSym const es[] = {
# include "table.h"
};
static int cmp(char const *a, ErrSym const *b) {
return strcmp(a, b->name);
}
static ErrSym const *find_by_name(char *needle) {
typedef int (*IVV)(void const *, void const *);
static int const n = (sizeof es / sizeof *es);
return (ErrSym*)bsearch(needle, es, n,
sizeof *es, (IVV)cmp);
}
static void list_and_exit() {
for (auto &e : es)
printf("%3d %-15s %s\n",
e.value, e.name, strerror(e.value));
exit(0);
}
static void handle_arg(char *arg) {
if (isdigit(arg[0]))
printf("%s\n", strerror(atoi(arg)));
else if (ErrSym const *p = find_by_name(arg))
printf("%s\n", strerror(p->value));
else
printf("Unknown error symbol: %s\n", arg);
}
int main(int argc, char *argv[]) {
if (argc > 1 && 0 == strcmp("-l", argv[1]))
list_and_exit();
for (int i = 1; i < argc; i++)
handle_arg(argv[i]);
return 0;
}
With the assumption that error numbers are less than 256, ErrSym.value is defined as unsigned char, so that the compiler can warn about values that are out of range.
To generate table.h, the trick (as mentioned in a comment above) is to use the C compiler and the preprocessor.
table.sh
#!/bin/bash
trap 'rm -f tmp.c' EXIT
echo '#include <errno.h>' > tmp.c
#
# -E Run the preprocessor only
# -dM At end, dump preprocessor symbols
# According to documentation, but not tested, these
# options should work also with clang and Intel's icc.
gcc -E -dM tmp.c | ./table.pl | sort
table.pl
#!/usr/bin/perl -n
#
# Convert '#define EACCES 13' to '{ "EACCES", 13 },'
# Skip lines that do not match
#
# By happy concidence, all names beginning
# with E are error numbers
#
next unless (/^#define +(E\S+) +(\S+)/);
$_ = sprintf("{%-18s %10s},\n", qq{"$1",}, $2);
s/, /, /; # Remove excess whitespace
print;
The output from errno -l is sorted by error symbol. Say errno -l | sort to sort by error number.

Resources