Skip to content

Instantly share code, notes, and snippets.

@TheMatt2
Last active June 29, 2025 00:07
Show Gist options
  • Save TheMatt2/414b3f0ad56cd231971361e68e8db8c2 to your computer and use it in GitHub Desktop.
Save TheMatt2/414b3f0ad56cd231971361e68e8db8c2 to your computer and use it in GitHub Desktop.
import re
import os
import csv
import sys
import textwrap
import platform
import subprocess
from io import StringIO
from colorama.ansitowin32 import AnsiToWin32 as AnsiToText
class AnsiToText:
def __init__(self, stream):
self.stream = stream
def write(self, text):
# Implement '\x08' to delete prior character.
while text:
index = text.find('\x08')
if index != -1:
leading, text = text[:index - 1], text[index + 1:]
else:
leading, text = text, ''
# Replace NSBP No Break Space.
#leading = leading.replace('\xa0', ' ')
# Replace Ascii-like Unicode
leading = leading.replace('\u2010', '-')
leading = leading.replace('\u2011', '-')
leading = leading.replace('\u2012', '-')
leading = leading.replace('\u2013', '-')
leading = leading.replace('\u2014', '-')
leading = leading.replace('\u2015', '-')
leading = leading.replace('\u2018', "'")
leading = leading.replace('\u2019', "'")
leading = leading.replace('\u201c', '"')
leading = leading.replace('\u201d', '"')
leading = leading.replace('\u2022', "*")
leading = leading.replace('\u2192', '->')
leading = leading.replace('\u2500', '-')
leading = leading.replace('\u2501', '-')
leading = leading.replace('\u2502', '|')
leading = leading.replace('\u250f', '|')
leading = leading.replace('\u250c', '|')
leading = leading.replace('\u2510', '|')
leading = leading.replace('\u2514', '|')
leading = leading.replace('\u2518', '|')
leading = leading.replace('\u251c', '|')
leading = leading.replace('\u2524', '|')
leading = leading.replace('\u252c', '|')
leading = leading.replace('\u252f', '|')
leading = leading.replace('\u2534', '|')
leading = leading.replace('\u253c', '|')
leading = leading.replace('\u2577', '.')
leading = leading.replace('\u27e8', '(')
leading = leading.replace('\u27e9', ')')
for c in leading:
if c in "\n\xa0": continue
if not c.isprintable() or not c.isascii():
print(repr(c))
assert 0
self.stream.write(leading)
def print_list(items, columns=4, width=80, file = None):
if not file:
file = sys.stdout
items = list(sorted(items))
colw = width // columns
rows = (len(items) + columns - 1) // columns
for row in range(rows):
for col in range(columns):
i = col * rows + row
if i < len(items):
file.write(items[i])
if col < columns - 1:
file.write(' ' + ' ' * (colw - 1 - len(items[i])))
file.write('\n')
def get_error_codes():
man_page = subprocess.check_output(["man", "errno"]).decode()
### Strip the ANSI code formatting
##man_stripped = StringIO()
##converter = AnsiToText(man_stripped, convert = True, strip = True)
##converter.write(man_page)
##
##print(repr(man_stripped.getvalue()[:100]))
##
##print(man_stripped.getvalue() == man_page)
man_stripped = StringIO()
converter = AnsiToText(man_stripped)
converter.write(man_page)
man_stripped = man_stripped.getvalue()
# Pull diagnostics section
diagnostics_index = man_stripped.find("DIAGNOSTICS")
if diagnostics_index == -1:
diagnostics_index = man_stripped.index("List of error names")
diagnostics = man_stripped[diagnostics_index:]
error_codes = []
matches = re.finditer(
r"^\s+(?P<errnum>\d*)\s*(?P<errname>E[A-Z]+)(?P<errdesc>.+?)\n\n",
diagnostics, re.MULTILINE | re.DOTALL)
for match in matches:
errnum, errname, errdesc = match.groups()
if errnum:
errnum = int(errnum)
else:
errnum = -1
if errnum == 0: continue
errdesc = " ".join(errdesc.split())
error_codes.append((errnum, errname, errdesc))
print(errnum, errname, textwrap.shorten(errdesc, 40, placeholder = "..."))
return error_codes
def list_commands():
# List all man pages
# https://unix.stackexchange.com/questions/44329/how-do-you-output-a-list-of-all-man-pages-in-a-particular-section
# Search man page section 2
commands = []
if platform.system() == "Darwin":
man_pages = subprocess.check_output(["man", "-aWS", "2", "*"]).decode()
for page in man_pages.split("\n"):
command = os.path.basename(page).split('.', 1)[0]
# "intro" is the introduction man page with general information
if command and command != "intro":
commands.append(command)
elif platform.system() == "Linux":
man_pages = subprocess.check_output(["man", "-k", ".", "-s", "2"]).decode()
for page in man_pages.split("\n"):
# Manual includes unimplemented function. Meaning they may be
# defined, but the associated functionality is not supported. Ignore.
if "unimplemented" in page.lower():
continue
command = page.split(' ', 1)[0]
# "intro" is the introduction man page with general information
if command and command != "intro":
commands.append(command)
commands = sorted(set(commands), key = lambda x: x.lower())
print_list(commands)
return commands
# :ook up its man page and its possible error return codes
def command_error_codes(command, global_error_codes):
man_page = subprocess.check_output(["man", "2", command]).decode()
man_stripped = StringIO()
converter = AnsiToText(man_stripped)
converter.write(man_page)
man_stripped = man_stripped.getvalue()
# Pull error section
errors_index = man_stripped.find("ERRORS")
if errors_index != -1:
errors = man_stripped[errors_index:]
else:
errors = ""
errors_index = 0
# Find Error numbers
matches = re.finditer(
r"^\s+\[?(?P<errname>E[a-zA-Z]+)\]?(?P<errdesc>.+?)\n\n",
errors, re.MULTILINE | re.DOTALL)
error_codes = []
start = len(man_stripped)
end = 0
for match in matches:
errname, errdesc = match.groups()
if errname == "EXAMPLES":
# Not actually an errno
continue
start = min(start, match.start())
end = max(end, match.end())
errdesc = " ".join(errdesc.split())
error_codes.append((errname, errdesc))
print(command, errname, textwrap.shorten(errdesc, 40, placeholder = "..."))
# Check if errno mentioned outside scanned region
for _, errname, _ in global_error_codes:
errname_index = man_stripped[:errors_index + start].find(errname)
if errname_index == -1:
errname_index = man_stripped[errors_index + end:].find(errname)
if errname_index != -1:
desc = man_stripped[errname_index - 20: errname_index + 20]
desc = " ".join(desc.split())
print("Name found outside error section in", command,
": ...", desc, "...")
return error_codes
def main():
error_codes = get_error_codes()
error_names = []
error_codes_map = {}
for errnum, errname, errdesc in error_codes:
error_names.append(errname)
error_codes_map[errname] = errnum, errdesc
commands = list_commands()
command_map = {}
for command in commands:
command_map[command] = command_error_codes(command, error_codes)
print("Found", len(commands), "commands")
print("Found", len(error_codes), "error codes")
# Write csv file data
with open("all_errnos.csv", "w") as f:
writer = csv.writer(f)
# Write headers
writer.writerow(["System Function"] + error_names)
writer.writerow(["Error Number"] + [str(error_codes_map[errname][0]) for errname in error_names])
writer.writerow(["Error Description"] + [error_codes_map[errname][1] for errname in error_names])
for command in command_map:
command_error_desc_map = {}
for errname, errdesc in command_map[command]:
command_error_desc_map.setdefault(errname, [])
command_error_desc_map[errname].append(errdesc)
command_error_rows = [command]
for errname in error_names:
descriptions = command_error_desc_map.get(errname, [])
if len(descriptions) == 1:
combined_descr = descriptions[0]
else:
combined_descr = ""
for i, descr in enumerate(descriptions):
combined_descr += f"{i + 1}. {descr}\n"
combined_descr = combined_descr.strip()
command_error_rows.append(combined_descr)
writer.writerow(command_error_rows)
if __name__ == "__main__":
main()
// #include <stdio.h>
// #define XSTR(x) STR(x)
// #define STR(x) #x
// #pragma message "GLIBC " XSTR(__GLIBC__) "." XSTR(__GLIBC_MINOR__)
// strerror_.h
#ifndef STRERROR_H
#define STRERROR_H
/**
* @brief Portable thread safe strerror()
*
* A version of strerror() guaranteed to be thread safe
* and meant to be portable across multiple platforms.
*
* The returned string may be overwritten after future calls to strerror_()
* from the same thread.
*
* Thread safe. Not async safe.
*
* Behavior is undefined if locale is set to something other than LC_ALL.
*
* @param errnum Errno number to make a user generated string for.
* @returns Pointer to const string representing the error.
*/
#if __STDC_VERSION__ >= 202311L
#define NO_DISCARD [[ nodiscard ]]
#elif defined(__GNUC__)
#define NO_DISCARD __attribute__ ((warn_unused_result))
#else
#define NO_DISCARD /* nothing */
#endif
NO_DISCARD const char* strerror_(int errnum);
#endif /* STRERROR_H */
#include <errno.h>
#include <stdio.h>
#include <string.h>
/* strerror() is MT-safe for glibc >= 2.32 */
#if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 32
#ifndef HAS_STRERROR_MT_SAFE
#define HAS_STRERROR_MT_SAFE 1
#endif
#endif /* glibc >= 2.32 */
/* printf("%m") is defacto MT-safe for glibc >= 1.06 */
#if __GLIBC__ >= 1 && __GLIBC_MINOR__ >= 6
#ifndef HAS_PRINTF_M
#define HAS_PRINTF_M 1
#endif
#endif /* glibc >= 1.06 */
/* Define thread_local for thread specific buffer */
#if __STDC_VERSION__ <= 199901L
#define thread_local __thread
#elif __STDC_VERSION__ < 202311L
// C23 changes thread_local to be a keyword
#define thread_local _Thread_local
#endif
#if HAS_STRERROR_MT_SAFE
// Just call strerror() when safe.
const char* strerror_(int errnum)
{
return strerror(errnum);
}
#elif HAS_PRINTF_M
// Use printf("%m") to get errno string
// Pedantic warnings warns about usage of %m, but this code already checks
// %m is only used if available.
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat"
#endif /* __GNUC__ */
const char* strerror_(int errnum)
{
// Thread local buffer for errors.
// Longest EN error message on linux is EILSEQ is 49 characters + null.
static thread_local char errbuf[50] = {0};
int prev_errno = errno;
errno = errnum;
snprintf(errbuf, sizeof(errbuf) - 1, "%m");
errno = prev_errno;
return errbuf;
}
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif /* __GNUC__ */
#else
// POSIX General Solution.
#include <pthread.h>
const char* strerror_(int errnum)
{
// Thread local buffer for errors.
// Longest EN error message on linux is EILSEQ is 49 characters + null.
static thread_local char errbuf[50] = {0};
const char *buf;
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mutex);
// Call strerror() to get error status
buf = strerror(errnum);
// Copy to a thread specific buffer. Always leave null terminator.
buf = strncpy(errbuf, buf, sizeof(errbuf) - 1);
pthread_mutex_unlock(&mutex);
return buf;
}
#endif
int main(int argc, char *argv[])
{
(void) argc;
(void) argv;
printf(" (0): %s\n", strerror_(0));
printf("EBADF : %s\n", strerror_(EBADF));
printf("EILSEQ: %s\n", strerror_(EILSEQ));
printf(" (999): %s\n", strerror_(999));
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment