Linux and Unix system programming
Table of Contents
- 1. Unix System Programming focused on Linux
- 1.1. Overview
- 1.2. Command Line Essentials
- 1.3. Widely used functions for strings and buffers in low level codes
- 1.4. Map of user-space APIs of Unix-like Operating Systems
- 1.5. Creating Portable Binaries for Linux / GLIBC Dependency Hell
- 1.6. Fully Statically linked executables for embedded Linux systems
- 1.7. Creating command line applications with CLI11 C++ Library
- 1.8. System information via virtual file systems
- 1.9. Disk Space Usage on Linux and BSD-variants
- 1.10. Low level open, read, write, close - IO sycalls overview
- 1.11. Low level IO - read() syscall
- 1.12. Low level IO - open(), read(), write() syscall functions
- 1.13. Low level IO - creat()
- 1.14. Reading/Listings directory contents
- 1.15. Process Inputs, Outputs and States and PROCFS on Linux
- 1.16. Information about file permissions, owner and group - fstat and stat syscalls
- 1.17. Information about current process
- 1.18. Dynamic Loading Shared Libraries / dlopen() API
- 1.19. Library calls hooking/interception with LD_PRELOAD
- 1.20. Launching processes with exec and fork system-call wrappers
- 1.21. Reading subprocess output via Popen()
- 1.22. Unix Pipes - Inter Process Communication
- 1.23. Monitor file system changes with Inotify API
- 1.24. mmap - Memory Mapping
- 1.25. POSIX Shared Memory
- 1.26. memfd_create syscall - Loading from memory
- 1.27. Syslog - System Message Logging
- 1.28. Sockets - TCP/IP and Unix Domain Sockets
- 1.28.1. Overview
- 1.28.2. BSD Socket API
- 1.28.3. Network Programming Pitfalls [DRAFT]
- 1.28.4. Command Line Tools for Network Debugging
- 1.28.5. Simple TCP/IP client socket
- 1.28.6. Simple TCP/IP socket echo server
- 1.28.7. Simple Unix-domain socket echo server
- 1.28.8. Telnet-like application
- 1.28.9. Telnet-like application with pseudo-terminal devices
- 1.29. Sockets with SSL/TSL - Secure Communication
- 1.30. Sockets with Multiplexed IO APIs (poll, epoll) [DRAFT]
- 1.31. Ptrace - system call
- 1.32. X11 - X Windows System
1 Unix System Programming focused on Linux
1.1 Overview
1.1.1 Unix-like Operating Systems
Unix-like operating system are a family of operating system based on the AT&T Unix operating system, developed in the 1970's by Ken Thompson, Dennis Ritchie and others. Later AT&T licensed Unix to third-party vendors, what lead to many proprietary Unix variants.
Some Unix-like operating systems are
Desktop or Server
- Linux-based or GNU-Linux based operating systems. (Note: Linux is
NOT an operating system, it is just the kernel.) Although, the
kernel is an essential component of an O.S., it is useless on its
own, without all the additional supporting software such as: the
GNU userland tools, bootloaders (Grub, Syslinux, Das U-boot),
X11 - Windows Systems and so on. Some GNU-Linux operating systems
are: Debian Linux; Ubuntu Linux; Fedora Linux; Centos Linux;
Gentoo Linux; Arch Linux and more.
- Somes places where Linux-based operating system is found:
- Data centers
- Most cloud virtualized servers.
- Super Computers for HPC - High Performance Computing
- HPC clusters - High Performance Computing cluster
- Some embedded systems with network connectivity such as: high end routers - wifi access point; security cameras; payment terminals; network printers and so on.
- Billions of mobile devices since Andoid is also based on Linux Kernel.
- Desktop computers of enthusiasts.
- Advantages:
- Largest server and cloud virtual machines install-base.
- Best hardware support among Unix-like operating systems.
- Largest open source community.
- Good support from embedded systems vendors.
- Transparency: the system exposes many of its internals as virtual-file systems, which only exist on the main memory (volatile memory).
- Stable and well documented system-calls.
- The kernel receives significant contribution from many large companies, such as Intel, AMD, Nvidia, Red Hat, IBM, Google, Linar, Samsung and so on.
- Drawbacks:
- GLIBC - The GLIBC, which that encapsulates kernel system calls and implements basic C functions, such as memcpy and strcpy, which is relied by almost all applications, does not expose all kernel system call. Other significant problem, is the lack of backward compatibility as GLIBC has breaking changes on every release, as a result native code applications or native executables may not work when moved or copied to other Linux machines.
- Lack of C API for many functionalities => Unlike other Unix-like operating systems and BSD-variants, Linux kernel lacks user-space C APIs (subroutines) for many of its functionalities. Instead of exposing these facilities as C APIs through the GlibC library, the kernel exposes them as virtual-file systems. For instance, instead of providing an user-space API (C subroutine) for listing all processes, the kernel exposes all processes via /proc virtual file system, which allows user-space applications to query process information just by reading and parsing files. Besides process information API, the Cgroups functionality also lcaks a user-space C API, instead it only has a virtual file system API.
- Fragmentation among Linux distributions which makes harder to develop software for Linux-based systems as every distribution has different versions of GLIBC, different versions of shared libraries and different package managers. The fragmentation could be reduced if Linux distributions could share binaries without recompilation and applications were developed in a self-contained way.
- Dependency hell: On most Linux distributions, it is only possible to install a single version of some application from package managers. Applications are also installed in a scattered way on many directories, for instance executables on /bin, /usr/bin, libraries on /lib, /lib64 and so on. If one install an application that relies on shared libraries newer from the ones installed on the system and the older libraries are overriden, applications relying on the older libraries may no longer work as the symbols which they need may no longer be available on the newer libraries.
- Somes places where Linux-based operating system is found:
- Mac OSX / Apple Inc. (Desktop-only) [BSD Variant]
- Most successful and widespread Unix-like operating system on Desktop. MacOSX is based on NeXTSETEP operating system, which is derived from BSD Unix (BSD - Berkley Software Distribution) and Mach Kernel, created at Carnegie Mellon University. Even though the graphics stack is built on top of Objective-C, MacOSX still shares a great deal of user-space APIs (Application Programming Interfaces) with BSD derived operating systems, such as: basic Unix system calls, open(), read(), write(); KQueue and poll() io multiplexing; pthread - POSIX threads and so on.
- Advantages:
- One of the best graphical user interfaces among Unix-like operating systems; consistent UX - user experience; high level GUI graphical user interface stack, that does not rely on X11. Unlike other unix-like, which applications are installed with files scattered in many different directories such as /bin, /lib, /usr/lib and so on, MacOSX uses self-contained applications app bundles, which are directories ending with .app suffix containing the application and executables and all dependencies in a single directory. App bundles allows installation by dragging and dropping its folder to the /Applications directory and uninstall by just removing the app bundle directory.
- Drawbacks:
- Proprietary.
- Operating system is bundled with the hardware. A potential user cannot choose his or her own hardware.
- Not allowed to be virtualized on the cloud on non-Apple hardware.
- Little care about backward and forward compatibility, applications that works today, may not work tomorrow without active maintenance as the operating system vendor is not afraid of breaking backward compatibility.
- FreeBSD
- Unix-style operating system based on Unix BSD (Berkeley Software Distribution). Unlike Linux, the FreeBSD is a full-featured operating system, with almost everything maintained by the same team. Some noteworthy features of FreeBSD are: BSD permissive license; the high quality network stack that still outmatches Linux one, for instance BSD was used by Whatsapp, Yahoo and Netflix; PF - Firewall - Packet Filter firewall; ZFS highly reliable and scalable file system; kqueue scalable IO multiplexing API; full-featured documentation; Ports collection; BSD Jails sandboxing which is similar to Docker and other Linux containers; bhyve - hypervisor for visualization; own userland tooling; Linux Emulation - FreeBSD is able to run Linux native executables without recompilation via system call emulation; more cohesive and less fragmented than Linux-systems as everything is almost maintained by the same team.
- Potential Problems: less hardware support than GNU/Linux systems; smaller community; less corporate support and less developers than Linux, for instance Linux Kernel often receives signifcant contribution from large companies such as Red Hat, IBM, Intel, Nvidia, Qualcom and so on; SVN version control system (note: FreeBDS is slowly moving to GIT). The hardware compatibility drawback that affects Desktop is not significant for usage of FreeBDS on server machines or on cloud virtual machines.
- See:
- OpenBSD - Similar to FreeBSD, but with security-aware design.
- NetBSD
- DragonFly BSD
- Minix - Open source small operating system created for teaching about operating system concepts and designed to be understandable by a single person.
- OpenIndiana - Derived from Illumos operating system, which is a fork of Sun Microsystem's Open Solaris.
- Redox-OS (Written in Rust language) - "Redox is a Unix-like
Operating System written in Rust, aiming to bring the innovations
of Rust to a modern microkernel and full set of applications."
- Unlike, Linux and other Unix-like flavours, Redox-OS has its own graphical user interface, which is not based on x11 - X Windows System.
- Dahlia OS (dahaliaOS) - Operating system based on GNU/Linux and Google's (Alphabet Inc) Fuchsia OS, which one of the main noteworthy feature is the capability-based security.
- Plan 9 (Bell Labs) [EXPERIMENTAL] - "Plan 9 from Bell Labs is a experimental operating system developed at Bell Labs at 1980s by Ken Thompson, Rob Pike, Dave Presotto, and Phil Winterbottom. Plan9 takes the concept of "everything is a file" to the next level. What sets Plan9 apart from other Unix variants is that "everything is almost a file", sockets are represented as files in PROCFS file system; GUI - graphical user interfaces are also represented as files.
- Inferno-OS (Plan-9 variant) [EXPERIMENTAL] - "Inferno® is a distributed operating system, originally developed at Bell Labs, but now developed and maintained by Vita Nuova® as Free Software. Applications written in Inferno's concurrent programming language, Limbo, are compiled to its portable virtual machine code (Dis), to run anywhere on a network in the portable environment that Inferno provides. Unusually, that environment looks and acts like a complete operating system. Inferno represents services and resrouces in a file-like name hiearchy. Programs access them using only the file operations open, read/write, and close. `Files' are not just stored data, but represent devices, network and protocol interfaces, dynamic data sources, and services."
Server or Mainframes (mostly discontinued)
- Solaris (from Sun Microsystems)
- z/OS / IBM - Unix-variant for mainframes.
- AIX / IBM
- HP-UX
- IRIX
- Xenix - Discontinued Microsoft Unix variant.
Mobile (touch devices)
- Android (Linux-based OS, but without GPL on the userland).
- Unlike Desktop Linux distributions, Android uses the C runtime library bionic, derived from BSD, instead of GLIBC. Other significant differences is that Android does not uses X11 (X Windows System) as graphical user interface, instead the operating system uses the framebuffer directly. Shell command line tools, such as mv, ls, cp, and others, come from toybox (BSD license), instead of busybox or other GNU tools.
- iOS / Apple
Embedded Systems
- Embedded-Linux
- Embedded Linux is not a single distribution or operating system. It is a highly customized Linux-based system comprised of many custom components, namely: bootloaders (das uboot, most popular); busybox or toybox as replacement for GNU coreutils (command line tolls: bash, ls, mv, …).
- Embedded Linux systems can be found in: network routers; network printers; many devices with web interface; security cameras; Amazon's Alexa; Mobile devices (Android can be regarded as an embedded Linux system) and on many other devices.
- QNX
- Minix
- NetBSD
- LynxOS
1.1.2 Common Features of Unix-like Operating Systems
Nowadays, there may be many difference between Unix-like operating systems, but they still have the following features in common:
- Command line shell
- In the early days of computing, the shell or command line interpreter was the primary means of interaction between users and computers. A unix shell is used both as a scripting language and as an interactive command line interpreter for user interaction, launching processes, launching daemons (aka services), manipulating files and controlling the operating system.
- Common Unix shells:
- Command Line Applications (Command-Line Centric)
- mv; cp; rm; mkdir; ls; telnet; ssh; … and so on.
- Terminal Emulation
- Early Unix-like operating systems only ran in big and expensive mainframe computers that were simultaneously shared by many users via physical dumb terminals attached through serial cables (RS232 - UART). The first terminals were teletype printers (tty) where users could type commands and the teletype printer would print the the command output in a paper type. Later, dumb CRT (Cathode-Ray Tube) terminals were adopted. Those machines were comprised of a keyboard attached to a CRT video display as single unit attached to a mainframe via a serial cable. Physical Terminals, such as the once popular DEC VT100, are long gone, but the are still emulated by unix-based operating systems. For instance, most of those systems provide the following types of terminal emulation:
- => Virtual console => Kernel built-in terminal emulator, which can be accessed without any graphical user interface by typing Ctrl + Alt + F1, Ctrl + Alt + F[N] on Linux. BSD has different keybinds for the virtual console. Note: Mac-OSX does not have virtual console.
- => Graphical Terminal Emulator => Example: Xterm (X11); Gnome Terminal; Terminator; iTerm (MacOSX).
- => Serial console => Allows accessing a command line shell (bash, for instance) through a serial cable (RS232 / UART). Serial console is still used for accessing servers or embedded systems.
- Multiple Simultaneous Users
- Most unix-based systems support multiple simultaneous user accounts through: serial terminal (serial RS232 cable); serial dumb terminals (mainframe heritage); telnet obsolete and unsafe; SSH (Secure Shell Server) protocol over the internet or intranet or X11 (X Windows Systems) for graphical remote access. Most editions of Windows NT operating systems also support remote access via remote desktop, but do not support multiple simultaneous users like Unix systems, this feature is only available on Windows NT server edition.
- Text-centric
- Unix-like operating system has the tradition of storing configuration in human-readable text files, instead of binary files as Windows does. The benefits of using text files are the better reusability, searchability and reproducibility of configurations. All that one needs to reproduce the configuration in another machine is to copy the configuration file.
- Many applications also use text for configuration instead of graphical user interfaces.
- Examples:
- Linux-based operating system store many of their configuration as text files in the '/etc' directory. For instance, the file '/etc/fstab' controls the disk partitions mounted during boot time. As opposite to Unix-tradition, Windows and most of its applications store configuration in the registry file which is a binary file.
- Applications such as bash, vi, emacs store configuration in text files placed in default locations which are read during the initialization.
- POSIX Features / POSIX - (Portable Operating System Interface) standard
- POSIX System Calls
- open(), read(), close() …
- POSIX Functions encapsulating system calls
- open(), read(), close(), mmap(), dlopen(), dlclose() …
- POSIX Threads (_PThreads_) - Thread C API
- POSIX Shells
- POSIX System Calls
- BSD TCP/IP Sockets => The TCP/IP network technology stack was first implemented on Unix BSD (Berkley Software Distribution) and later became the reference implementation for network stacks.
- Hierarchical file system
- File system with paths like: '/' (root directory); '.' (dot) as the current directory; '/home/user/data' (Linux); '/home/Users/somthing' (MacOSX) and so on.
- Symbolic Link (Symlink)
- Allows file and directories to be accessed from other directories as they were in that location.
- Virtual File Systems
- Many unix-like operating system provide virtual file-system (in-memory file systems) which exposes kernel and system information to user-space applications as human-readable text files. For instance, Linux has the PROCFS (/proc directory) file system which exposes information about all the running processes. PROCFS also has the file /proc/cpuinfo which contains information about the current machine microprocessor, such as: CPU cores; number of hyper threads; CPU features and capabilities.
- Everything is a file (or better everything is a file descriptor)
- Many operating such as reading/writing sockets, pipes and so on, use the same read/write system-calls for file descriptors.
- Hardware represented as device-files
- Kernel exposes information as text files (Linux)
- Disk partitions are represented as files /dev/sda, /dev/sdb1, /dev/sdb2 …
- see: file descriptor
- Device Files (a.k.a device nodes)
- Device files are pseudo-files created in virtual file systems which allows user-space applications to interact with the hardware peripherals by using reading and writing system-calls or just by reading and writing files. This feature is particularly important for industrial and embedded system applications as it allows reading sensor data, such as ADC - Analog-To-Digital Converters or digital input, just by reading files and controlling actuators, such as motors, solenoids or valves just by writing to files.
- Note A: On Linux-based, operating systems, most device files are in the virtual file systems devfs (/dev) and and sysfs (/sys) directory.
- Note B: Not all device files are associated to real hardware devices. The device-files /dev/null, /dev/zero, /dev/random, /dev/urandom, /dev/kmsg, /dev/tty0 are not associated to any real hardware.
- Example 1: It allows user-space application to read and write from/to serial port devices by just reading or writing to the device file /dev/ttyS1, /dev/ttyUSB0, …
- Example 2: In single-board computers SBCs (embedded systems) such as Beagle Bone Black or Raspberry PI, both which contains SOC (System-On-Chip) embedded processors, it is possible to control GPIOs (General Purpose IO) which are digital IOs, and consequently any device attached to them, such as lights, LEDs, motors or valves, just by writing to the corresponding GPIO device file (/sys/class/gpio/gpio10/value). By writing 1 to the this file, the LED is turned on, by writing 0, the LED is turned off. The device-file feature allows controlling the hardware with any programming language, including, shell scripts, python, ruby, standard C++ (without volatile and embedded bare-metal restrictions) and so on.
- Common Linux Device Files associated to hardware
- /dev/mem
- => Device file that represents the physical RAM memory. It can be used for reading MMIO (Memory-Mapped IO) devices.
- /dev/lp0, /dev/lp1 => Parallel ports
- /dev/ttyS0, /dev/ttyS1, … => Serial Ports
- /dev/ttyUSB0, /dev/ttyUSB1, … => USB-to-serial converters such as FTDI
- /dev/sys/class/gpio/gpio10/export => GPIO / General Purpose Digital IO
- /dev/sys/class/gpio/gpio10/value
- … … …
- /dev/input/mice => Binary file contains mouse position, mouse buttons and and so on. The mouse position can be obtained by reading this file, without any special API.
- /dev/fb0, /dev/fb1 … => Framebuffer - allows user-space applications to control the graphics card and draw on the screen by just writing to a file. The framebuffer is the lowest level GUI in Linux kernel. Most embedded linux applications draw directly on framebuffer without X11 (X-Windows System).
- /dev/mem
- Linux Device Files not associated to hardware:
- /dev/kmsg
- => charavter device file containing kernel messages. The file is read by the command line tool '$ dmesg'.
- /dev/stdout => Console stdout
- /dev/stdin => Console stdin
- /dev/stderr => Console stderr
- /dev/random => File which generates random numbers
- /dev/urandom => File which generates random numbers
- /dev/null => File used for discarding stdout or stderr of command line applications.
- /dev/zero => Generate zero-values
- /dev/null
- /dev/kmsg
Quotes
- Ken Thompson
I think the major good idea in Unix was its clean and simple interface: open, close, read, and write.
- Doug Mcllroy - Creator of Unix Pipes
This is the Unix philosophy: Write programs that do one thing and do it well. Write programs to work together. Write programs to handle text streams, because that is a universal interface.
- Brian Kernighan
Unix has, I think for many years, had a reputation as being difficult to learn and incomplete. Difficult to learn means that the set of shared conventions, and things that are assumed about the way it works, and the basic mechanisms, are just different from what they are in other systems.
- Rob Pike - Member of the Unix design team; creator of GO (Golang) and Plan-9 operating system.
Even though the UNIX system introduces a number of innovative programs and techniques, no single program or idea makes it work well. Instead, what makes it effective is the approach to programming, a philosophy of using the computer. Although that philosophy can't be written down in a single sentence, at its heart is the idea that the power of a system comes more from the relationships among programs than from the programs themselves. Many UNIX programs do quite trivial things in isolation, but, combined with other programs, become general and useful tools.
See also
Unix History:
- The Origin of the Shell - Multics
- GitHub - yvesnrb/Thompson-Shell
- Original source code for the Sixth Edition (V6) UNIX Thompson shell.
- GitHub - susam/tucl
- The first-ever paper on the Unix shell written by Ken Thompson in 1976 scanned, transcribed, and redistributed with permissio.
- THE UNIX COMMAND LANGUAGE | The UNIX Command Language (Ken Thompson)
- GitHub - dspinellis/unix-history-repo
- "The history and evolution of the Unix operating system is made available as a revision management repository, covering the period from its inception in 1970 as a 2.5 thousand line kernel and 26 commands, to 2018 as a widely-used 30 million line system. The 1.5GB repository contains about half a million commits and more than two thousand merges. The repository employs Git system for its storage and is hosted on GitHub. It has been created by synthesizing with custom software 24 snapshots of systems developed at Bell Labs, the University of California at Berkeley, and the 386BSD team, two legacy repositories, and the modern repository of the open source FreeBSD system. In total, about one thousand individual contributors are identified, the early ones through primary research. The data set can be used for empirical research in software engineering, information systems, and software archaeology."
- AT&T Archives: The UNIX Operating System
- UNIX: Making Computers Easier To Use -
- AT&T Archives film from 1982, Bell Laboratories
- LINUX's History by Linus Torvalds
- "What UNIX Cost Us" - Benno Rice (LCA 2020)
- "UNIX is a hell of a thing. From starting as a skunkworks project in Bell Labs to accidentally dominating the computer industry it's a huge part of the landscape that we work within. The thing is, was it the best thing we could have had? What could have been done better? Join me for a bit of meditation on what else existed then, what was gained, what was lost, and what could (and should) be re-learned."
- => Criticism of the mindset: "Everything is a file or a file descriptor. Not everything should be a file."
General Unix Concepts:
- Unix concept
- Book: The Art of Unix Programming - Eric Raymond
- 'The Art of Unix Programming attempts to capture the engineering wisdom and philosophy of the Unix community as it's applied today — not merely as it has been written down in the past, but as a living "special transmission, outside the scriptures" passed from guru to guru. Accordingly, the book doesn't focus so much on "what" as on "why", showing the connection between Unix philosophy and practice through case studies in widely available open-source software.'
- Unix-like operating systems
- POSIX - Portable Operating System Interface
- Unix philosophy
- Unix Shell Scripting
- Symbolic Link / symlink
- Pipeline, Pipes - IPC (Inter Process Communication)
- Unix File Descriptors
- File Descriptors
- Operating Systems - File System Implementation
Papers and mail lists:
- The UNIX Time Sharing System {PDF} - Dennis M. Ritchie and Ken Thompson - Bell Laboratories
- Files, file descriptors, and name space
- Linux kernel mail list discussion - Everything is a file
Usage of Linux Device Files for controlling hardware (reading Sensors and manipulating actuators):
- /dev/mem - memory - How to access physical addresses from user space in Linux? - Stack Overflow
- /dev/mem - EBC GPIO via mmap - eLinux.org
- /dev/mem - GitHub - tchebb/memdump: A simple /dev/mem dumper for Linux
- /dev/mem - kmem(4) - Linux manual page
- Who needs /dev/kmem? - LWN.net
- Difference between Device file and device drivers
- Introduction to device drivers and device nodes
- The hacker diary - exploring /dev/input => Shows how to read the device file /dev/input/mice which contains mouse information such as position and buttons state.
- How to Control GPIO Hardware from C or C++
- Note: It only works in PCs with industrial IO cards or systems with SOC processors (System-On-Chip) with GPIO devices; SBC - Single Board Computers, which are PCBs - printed circuit boards containin SOC microprocessors or MCU - Microcontrollers and all necessary supporting ICs (Integrated Circuits) such as RAM memory chips; flash memory chips; voltage regulators; USB connectors; …
- Linux Serial Ports Using C/C++
- How to open, read, and write from serial port in C?
- Raspberry Pi And The IoT In C - SYSFS The Linux Way To GPIO
Terminal Emulation and Serial Console
- A Brief History of Terminal Emulation
- DEC VT100 CRT Terminal
- Computer Terminal
- Linux Kernel Docs / Linux Serial Console
- Remote Serial Console HOWTO Prev / Chapter 1. Introduction
- Arch Linux / Working with the serial console
- Elinux - Serial Console
- Bootlin - Linux Serial Driver
- Configuring Serial Port Debug Console (Linux/U-Boot)
- Alpine Linux - Enable Serial Console
- Linux Serial Consoles for Servers and Clusters
- Roll Your Own Network / Serial Console (for servers, data-centers)
Plan 9 Operating System
- Plan 89 from Bell Labs [PAPER] - Ken Thompson, Rob Pike, Dave Presotto, and others.
- Unix, Plan 9 and the Lurking Smalltak [PAPER]
- Making Unix a little more Plan9-like
- Plan 9 - Gentoo Wiki
- Plan 9: The Way the Future Was - Chapter 20. Futures
- Plan 9 - from Bell Labs - Unix++ Anyone?
- The Plan-9 Effect or why you should not fix it if it ain't broken
- Glendix - A Plan 9/Linux Distribution
- Dr. David A. Eckhardt – Plan 9
- Plan 9: Not (Only) A better UNIX - Jim Huang
- Rc - The Plan 9 Shell
- Plan-9 from the outer space (Presentation, In Japanese)
- UNIX to Plan 9 command translation
- The Unix Spirit set Free: Plan 9 from Bell Labs
- Introduction to Plan 9
- Running Plan 9 Userland Tools On Unix
1.1.3 Components of Linux-based operating systems (Linux Distros)
Widely Used GNU and Kernel-Organization Tools
GNU tools are pervarsive on most Linux distributions either as essential command line utilities or building tools:
- GNU Toolchain (GNU Compilers and GDB Debugger)
- GDB => GNU Debugger
- GCC => C, ObjectiveC and C++ compiler
- GFortran => GNU Fortran Compiler
- Ada => GNU Ada compiler
- … …
- GNU Binutils - Collection of tools for manipulating object-files:
- ld => GNU linke
- nm => Shows symbols exported by object code file or shared library.
- as => GNU assembler
- objdump => Dump object file symbols, can be used for disassembly ELF object-files.
- c++-filt => Demangle (aka decode) C++ symbols
- readelf => Red information about ELF object-files.
- strings => List printable strings from any binary file.
- GNU Building System (GNU Autotools, GNU automake and Libtool)
- GNU Core Utils (coreutils) - GNU Core Utilities - Comprises essential Unix command line utilities such as: ls, cat, grep, less and so on.
- util-linux (Maintened by Linux Kernel Organization) => Set of
command applications not provided by GNU tools:
- $ mount => Command line application for mounting file systems
- $ fdisk => Command for creating and manipulating disk partitions
- $ getopt => Parse command line arguments
- $ cal => Prints calendar
- $ hexdump => Dump binary files in hexadecimal format.
- $ logger => Sends message to syslog
- $ chcpu => Configure CPUs in a multi-processor system.
- $ dmesg => Display kernel messages (content of device file /dev/kmsg)
- $ findmnt => Find mounted file systems.
- $ lsmode => List loaded kernel modules
- $ lsblk => List block devices (disks and partitions) and mount points.
- $ lsscsi => List SCSI devices, hard disks.
- $ lscpu => Display CPU information: architecture, operating mode, number of cores and so on. This information comes from the file /proc/cpuinfo
- $ lslocks => List local file system locks.
- $ lsusbs => List USB devices
- $ lspci => Show information about devices attached to PCI bus.
- $ lslogin => Display information about known users in the system.
- $ lsof => List open files.
- … and much more …
Components of a Linux-based Operating System (Generic IBM-PC Architecture)
- First-stage Boot Loader (Built-in, firmware)
- Second-stage Boot Loader => Loads the operating system
- Examples:
- Grub (Mostly used on Desktop)
- Syslinux (Mostly used for booting CDROM ISO images, Linux Live CDs)
- Examples:
- Linux Kernel (Written in C with proprietary GNU GCC compiler extension)
- One of the most important parts of GNU/Linux desktop and server distribution. The kernel provides many essential services that user-space applications take for granted such as: standardized and documented system-calls; process and thread scheduling; network stack; virtual memory management; virtual file systems; uniform interfaces for accessing hardware either as character device, block device or network device for network cards.
- CRT - C-Runtime Library
- Encapsulates kernel system-calls and basic services as C APIs for user-space applications and implements C functions required by C standards.
- Example:
- GLIBC (GNU C-library, most used C library by Linux Desktop and server distributions). Maintained by: FSF - Free Software Foundation.
- Bionic - CRT library used by Android.
- MUSL - CRT library used for embedded systems and static linking.
- User-space utilities
- Shell - command line interpreter.
- Basic Command Line Tools (ls, mv, pwd, ln, rm, file, …)
- GNU Coreutils
- Busybox => Is a single binary containing all command line tools as sub-commands. Busybox is widely used on Embedded-Linux systems.
- GUI - Graphical User Interface Stack (Not part of Kernel) - all
GUI applications are built on top of kernel framebuffer.
- X11 - X Windows System
- GTK - Gimp Toolkit
- QT
- Package Managers
- Examples:
- Dpkg (Debian)
- Aptitude (APT - Debian, Ubuntu)
- RPM Package Manager (Read-Hat Package Manager) => Used by Fedora.
- Pacman => Arch Linux Distribution
- Portage => Gentoo Distribution
- More: List of package managers
- Examples:
Components of a Embedded Linux System
- Cross Compiler Toolchain: Mostly GCC - GNU C Compiler
- Note: The Linux Kernel uses many C proprietary extensions of GCC compiler, therefore it is almost impossible to compile the kernel using any other compiler.
- Boot Loader => Loads the operating system
- Lk - Little Kernel (Android Bootloader)
- U-Boot (Most popular)
- Barebox
- RedBoot
- Yaboot
- Linux Kernel (GPL v2 License)
- Among other-things, the kernel provides: virtual memory management; processes; kernel-space (native) threads; kernel modules (device drivers) API and many other essential features.
- CRT - C Runtime Library
- uLibC
- MUSL
- Bionic (Android C library)
- GLIBC
- User-Space Utilities (Replacemente for GNU CoreUtils)
- busybox (GPL v2 License) - Single binary executable containing
many common unix command line utitlities in a single
executable, such as: unix shell, ls, mv, cp, rm, zip, unzip, … and so
on.
- "BusyBox combines tiny versions of many common UNIX utilities into a single small executable. It provides replacements for most of the utilities you usually find in GNU fileutils, shellutils, etc. The utilities in BusyBox generally have fewer options than their full-featured GNU cousins; however, the options that are included provide the expected functionality and behave very much like their GNU counterparts. BusyBox provides a fairly complete environment for any small or embedded system."
- toybox (BSD License) => Used on Android.
- "Toybox combines many common Linux command line utilities together into a single BSD-licensed executable. It's simple, small, fast, and reasonably standards-compliant (POSIX-2008 and LSB 4.1). Toybox's main goal is to make Android self-hosting by improving Android's command line utilities so it can build an installable Android Open Source Project image entirely from source under a stock Android system. After a talk at the 2013 Embedded Linux Conference explaining this plan (outline, video), Google merged toybox into AOSP and began shipping toybox in Android Mashmallow."
- busybox (GPL v2 License) - Single binary executable containing
many common unix command line utitlities in a single
executable, such as: unix shell, ls, mv, cp, rm, zip, unzip, … and so
on.
- BSP - Board Support Package
- Device drivers or kernel modules provided by SOC (System-On-Chip) manafucaturer. Most device drivers maps on-board peripherals, such as UART (serial communication device); GPIO - General Purporse IO; CAN - Controller Area Network or ADC - Analog-To-Digital Converter, to device files which allows user-space applications to control the hardware by just reading and writing files.
- The device-files feature allows using standard C or C++ for interacting the aforementioned peripherals and any physical devices attached to them such as pumps, electric motors, , valves, sensors, accelerometers or gyroscopes. The device-file feature also allows controling the hardware with any scripting language such as: ruby, python, unix shell script (bash, ash, sh, …) …
- GUI - Graphical User Interface
- Framebuffer - Linux Kernel Device file which allows user-space applications to control graphical display.
- In-house GUI on top framebuffer (Lowest level)
- SDL (LGPL) - http://www.libsdl.org/ - Library that came from Game Development.
- QT on top of framebuffer
- X11 - X Windows System (Rarely used, too heavy.)
See
- Direct FB - Elinux
- QT For Embedded Linux (QT Company)
- Qt for Embedded Linux Display Management
- Building a Linux system for the STM32MP1: enabling Qt5 for graphical applications - Bootlin's blog
- Choosing Embedded Graphical Libraries
- Top Five Libraries for creating GUI on Embedded Linux | by Kevin Muhuri | ITNEXT
1.2 Command Line Essentials
1.2.1 Overview
Useful reminder of common built-in command line applications for many unix-like operating systems, including Linux-distros; BSD-variants and MacOSX.
1.2.2 Commands summary table
The following table contains the commands for Linux and other Unix-like operating systems that may be useful for troubleshooting and debugging.
Command | Description | |
---|---|---|
System status | ||
$ uname -a | Display operating system, kernel version and uptime. | |
$ uptime | Tells how long the machine is running. | |
$ w | Show logged users and pseudo terminal sessions. | |
Current user | ||
$ whoami | Show current logged user. | |
$ id | Show current user ID and GID | |
$ groups | Show groups that the current user belongs to. | |
$ locale | Show user locale (local language settings) | |
$ sudo su | Log as root (shoud be avoided) | |
$ sudo <COMMAND> | Run command as root user. | |
Enviroment Variables | ||
$ env | Show all enviroment variables. | |
$ echo $PATH | Shows path environment variable | |
$ echo $HOME | Shows HOME enviroment variable, akin to Windows %USERPROFILE% | |
$ echo $TERM | Show type of terminal emulator, dumb, vt100, xterm, and so on. | |
Process management | ||
$ which <COMMAND> | Show path to given command or executable such as GCC. | |
$ ps -ef | List all running processes. | |
$ ps -aux | List all running processes | |
$ top | Terminal user interface for process monitoring | |
$ atop | Monitor CPU and IO performance | |
$ iotop | Monitor only disk IO speed and performance. | |
$ htop | Similar to htop, but with colors and more functionality. | |
$ kill <PROCESS-PID> | Terminate a process given its PID (unique identifier) | |
$ pgrep <PATTERN> –list-full | List all processes PIDs and paths that match a given pattern such as name. | |
$ pkill <PROCESS-NAME> | Terminate all processes with a given name | |
$ pkill nvim | Terminate all neovim processes (executable name is 'nvim') | |
Hardware information | ||
$ free -m | Shows the amount of RAM memory usage and remaining memory. | |
$ lscpu | Display information about machine CPUs or processor capabilities. | |
$ lspci | List all PCI card (network cards, video cards and so on) | |
$ lsusb | List all devices connected to USB. | |
$ lsmod | List all kernel loaded modules (aka device drivers) | |
$ hostnamectl | Show hostname, Machine-ID, Boot-ID, Operating system and architecture. | |
# demidecode | Show hardware serial numbers. | |
Network Interfaces | ||
$ ip addr | Display all network interfaces | |
$ ip route | Display default gateway and route. | |
$ ifconfig | Display all network interfaces, mac and IP addresses | |
$ iwconfig | Display only Wifi network interfaces. | |
$ arp -a | Shows ARP table. | |
# netstat -anp | ||
$ ping <HOSTNAME> | Check network connectivity with ping. | |
$ traceroute <HOSTNAME> | Traceroute checking for network connectivity. | |
# netstat -tulnp | Show all TCP/IP and TCP/UDP connections, ports and related processes. | |
# netstat -tulnp | grep -i listen | Show all TCP/IP and TCP/UDP listening (passive socket, server sockets). | |
$ ss | List all TCP/IP, TCP/UDP and unix domain sockets connections statistics. | |
$ ss –process –tcp –all | List all TCP/IP connections. | |
$ ss –process –tcp –listen | List only TCP/IP listening sockets and connections. | |
$ ss –process –udp –listen | List only UDP/IP listening sockets and connections. | |
$ ss –process –raw | List all raw sockets. The flag (–process) shows the process that socket belongs to. | |
$ ss dst <HOSTNAME> –process | List all connections related to a given hostname, for instance, 192.168.0.110 | |
$ ss -4 | Display only Ipv4 connections | |
$ ss -6 | Display only Ipv6 connections | |
Disks, partitions and mount-points | ||
$ df -H | Display used disk space of all partitions and VSFs | |
# fdisk -l | List all disks and partitions | |
$ blkid | List all disks (block devices), labels and their UUID | |
$ mount | List mout points. | |
$ mount | grep -i /dev/sd | List only disk mount points. | |
Systemd - Init system | ||
$ systemctl list-units | List systemd running units | |
$ systemclt –failed | List systemd failed units. | |
$ systemctl list-unit-files | List systemd unit files. | |
$ systemctl start <SERVICE> | Start a daemon, aka background service, akin to Windows service | |
$ systemctl stop <SERVICE> | Stop a service. | |
$ systemctl enable <SERVICE> | Restart a service. | |
$ systemctl stop <SERVICE> | Stop a service. | |
$ systemctl status <SERVICE> | Display service status | |
System logs | ||
$ journalctl -b | Show kernel boot messages. | |
$ dmesg -T | Kernel messages with human-redable timestamp. | |
$ dmesg -T –level=err | Shows only kernel messages that reports error. | |
Package manager | ||
apt-get -y install <APP> | Install an application on Ubuntu, Debian with Apt package manager. | |
yum install <APP> | Install app on any Read-Hat Linux based distribution. | |
dnf install <APP> | Install app on Fedora using DNF package manager. | |
apk add <APP> | Install app. on Alpine. | |
X Windows System | ||
pkill -15 Xorg | Reset X11 X windows System display server. | |
setxkbmap -option | Reset capslock customization. | |
setxkbmap -option ctrl:swapcaps | [EMACS FRIENDLY] Swap ctrl and capslock (X11 only, Linux, BSD and so on) | |
setxkbmap -option caps:swapescape | [VIM FRIENDLY ] Swap ESC and capslock (X11 only) |
Notes:
- Commands started with '$' dolar sign does not require administrative privilege.
- Commands started with '#' symbol require administrative or root privilege and should be run as root user or by using sudo.
- VSF - Stands for Virtual File System such as udev, tmpfs and so on.
- ifconfig command, for displaying information about network interfaces, usage is no longer recommended due to it be deprecated on most modern Linux distribution. Ifconfig has been superseded by ip command. However, the ifconfig is still used on BSD-variants, MacOSX and older Unix-like operating systems.
- iwconfig is also deprecated on most Linux distributions.
- The command '$ ip addr' is useful for obtaining the IP address of a home network router and browse its web interface using a web browser.
- UUID stands for Univeral Unique IDentifier
- The applications, atop, iotop and htop are not installed on most Linux distributions by default, however they can be obtainted through the package manager.
- The applications atop and iotop are essential for monitoring CPU and disk IO of http servers, database servers and etc.
1.2.3 Basic Commands
- => Change current prompt (works for Bash and ZSH shells)
$ export PS1=" $ >> " $ >> command typed by user output line 0 output line 1 ... ... ... output line N-1
- pwd => Get current working directory
$ pwd /home/mxpkf8/Downloads
- cd - Change the current directory
$ cd /Applications # OSX $ cd ~/Downloads $ cd /home/user/Downloads $ cd /Users/user/Downloads # Enter in Home directory: # /Users/<USER> in Mac OSX # /home/<USER/ in Linux $ cd $HOME
- ls - List directory
Linux:
$ ls / bin@ dev/ home/ lib64@ media/ opt/ root/ sbin@ sys/ usr/ boot/ etc/ lib@ lost+found/ mnt/ proc/ run/ srv/ tmp/ var/
Mac OSX:
$ ls -l /System/Applications/Utilities/Terminal.app/Contents total 32 -rw-r--r-- 1 root wheel 7998 Apr 17 22:57 Info.plist drwxr-xr-x 3 root wheel 96 May 27 20:40 MacOS -rw-r--r-- 1 root wheel 8 Apr 17 22:57 PkgInfo drwxr-xr-x 74 root wheel 2368 May 27 20:30 Resources drwxr-xr-x 3 root wheel 96 Apr 17 22:57 _CodeSignature -rw-r--r-- 1 root wheel 510 Apr 17 22:58 version.plist
- rm - Delete files or directory
# Delete file $ rm file.txt # Delete directory dir2 and all sub-directories of dir2 $ rm -rf ./dir1/dir2 # Delete directory dir2 and all sub-directories of dir2 in verbose mode $ rm -rf -v ./dir1/dir2
- cat - Display text file content
Linux:
$ cat /proc/uptime 11960.35 36214.14 $ cat /proc/filesystems nodev sysfs nodev tmpfs nodev bdev nodev proc nodev cgroup nodev cgroup2 .. ... ... ... ... ... ... ..
Mac OSX:
$ cat /System/Applications/Utilities/Terminal.app/Contents/Info.plist <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>ATSApplicationFontsPath</key> <string>Fonts</string> <key>BuildMachineOSBuild</key> <string>18A391024</string> ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
1.2.4 Creating files and directories from command line
- mkdir - Create directory (aka folder)
# Create single directory/folder 'somedir' at current directory $ mkdir somedir # Create single directory/folder 'somedir' at /tmp path $ mkdir -p /tmp/somedir # Create directory-tree root-folder, root-folder/sub1 and root-folder/sub1/sub2 $ mkdir -p /Users/unix/root-folder/sub1/sub2 # Create directory tree $ mkdir -p mydir/{subA,subB}/d1/d2 $ tree mydir mydir ├── subA │ └── d1 │ └── d2 └── subB └── d1 └── d2
- touch - Create empty files:
$ touch file1.txt somefile.cpp CMakeLists.txt
- Create multi-line file with echo
$ echo line1 > file.txt $ echo line1 > file.txt $ echo line3 >> file.txt $ cat file.txt line1 line2 line3
- Create multi-line files with cat-EOF trick.
# Create a file with some content $ cat > script.sh <<EOF #!/usr/bin/env sh echo " => Showing root directory" ls / EOF $ cat script.sh #!/usr/bin/env sh echo " => Showing root directory" ls / $ sh script.sh => Showing root directory bin dev home lib64 media opt root sbin sys usr boot etc lib lost+found mnt proc run srv tmp var
1.2.5 Files and executables
- which - Get path to executable in $PATH environment variable.
$ which xdg-open /usr/bin/xdg-open $ which zsh /usr/bin/zsh $ which brave /home/mxpkf8/Applications/brave/brave
- file - Identify text or binary files by they "magic-number" (uniquely identifying byte sequence)
Linux:
$ file /usr/bin/bash
/usr/bin/bash: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV)
, dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2
.. ... ... ... ... ...
$ file vs_buildtools__80950065.1591646379.exe
vs_buildtools__80950065.1591646379.exe: PE32 executable (GUI) Intel 80386, for MS Windows
Mac OSX:
$ file /bin/zsh /bin/zsh: Mach-O 64-bit executable x86_64 $ file $HOME/Desktop/Console /Users/unix/Desktop/Console: MacOS Alias file $ file /System/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal /System/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal: Mach-O 64-bit executable x86_64
- Open file with default system Application / xdg-open (Linux with X11, BSD with X11); open (MacOSX)
Linux:
# Open Desktop directory with default system file manager $ xdg-open $HOME/Desktop # Open file image.png with default system image viewer $ xdg-open image.png
Mac OSX:
# Open /Volumes directory with default system file manager (finder) $ open /Volumes # Open file CmakeLists.txt with default system text editor $ open CMakeLists.txt $ open disk-image.dmg
1.2.6 System information
- uname => Get Kernel Version
Linux:
$ uname -a Linux localhost.localdomain 5.6.6-300.fc32.x86_64 #1 SMP Tue Apr 21 13:44:19 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
Mac OSX Catalina:
$ uname -a Darwin ghosts-iMac-Pro.local 19.5.0 Darwin Kernel Version 19.5.0: Tue May 26 20:41:44 PDT 2020; root:xnu-6153.121.2~2/RELEASE_X86_64 x86_64
- uptime - Shows how long the machine is running.
# Linux $ >> uptime 13:43:36 up 2:22, 1 user, load average: 1.31, 1.00, 0.99 # Mac OSX 13:44 up 1:48, 2 users, load averages: 1.17 1.30 1.35
- whoami - Show current user (Note: it also works on Windows)
$ whoami user_ml7abfg
- w - Show logged users
Linux:
$ w
13:37:31 up 2:16, 1 user, load average: 0.63, 0.87, 0.98
USER TTY LOGIN@ IDLE JCPU PCPU WHAT
user_ml7abfg :0 11:22 ?xdm? 43:26 0.00s /usr/libexec/gdm-x-session --register-session --
Mac OSX:
$ w
13:38 up 1:42, 2 users, load averages: 1.86 1.53 1.44
USER TTY FROM LOGIN@ IDLE WHAT
unix console - 11:58 1:39 -
unix s000 - 13:32 - w
- ps - Show running processes
Linux:
$ ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 11:21 ? 00:00:18 /usr/lib/systemd/systemd --switched-root --system --deserialize 29 root 2 0 0 11:21 ? 00:00:00 [kthreadd] root 3 2 0 11:21 ? 00:00:00 [rcu_gp] root 4 2 0 11:21 ? 00:00:00 [rcu_par_gp] ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... mxpkf8 2383 2382 0 11:22 ? 00:00:00 dbus-broker --log 4 --controller 9 --machine- mxpkf8 2387 1946 0 11:22 ? 00:00:01 /usr/libexec/at-spi2-registryd --use-gnome-se mxpkf8 2392 1 0 11:22 ? 00:00:00 /usr/bin/gpg-agent --sh --daemon --write-env- mxpkf8 2393 2149 0 11:22 tty2 00:01:18 xfwm4 ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
Mac OSX:
$ ps -ef UID PID PPID C STIME TTY TIME CMD 0 1 0 0 11:56AM ?? 0:16.58 /sbin/launchd 0 86 1 0 11:58AM ?? 0:01.13 /usr/sbin/syslogd 0 87 1 0 11:58AM ?? 0:03.44 /usr/libexec/UserEventAgent (System) 0 90 1 0 11:58AM ?? 0:01.11 /System/Library/PrivateFrameworks/Uninstall.framework/Resources/uninstalld 0 91 1 0 11:58AM ?? 0:05.95 /usr/libexec/kextd 0 92 1 0 11:58AM ?? 0:09.31 /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/FSEvents.framework/Versions/A/Support/fseventsd 0 93 1 0 11:58AM ?? 0:00.32 /System/Library/PrivateFrameworks/MediaRemo ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... 501 6102 1 0 1:29PM ?? 0:00.26 /System/Library/CoreServices/CoreServicesUIAgent.app/Contents/MacOS/CoreServicesUIAgent 501 6813 1 0 1:45PM ?? 0:01.12 /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/CVMCompiler 2 0 6795 319 0 1:32PM ttys000 0:00.06 login -pf unix 501 6796 6795 0 1:32PM ttys000 0:00.38 -zsh 0 6816 6796 0 1:46PM ttys000 0:00.01 ps -ef
- env - Show environment variables
Linux:
$ env IMSETTINGS_INTEGRATE_DESKTOP=yes SHELL=/bin/bash SESSION_MANAGER=local/unix:@/tmp/.ICE-unix/2149,unix/unix:/tmp/.ICE-unix/2149 WINDOWID=52428803 COLORTERM=truecolor XDG_CONFIG_DIRS=/etc/xdg HISTCONTROL=ignoredups XDG_MENU_PREFIX=xfce- ... .. ... .. ... .. ... .. ... .. ... ... .. ... .. ... .. ... .. ... .. ...
Mac OSX:
$ env TMPDIR=/var/folders/jh/w00bgbps79bddlbqwkv31d9m0000gn/T/ XPC_FLAGS=0x0 LaunchInstanceID=433A53AA-AB93-4CF7-9700-BA15B57FFDAC TERM=xterm-256color LANG=en_US.UTF-8 ... ... ... ... ... ... ... ... ... ... ... ... ... ... SHELL=/bin/zsh HOME=/Users/unix LOGNAME=unix USER=unix PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin SHLVL=1 PWD=/Volumes/data/unix-explore OLDPWD=/Volumes/data PS1= $> _=/usr/bin/env
1.3 Widely used functions for strings and buffers in low level codes
This section provides a recapitulation of the most widely used C functions in low level code bases and legacy C codes. Those functions are in the header <string.h> for C or <cstring> for C++.
String Manipulation
- strlen() => Get length of null-terminated character array.
- Docs: $ man strlen
size_t strlen(const char *s); root [39] const char* st1 = "unix posix irix linux"; root [40] const char* st2 = "bsd msdos"; root [41] strlen(st1) (unsigned long) 21 root [42] strlen(st2) (unsigned long) 9
- strdup() => Copy a C-string allocating the copy with malloc.
char* strdup(const char *s); root [39] const char* st1 = "unix posix irix linux"; root [43] char* p_copy = strdup(st1) (char *) "unix posix irix linux" // Release memory for heap-allocated string root [47] free(p_copy)
- strcmp() => Compare two C-strings null-terminated (terminated with
'\0' or 0x00 null char) array of characters. This function returns
0 if the two strings are equal.
- Docs: $ man strcmp
int strcmp(const char *s1, const char *s2); // ------- USAGE --------------------------// if ( strmcp(STRING_A, STRING_B) == 0 ) puts(" => Strings are equal"); else puts(" => Strins are not equal"); // ------ CERN's Root REPL Session -------// root [0] #include <string.h> root [1] const char* s1 = "hello"; root [2] const char* s2 = "world"; root [3] const char* s3 = "world C++17 c11 ADA spark"; root [4] const char* s4 = "hello"; root [5] strcmp(s1, s2) (int) -15 root [6] strcmp(s1, s3) (int) -15 root [7] strcmp(s1, s4) (int) 0
- strcasecmp() => Compare two string ignoring case sensitivity. This function returns 0 if the string arguments are equal. Othewise, it returns anything other than 0.
int strcasecmp(const char *string1, const char *string2); // ---- USAGE ------------------------------------------// if( strcasecmp(stringA, stringB) == 0 ) printf(" Strings are equal Ok. \n"); else printf(" Strings are not equal. \n"); // ----- CERN's ROOT REPL session ----------------------// // Strings not equal root [21] strcasecmp("app", "Application") (int) -108 // Strings are equal root [22] strcasecmp("application", "Application") (int) 0 root [23] strcasecmp("AppLiCatION", "Application") (int) 0 root [24]
- sprintf() => Print string to a buffer. (Headers: <cstdio> in C++ or <stdio.h> in C). Note: this function is vulnerable to buffer overflow, if the data copied to the buffer is larger than its size.
int sprintf(char *str, const char *format, ...); // ---- CERN's ROOT REPL session ----------------------// root [1] char buffer[300]; root [2] memset(buffer, 0x00, 300) root [4] sprintf(buffer, "The root of %d is equal to %f", 2, 2.0); root [5] buffer (char [300]) "The root of 2 is equal to 2.000000\0\0\0\0\0\0\0\.... ... \0" root [7] std::cout << " buffer = <" << buffer << ">" << '\n'; buffer = <The root of 2 is equal to 2.000000>
- snprintf() => Print string to a buffer limiting the number of characters that are copied to the buffer. The limitation of the number characters copied helps protecting against buffer overflow vulnerabilities.
int snprintf(char *str, size_t size, const char *format, ...); // ---- CERN's ROOT REPL session ----------------------------// root [0] char buf[200]; root [1] memset(buf, '\0', 200); // Initialize buffer root [6] snprintf(buf, 10, "The square root of %f is equal to %.3f ", 10.0, sqrt(10.0)); root [7] root [7] buf (char [200]) "The squar\0\0\0\0\0... ...\0\0\0" root [8] snprintf(buf, 25, "The square root of %f is equal to %.3f ", 10.0, sqrt(10.0)); root [9] root [9] buf (char [200]) "The square root of 10.00\0\0\0...\0\0" root [10] snprintf(buf, 125, "The square root of %f is equal to %.3f ", 10.0, sqrt(10.0)); root [11] root [11] buf (char [200]) "The square root of 10.000000 is equal to 3.162 \0\0\0..\0\0"
- dprintf => Print to file descriptor. It can be used for formatted printing to any file descriptor, including sockets, pipes, regular files and standard streams (stdout and stderr).
int dprintf(int fd, const char *format, ...); // -- EXAMPLE =>> CERN's ROOT REPL Session ---------------// root [6] dprintf(STDOUT_FILENO, " [INFO] x = %d ; sqrt(3) = %.3f \n", 100, sqrt(3.0)) [INFO] x = 100 ; sqrt(3) = 1.732 root [5] dprintf(STDERR_FILENO, " [INFO] x = %d ; sqrt(3) = %.3f \n", 100, sqrt(3.0)) [INFO] x = 100 ; sqrt(3) = 1.732 (int) 35
- asprintf => Print allocated string. Similar to snprintf, but it stores the output in a large enough heap-allocated (dynamically allocated) buffer.
int asprintf(char **strp, const char *fmt, ...); // ==>> EXAMPLE =>> CERN's ROOT REPL Session ============// // Output root [7] char* output; root [8] asprintf(&output, " [TRACE] x = %d ; sin(3.1415) = %.5f ; s = %s \n", -261, sin(3.1415), "Hello world") (int) 61 root [9] output (char *) " [TRACE] x = -261 ; sin(3.1415) = 0.00009 ; s = Hello world " root [10] puts(output) [TRACE] x = -261 ; sin(3.1415) = 0.00009 ; s = Hello world // Release allocated memory., root [11] free(output) root [12]
- strcpy() => Copy string 'src' parameter (null-terminated array of
characters) to a buffer.
- Docs: $ man strcpy
- Note: It is vulnerable to buffer overflow, if the size of the string is bigger than the buffer. This function is often used to for copying string literals to a buffer.
char* strcpy( char* p_buffer // Pointer to buffer (array of chars) ,const char* c_string_src // C-string ); root [9] char buffer[200]; root [10] buffer (char [200]) "\0\0\0\0\0\0\0\0\0\0\0\0\0\0 ...... \0\0\0\0" root [11] strcpy(buffer, "Hello world C++17 C++20 ADA Rust") (char *) "Hello world C++17 C++20 ADA Rust" root [12] buffer (char [200]) "Hello world C++17 C++20 ADA Rust\0\0\0\0\0\0\0 ... ... \0\0\0"
- strncpy() => Copy N characters from string to buffer.
- Docs: $ man strncpy
- Linux mapage: "The strcpy() function copies the string pointed to by src, including the terminating null byte ('\0'), to the buffer pointed to by dest. The strings may not overlap, and the destination string dest must be large enough to receive the copy. Beware of buffer overruns! (See BUGS.)"
char* strncpy( char* dest // Pointer to buffer , const char* src // C-string (null-terminated) that will be copied , size_t n // Number of chars (bytes) from src that will be copied ); // ---------- CERN's ROOT Repl Session ------------// root [13] char buff1[30]; root [14] char buff2[30]; root [15] const char* cstr = "Hello world C++17 embedded systems real time control systems"; root [16] buff1 (char [30]) "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" root [17] buff2 (char [30]) "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" root [18] strncpy(buff1, cstr, 10) (char *) "Hello worl" root [19] buff1 (char [30]) "Hello worl\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" root [20] strncpy(buff2, cstr, 25) (char *) "Hello world C++17 embedde" root [21] buff2 (char [30]) "Hello world C++17 embedde\0\0\0"
- strcat() => Manpage: "The strcat() function appends the src string
to the dest string, over‐ writing the terminating null byte ('\0')
at the end of dest, and then adds a terminating null byte. The
strings may not overlap, and the dest string must have enough
space for the result. If dest is not large enough, program
behavior is unpredictable; buffer overruns (aka buffer overflow)
are a favorite avenue for attacking secure program"
- Note: This function is unsafe and can introduce buffer overflow vulnerability if the size of the src string is not limited.
char *strcat(char *dest, const char *src); // ------------- CERN Root REPL ------------------// const char* null_terminated_char_array1= "C++1z KALMAN FILTER sensor fusion data"; char buffer1[100] = "Append cstring: "; root [3] buffer1 (char [100]) "Append cstring: \0\0\0\0\0\0\0\0\0\0\0\0\0...\0\0" root [4] strcat(buffer1, null_terminated_char_array1) (char *) "Append cstring: C++1z KALMAN FILTER sensor fusion data" root [5] buffer1 (char [100]) "Append cstring: C++1z KALMAN FILTER sensor fusion data\0\0....\0\0" root [6] strcat(buffer1, " string literal ") (char *) "Append cstring: C++1z KALMAN FILTER sensor fusion data string literal " root [7] buffer1 (char [100]) "Append cstring: C++1z KALMAN FILTER sensor fusion data string literal \0\0\0\0...\0\0"
- strncat() => Similar to strcat, but it limits the number of character to be copied from src to a buffer.
char* strncat(char *dest, const char *src, size_t n); // ------------ CERN's Root REPL session ----------------------// // // Stack-allocated buffer root [8] char buff[100]; root [11] memset(buff, 100, 0x00); // Initialize buffer root [12] const char* str1 = "Hello world APL C++1z ADA RUST DLang OCaml"; root [13] buff (char [100]) "\0\0\0\0\0\0\0\0\0\0\0\0\0\0...\0\0" root [14] strncat(buff, str1, 10) (char *) "Hello worl" root [15] buff (char [100]) "Hello worl\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 .. \0\0" root [16] strncat(buff, str1, 20) (char *) "Hello worlHello world APL C++1" root [17] buff (char [100]) "Hello worlHello world APL C++1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0....\0\0" root [18] strncat(buff, str1 + 25, 30) (char *) "Hello worlHello world APL C++1 RUST DLang OCaml" root [19] buff (char [100]) "Hello worlHello world APL C++1 RUST DLang OCaml\0\0...\0\0"
- strtok => Break string into tokens.
char* strtok(char* str, const char* delim); // ---- Usage Example ------------------// root [8] const char* sep = ","; // Separators root [9] char text[500] = "BRL , CAD ,, USD ,,, EUR , YEN , JPY"; root [10] char* tok = strtok(text, sep) (char *) "BRL " root [11] while( tok != nullptr) { printf(" => token = %s \n", tok); tok = strtok(nullptr, sep); } => token = BRL => token = CAD => token = USD => token = EUR => token = YEN => token = JPY
- strtoul() => Attempt to parse an unsigned integer from a string. On failure, this function sets the errno variable to ERANGE.
unsigned long int strtoul (const char* str, char** endptr, int base); // --------------- Example -----------------------// root [42] auto n = strtoul("1001001", nullptr, 2) (unsigned long) 73 root [43] auto n = strtoul("1001001", nullptr, 10) (unsigned long) 1001001 root [44] auto n = strtoul("1001001", nullptr, 16) (unsigned long) 16781313 root [45] auto n = strtoul("1001001", nullptr, 8) (unsigned long) 262657 root [46] auto n = strtoul("AFBC01", nullptr, 16) (unsigned long) 11516929 // Failure root [47] auto n = strtoul("XAFBC01", nullptr, 16) (unsigned long) 0 root [54] auto n = strtoul("AFBC01 rest of text", nullptr, 16) (unsigned long) 11516929 root [55] auto n = strtoul(" AFBC01 rest of text", nullptr, 16) (unsigned long) 11516929
Buffer Manipulation
- memchr() => Locate a byte in a buffer or memory region. This function returns a pointer to the first occurency of the search byte and returns null pointer if the byte is not found.
void* memchr(const void* buffer, int byte_searched, size_t buffer_size); // ----- Example: CERN's ROOT REPL Session ----------------// root [19] char mybuffer[200] = "hello world 9 10 36 buffer byte array 9 0 %"; // Pointer to first occurrency of character '9' (byte) root [22] p (char *) "9 10 36 buffer byte array 9 0 %" root [23] p[0] (char) '9' root [24] // Position (aka index) where the byte is found root [35] ptrdiff_t idx = p - mybuffer (long) 12 root [37] mybuffer[idx] (char) '9' // Next ocurrency root [41] p = (char*) memchr(mybuffer + idx + 1, '9', 200 - idx) (char *) "9 0 %"
- memset() => Fill some memory area (buffer) with a constant byte.
void* memset(void *s, int c, size_t n); // ------ CERN's ROOT REPL session -------------// // // Create a heap-allocated buffer containing 6 characters. root [0] char* char_heap_buffer = (char*) malloc(6 * sizeof(char)) (char *) "@\x0e" "e\x01" root [1] char_heap_buffer[0] (char) '@' root [2] char_heap_buffer[1] (char) '0x0e' root [3] char_heap_buffer[2] (char) 'e' // Intialize buffer char_hepa_buffer root [9] memset(char_heap_buffer, 'z', 6) (void *) 0x128c760 root [10] char_heap_buffer[0] (char) 'z' root [11] char_heap_buffer[1] (char) 'z' root [12] char_heap_buffer[2] (char) 'z' root [13] char_heap_buffer[3] (char) 'z' root [14] char_heap_buffer (char *) "zzzzzz" root [15] free(char_heap_buffer)
- memcmp() => Compare the first N bytes of two buffers. It returns 0 if all the first N bytes from two buffers are equal. Note: This function can only compare buffers containing POD data (Plain-Old Data) without any internal pointer or heap-allocated data.
int memcmp( const void* s1 // Pointer to the beginning of buffer 1 , const void* s2 // Pointer to the beginning of buffer 2 , size_t n // Number of bytes that will be compared ); // -------- CERN's ROOT REPL Session -----------------------// root [22] uint8_t bf1[] = { 0x01, 0xAB, 0x2C, 0x3C, 0xFF }; root [26] uint8_t bf2[] = { 0x01, 0xAB, 0x2C, 0x3C, 0xFF, 0x85, 0x2A, 0x45 }; root [30] uint8_t bfx[] = { 0x3A, 0xF8, 0x25 } // Result: Non-zero => First 3 bytes of two buffers are not equal. root [32] memcmp(bf1, bfx, 3) (int) -57 // Result: Zero => First 5 bytes of two buffers are equal. root [36] memcmp(bf1, bf2, 5) (int) 0 // Result: Zero => First 2 bytes of two buffers are equal. root [37] memcmp(bf1, bf2, 2) (int) 0 root [13] free(heap_buffer)
- memcpy() => Copy N bytes from a memory area (or buffer) src to a memory area dest. This function can only copy POD (Plain Old Data) types, it cannot copy anything which contains pointers or heap-allocated data.
void* memcpy(void* dest, const void* src, size_t n); // ------- CERN's ROOT Repl session ---------------// // // ---- Char array --------// root [51] char buf1[10] = { 'h', 'e', 'l', 'l', 'o', ' ', 'c', 'p', 'p', '\0' } (char [10]) "hello cpp" root [53] char buf2[15] (char [15]) "\0\0\0\0\0\0\0\0\0\0\0\0\0" root [54] memcpy(buf2, buf1, 10 * sizeof(char)) (void *) 0x7f5c5cd7d1b5 root [55] buf2 (char [15]) "hello cpp\0\0\0\0" // ---- Int array -------// root [56] int arr1[5] = { 10, 20, 25, 100, -2} (int [5]) { 10, 20, 25, 100, -2 } root [57] int arr2[10] (int [10]) { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } root [58] memcpy(arr2, arr1, 5 * sizeof(int)) (void *) 0x7f5c5cd7d1f0 root [59] arr2 (int [10]) { 10, 20, 25, 100, -2, 0, 0, 0, 0, 0 }
- bzero() => Zero-initialize some memory area. In the words of manpage: "The bzero() function erases the data in the n bytes of the memory starting at the location pointed to by s, by writing zeros (bytes containing '\0') to that area."
void bzero(void *s, size_t n); // ------------ CERN's ROOT REPL session --------------// // // Heap-allocated buffer with 5 integers root [1] int* heap_buffer = (int*) malloc(5 * sizeof(int)) (int *) 0x2c50ea0 root [2] heap_buffer[0] (int) 37832720 root [3] heap_buffer[1] (int) 0 root [4] heap_buffer[2] (int) 51513872 // Initialize buffer, filling it with zeroes. root [7] bzero(heap_buffer, 5 * sizeof(int)) root [8] heap_buffer (int *) 0x2c50ea0 root [9] heap_buffer[0] (int) 0 root [10] heap_buffer[1] (int) 0 root [11] heap_buffer[2] (int) 0 root [12] heap_buffer[4] (int) 0
- bcopy() => Manpage: "The bcopy() function copies n bytes from src
to dest. The result is correct, even when both areas overlap."
- Note: Despite the manpage state that the function is deprecated it is still used by many legacy codes, so it is still worth knowing about it.
void bcopy(const void *src, void *dest, size_t n); // ----------- CERN's ROOT Repl Session --------------// root [0] char buffer[50]; root [1] bzero(buffer, 50 * sizeof(char)) root [2] buffer (char [50]) "\0\0\0\0\0\0\0\0\0\0\0... ...\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" // Null terminated array of chars root [3] const char* src1 = "This is the buffer 1"; root [4] bcopy(src1, buffer, 10) root [5] buffer (char [50]) "This is th\0\0\0\0\0\0\0\0... ...0\0\0\0\0\0\0" root [6] bcopy(src1, buffer, 25) root [7] buffer (char [50]) "This is the buffer 1\0\0\0\0\x14\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" root [8] bcopy("control system Modelica / state space model / SIMULINK", buffer, 40) root [9] buffer (char [50]) "control system Modelica / state space mo\0\0\0\0\0\0\0\0"
1.4 Map of user-space APIs of Unix-like Operating Systems
It is worth knowing where to find common Unix and POSIX (Portable Operatin System Interface) C APIs, which are common to many UNIX-based operating systems, such as Linux, BSD-variants, MacOSX, QNX and so on. In order to enhance the discoverability, this section lists selected bookmarks to widely used Unix, POSIX and system-specific Application Programing Interfaces (APIS).
Primitive IO (Read, Open, Close - Sytem Calls)
- open(2) - Open a file returning a file descriptor.
- read(2) - Read data from a file descriptor to a buffer.
- write(2) - Write data from buffer to file descriptor.
- close(2) - Close file descriptor.
- lseek(2) - Position offset for reading writing operation. This useful for binary files, where some data are located at specific positions.
- Scatter, gather IO - (reav, writev) - Scatter, Gatter or Vectored IO.
File Descriptor Manipulation
- dup, dup2, dup3 - Duplicate file descriptors for IO redirection.
- fcntl(2) - Manipulate file descriptor.
System Information and User Accounts
- uname(3) - Get system information, operating system, kernel version, network name and so on.
- getdomainname(2) - Get/Set domain name.
- getpwnam(3) - Password file entry.
- getgrgid(3) - Group file entry.
- gethostname(2) - Get/Set machine hostname.
- getpwuid(3) - Get information about current user.
- getlogin(2)
Network / BSD Sockets
- IPv4 Protocol - Internet Protocol Version 4
- IPv6 Protocol - Internet Protocol Version 6
- Unix Domain Socket - Inter-process communication which is a replacement for IP protocol on local machine. Unix Domain Sockets has less overhead than IP protocol on local host. This protocl is useful for providing network transparency. This API is used by Docker daemon on local machine, DBUS (Inter Process Communication BUS common on Linux Desktops systems) and X11 (X Windows Systems).
- TCP Protocol - Transmission Control Protocol (Most used protocol over the internet.)
- UDP Protocol - User Datagram Protocol
- SCTP Protocol - (Internet Stream Control Transmission Protocol) - New protocol, combining the realibility of TCP with message boundary feature of UDP. Note: This type of protocol is still not availability on Windows NT or Windows CE operating systems, therefore the usage of SCTP is not portable yet.
- socket(2) - Create a socket endpoint.
- recv(2) - (read) Receive data from socket.
- send(2) - (write) Send data to socket
- read(2) - The read() system call also works with sockets.
- write(2) - Write sycall also works with sockets. It sends data from some buffer to socket.
- connect(2) - Used by a TCP client socket to initiate connection to a TCP server socket (a.k.a passive socket or listening socket) running in local or remote machine.
- accept(2) - Used by a TCP server socket (a.k.a passive socket) to accept connection from a client socket.
- bind(2) - Used by a TCP server socket to bind to some TCP port (16 bits unsigned number from 0 to 65535). Note: Ports which number is less than 1024 requires administrative or root privilege.
- listen(2) - (Server socket) - Listen connection.
- getsockopt(2) - Get and set socket options.
- gethostbyname(3) - Query DNS, get IPv4 or IPv6 network address from hostname.
- byteorder(3) (htonl, htons, nothl, ntohs) - Convert from/to network and host machine endianess for numerical values.
IO Multiplexing (aka non-blocking IO)
Note: As most of those APIs are not standardized. In order to ensure code portability, it is better to use C++ libraries that encapsulates multiplexed IO (Input/Output) APIs rather than use them directly. Some of those libraries are: Boost.ASIO (C++ library) and LibUV (C library).
Standardized APIs:
- select(2) - (Standardized, common to Linux, BSD and MacOSX)
- poll(2) (Linux Docs); poll (BSD Docs) - (Standardized, common to Linux, BSD and MacOSX)
Non-standardized APIs:
- epoll(7) - [BEST] Linux-only API for IO multiplexing API.
- kqueue - [BEST] BSD-Only API for IO multiplexing. This API is available at: FreeBSD; NetBSD; OpenBSD; DragonFlyBSD and MacOSX.
Threads and Concurrency
- clone(2) [LINUX ONLY] - clone() system call wrapper used for creating processes or threads. This API is also a fundamental building block of Linux container runtimes, such as Docker and Podman.
- pthread (BSD Docs) - Posix API Threads and concurrency primitives.
- pthreads(7) (Linux Docs)
- pthread_create(3) - Create new thread.
- pthread_join(3)
- pthread_detach(3)
- pthread_exit(3) - terminate calling thread
- pthread_cancel(3)
Process Control APIs
- execve(2) => Run a process (Execv system call)
- fexecve(3) => Similar to execve, but executes program (any Unix native-code executable or script starting with shebgang #! such as #!/usr/bin/python …) from file descriptor.
- fork() => Fork system call, often used alongside Execv.
- exit(3) => Exit system call. Force termination of current process.
- wait(2) => Wait for a process.
- getpid() => Get process unique identifier, PID
- getppid() => Get parent process PID.
- getpgrp() => Get process group IDs
- getsid(2) => Get process session ID.
- chdir => Current working directory
- environ(7) => All environment variables from current process.
- kill(2) => Send UNIX signal to process. It can also be used to request or force process termination.
IPC - Inter Process Communication (Except socket)
- mmap(2) (BSD Docs) => Map file descriptor to process virtual memory or allocate memory. This API can also be used for shared memory inter-process communication.
- mmap(2) (Linux Docs)
- mmap (Blackberry)
Memory Management
- getpagesize(2) => Get memory page size.
- mmap(2) (BSD Docs) => Map file descriptor to virtual memory or allocate memory.
- msync(2) - Synchronize file with memory mapping.
- mremap(2) => Remap virtual memory region.
- mprotect(2) => Change protection for some memory region.
- mlock(2) => Lock/ulock memory.
- brk, sbrk (Linux) => Change data segment. (Process Heap)
- brk, sbrk (Free BSD)
- memalign(3)
- malloc, calloc, realloc, free - Dynamic memory allocation. (High Level)
Dynamic Loading of Shared Libraries at Runtime
Note: shared libraries (Windows DLLs - Dynamic Linked Libraries) are called SO (Shared Object) or DSO (Dynamic Shared Object) on Unix-based systems.
- dlopen(3) - Load shared library in process address space.
- dlclose(3) - Close shared library.
- dlerror(3) - Get error diagnostics for dlopen() API.
- dlsym(3) - Load symbol, function pointer or variable, from shared library.
- dlvsym(3) - Similar to dlsym, but allows loading an specific version of a symbol.
- dlinfo(3)
- dladdr(3)
- dl_iterate_phdr(3) - Iterate over the list of shared libraries loaded by the current process.
Functionalities for supporting containers and sandboxing
Containers are a lightweight virtualization alternative to virtual machines, that uses fewer system resources, such as memory and CPU time as unlike virtual machines, containers share the same kernel. Containers are nothing more than sandboxed processes with restricted access to system resources. On Linux, container runtimes, such as Docker, Podman (Red-Hat) or LXC implement containers with the following APIs: cgroups API for restricting CPU time and memory usage; chroot or restricting the directories that a sandboxed processes can access and making them look like the process' root directory; Linux user, pid, mount, network and namespaces for restricting user privilege, access to system directories and network.
- clone(2) [LINUX ONLY] - clone() system call for creating processes with Linux namespaces, which can restrict a process capability and system access. This system call is also the primitive for creating new processes or threads.
- unshare(2)
- capabilities(7) - Capability-based security feature which allows limiting what executables with SETUID bit set can do.
- cgroups(7) - Linux manual page
- Linux namespaces pid,network,mount,ipc,uts,user,cgroup
- setns(2) - Linux manual page
- ipc_namespaces(7) - Linux manual page
Low Level Terminal API
- termios(4)
- terminfo(5) - Terminal capability database.
- termcap(5) - Terminal capability database.
- isatty(3) => Check whether file descriptor is a terminal (pseudo terminal). This function is used in this way: isatty(STDIN_FILENO), if this expression returns true (1), the current process is running a terminal emulator, otherwise it is not running in a terminal. Note: STDIN_FILENO, has value 1, and refers to standard input (stdin).
- ttyname(3)
- tty(4)
- pty(4) - BSD-style and System V-style compatibility pseudo-terminal driver
- pts(4) - Pseudo Terminal Pairs
- pty(3) (BSD) - openpty, forkpty
- pty(7) (Linux) - Pseudo Terminal Interfaces
- posix_openpt(2)
- console_ioctl(4)
- login_tty(3)
- unlockpt(3)
- ptsname(3)
- ioctl(2)
- tcsetattr(3)
- tput(1) - Query terminal database
Terminal Escape and Control Sequences
- General Terminal Interface
- Open Group, Single UNIX specification.
- VT100 escape codes
- Terminal codes (ANSI/VT100) introduction - Bash Hackers Wiki (DEV 20200708T2203)
- XTerm-Control-Sequences.txt · GitHub
- Proprietary Escape Codes / iTerm2
- Listing of iTerm2 (MacOSX-only terminal emulator) proprietary escap sequences.
- C Programming - using ANSI escape codes on Windows, macOS and Linux terminals | Solarian Programmer
- Colours and Cursor Movement With tput
Articles related to pseudo-terminal device API:
- [BEST] Windows Command-Line: Introducing the Windows Pseudo Console (ConPTY) | Windows Command Line
- Line discipline - Wikipedia
- pty - pseudoterminal interfaces / Linux Manpage
- Streams Programming Guide - Oracle
- General Terminal Interface - UNIX Open Group
- TTYs and X Windows - Unix User Interaction now and then
- Controlling TTYs: A Unix Horror Story
- The TTY desmystified.
- A brief introduction to Termios
- A brief introduction to termios:(3) and stty
- Controlling terminal
- Virtual Consoles and Terminals
- tty, session leader, terminal, controlling terminal, stdout and stdin, foreground and background
- Terminal Packet Mode - STREAMS Programming Guide - Oracle
- Pseudo-tty drivers – ptm and pts
- Terminal device control
- Serial Programming/termios - Wikibooks, open books for an open world
- c++ - Terminal ESC Sequence Decoder - Code Review Stack Exchange
- Tera Term Source Code Overview
Utilities for PTY - Pseudo-Terminal Interface
- Expect (Perl) - - automate interactions with command line programs that expose a text terminal interface. - metacpan.org
- How to Record and Replay Linux Terminal Sessions using 'script' and 'scriptreplay' Commands
- terminal.sexy - Terminal Color Scheme Designer (Online Tool)
- reptyr: Changing a process's controlling terminal
- Open-Source Kermit Project
- "Kermit is a robust transport-independent file-transfer protocol and a large collection of software programs that implement it on a wide variety of platforms. In addition to file transfer, many of these programs also make network, dialed, and/or serial-port connections and also offer features such as terminal emulation, character-set conversion, and scripting for automation of any communication or file-management task. The Kermit Project originated at Columbia University in New York City in 1981 and remained there for 30 years. Since 2011 it is independent. CLICK HERE for more about the Kermit Project."
- Anatomy of My MacOS Terminal
Linux-specific (VFS) Virtual File System APIs
GENERAL:
- Overview of the Linux Virtual File System — The Linux Kernel documentation
- Introduction to the Linux Virtual Filesystem (VFS) – Part I: A High-Level Tour — Star Lab Software
- The Linux kernel: The Linux Virtual File System
- Shared Memory Virtual Filesystem
SYSFS VFS:
- SYSFS - VFS that exports Kernel objects. Some files in SYSFS are mapped to real hardware such as GPIOs; I2C; CAN bus; PCI Bus; USB bus and so on.
- The sysfs Filesystem - Patrick Mochel
PROCFS VFS:
- PROCFS - Procfs /proc file system.
- /PROC/SYS - Directory for manipulating Kernel runtime parameters (sysctl tool)
TMPFS VFS:
- Brief: Virtual file system stored in volatime memory. This VSF allows files to be manipulated entirely on RAM volatile memory with just ordinary open(), read(), close(), write() system calls without touching the disk and without incurring on any IO overhead. The directories /dev/shm and /tmp are TMPFS VSFs, are entirely mounted on RAM memory. Any files stored on those directories are lost after system reboot.
- Tmpfs — The Linux Kernel documentation
- About tmpfs, /dev/shm and Oracle | Develop Paper
- Persistent RAM disk - Observium
- Improve Nginx cache performance with tmpfs - VirtuBox
- Storing Files/Directories In Memory With tmpfs
- Reduce your server's resource usage by moving MySQL temporary directory to tmpfs
Linux System Calls (Assembly)
- Linux System Call Table for x86 64 · Ryan A. Chapman
- Chromium OS Docs - Linux System Call Table
- Linux Syscalls Reference · GitHub
System V Calling Conventions
Calling conventions adopted by most Unix-like Operating Systems for C-compatible APIs.
- x86 calling conventions - Wikipedia
- System V ABI - OSDev Wiki
- System V Application Binary Interface - AMD64 Architecture Processor Supplement - Draft Version 0.99.7
- (Aaron Bloomfield) - Chapter 1 - The 64 bit x86 C Calling Convention
- (Agner Fog) - Calling conventions for different C++ compilers and operating systems
1.5 Creating Portable Binaries for Linux / GLIBC Dependency Hell
1.5.1 Overview
One of the greatest challenges of Linux desktop is building and deploying portable applications and native executable which work on any Linux distribution. Even if an application totally statically linked against all dependencies or it is bundled with all its shared libraries dependencies via LD_LIBRARY_PATH environment variable or RPATH, the program may still fail to run in other Linux distribution due to the GLIBC (GNU C library runtime) dependency. A GLIBC is forward compatible, but not backward compatible, the GLIBC runtime failure is more likely to happen if the application was built on a Linux distribution using a newer version of GLIBC, but the program is deployed in a distribution using an older version of GLIBC.
- Side Note 1:
- Applications built with GO (Golang) programming language are less likely to be affected by the GLIBC-issue, as GO compiler often does not link against the GLIBC and builds statically linked binaries which can be deployed everywhere.
- Side Note 2:
- The GLIBC issue does not only affect C++, all programming languages which generate native Linux executable, for instance, D-lang, Rust, OCaml, Haskell may also be affected due to the GLIBC compatibility problem. Besides executables, the GLIBC-dependency hell can also impact shared libraries, also known as share objects, and consequently native libraries used by programming languages such as Python, Ruby, Java and so on.
- Side Note 3:
- Even AppImage, which is a solution for distributing binaries on Linux is affected by the GLIBC-problem. As a result, the AppImage documentation recommends building AppImage on Linux distributions with older versions of GlibC.
- Side Node 4:
- The GLIBC can be bypassed if the application invokes system-calls directly by using assembly or inline assembly. On Linux, this is possible because the system calls are stable and well documented. This might be the approach used by Golang for avoiding the GLIBC dependency.
Possible Solutions:
- Link against an older version of GLIBC
- Build the application in a system with the oldest possible version of GLIBC. System in this context means: a linux virtual machine; a chroot environment or a docker image with an older version of GLIBC.
- Link against MUSL (CRT - C Runtime Library)
- The MUSL library, is an alternative to GLIBC, which was designed for embedded systems and static linking, allows building statically linked binaries that does not suffer from the GLIBC compatibility problems. The drawback of this procedure is that dynamic loading with dlopen API (dlopen, dlclose, dlsym functions) is still not supported.
- Deploy via Docker
- Deploy the application via docker image. While this solution is acceptable for compilers, building environments, servers and command line applications, Docker is not suitable for games or Desktop GUI (Graphical User Interface) applications intended to be used by non-technical users.
- Advantage:
- Pack application, dependencies and configuration.
- The deployment environment can be reproduced everywhere.
- Distribute as source and recompile from source on every deployment machine.
- Distribute the application via Linux distribution repositories
- Distribute the application via official Linux distribution repositories by creating distribution-specific packages for every supported distribution. It means creating (.deb) Debian packages for Debian-based distributions; (.rpm) packages for Fedora and Centos; (.apk) alpine packages for Alpine.
Further Reading
General reading about Linux CRT (C Runtime Library), GlibC and alternatives:
- API/ABI changes review for glibc
- Comparison of C/POSIX standard library implementations for Linux
- Brief: "The table below and notes which follow are a comparison of some of the different standard library implementations available for Linux, with a particular focus on the balance between feature-richness and bloat. I have tried to be fair and objective, but as I am the author of musl, that may have influenced my choice of which aspects to compare. Future directions for this comparison include detailed performance benchmarking and inclusion of additional library implementations, especially Google's Bionic and other BSD libc ports."
- A turning point for GNU libc - LWN.net - Jonathan Corbet
- Brief: "The kernel may be the core of a Linux system, but neither users nor applications deal with the kernel directly. Instead, almost all interactions with the kernel are moderated through the C library, which is charged with providing a standards-compliant interface to the kernel's functionality. There are a number of C library implementations available, but, outside of the embedded sphere, most Linux systems use the GNU C library, often just called "glibc." The development project behind glibc has a long and interesting history which took a new turn with the dissolution of its steering committee on March 26. … …"
- C library system-call wrappers, or the lack thereof = LWN.net - Jonathan Corbet
- Brief: "User-space developers may be accustomed to thinking of system calls as direct calls into the kernel. Indeed, the first edition of The C Programming Language described read() and write() as "a direct entry into the operating system". In truth, user-level "system calls" are just functions in the C library like any other. But what happens when the developers of the C library refuse to provide access to system calls they don't like? … "
- ELF Symbol Versioning - Ulrich Drepper
- Brief: "The symbol versioning implementation used on Linux with glibc 2.1 o up is an extension of Sun's versioning. It provides most of the functionality Sun has plus one decisive new element: symbol-level versioning with multiple definitions of a symbol. The implementation allows every DSO to either use versions for their symbols or not. … …"
- Choosing System C library - Khem Raj (Comcast) - Embedded Linux Conference Europe 2014 Düsseldorf Germany.
- Wikidev - C Library
- Brief: "The C standard library provides string manipulation (string.h), basic I/O (stdio.h), memory allocation (stdlib.h), and other basic functionality to C programs. The interface is described in the C standard, with further additions described in POSIX as well as vendor extensions. On Unix platforms, the library is named libc and is linked automatically into every executable. … ."
- Running new applications on old glibc - (lightofdawn)
- Brief: "Glibc (short for GNU Libc, or GNU C Library) is a library that provides the interface between application programs and the Linux kernel. Although its official name is the "C" library (library for programs written in the "C" language), virtually all dynamically linked program binaries depend on it - it is the de-facto system library in almost all Linux operating systems. … "
- linux - Multiple glibc libraries on a single host - Stack Overflow
- Brief: "Multiple glibc libraries on a single host. My linux (SLES-8) server currently has glibc-2.2.5-235, but I have a program which won't work on this version and requires glibc-2.3.3. Is it possible to have multiple glibcs installed on the same host? This is the error I get when I run my program on the old glibc: …"
- musl libc Wiki [EMBEDDED-SYSTEMS] (Alternative to GLIBC - allows
creating fully statically linked and self-contained binaries)
- Brief: "musl is a C standard library implementation for Linux. This is a wiki maintained by the enthusiastic user community of musl. Some of musl’s major advantages over glibc and uClibc/uClibc-ng are its size, correctness, static linking support, and clean code."
- GitHub - ifduyue/musl - Unofficial mirror of MUSL (CRT)
- musl libc - Projects using musl - List of projects using MUSL libC instead of GLIBC (GNU C Library)
- The Newlib Homepage [EMBEDDED-SYSTEMS] (Alternative to GLIBC)
- Brief: "Newlib is a C library intended for use on embedded systems. It is a conglomeration of several library parts, all under free software licenses that make them easily usable on embedded products. Newlib is only available in source form. It can be compiled for a wide array of processors, and will usually work on any architecture with the addition of a few low-level routines."
- GitHub - hwoarang/uClibc [EMBEDDED-SYSTEMS] (Alternative to GLIBC)
- Brief: "uClibc (aka µClibc/pronounced yew-see-lib-see) is a C library for developing embedded Linux systems. It is much smaller than the GNU C Library, but nearly all applications supported by glibc also work perfectly with uClibc. Porting applications from glibc to uClibc typically involves just recompiling the source code. uClibc even supports shared libraries and threading. It currently runs on standard Linux and MMU-less (also known as µClinux) systems with support for alpha, ARM, cris, e1, h8300, i386, i960, m68k, microblaze, mips/mipsel, PowerPC, SH, SPARC, and v850 processors. …"
System Calls and GlibC:
- Calling System Calls — Glibc and System Calls 1.0 documentation
- glibc and system call wrappers - Florian Weimer, Red Hat Platform Tools Team
- Glibc wrappers for (nearly all) Linux system calls - LWN.net - Jonathan Corbet
- Brief: " … A C programmer working with glibc now would look in vain for for a straightforward way to invoke a number of Linux system calls, including futex(), gettid(), getrandom(), renameat2(), execveat(), bpf(), kcmp(), seccomp(), and a number of others. The only way to get at these system calls is via the syscall() function. Over the years, there have been requests to add wrappers for a number of these system calls; in some cases, such as gettid() and futex(), the requests were summarily rejected by the (at-the-time) glibc maintainer in fairly typical style. …"
- How system calls work in Linux - Nasser M. Abbasi.
Utilities to Patch and modify ELF binaries and other general utilities:
- GitHub - NixOS/patchelf
- Brief: "PatchELF is a simple utility for modifying existing ELF executables and libraries. In particular, it can do the following: Change the dynamic loader ("ELF interpreter") of executables; Change the RPATH of executables and libraries; Shrink the RPATH of executables and libraries."
- GitHub - wheybags/glibc_version_header (Utility for ignoring ELF
symbol version - note: it is not safe).
- Brief: "Build portable Linux binaries, no more linker errors on users' older machines from incompatible glibc versions. Essentially, this is a tool that allows you to specify the glibc version that you want to link against, regardless of what version is installed on your machine. This allows you to make portable Linux binaries, without having to build your binaries on an ancient distro (which is the current standard practice). "
- GitHub - emk/rust-musl-builder
- Brief: ": Docker images for compiling static Rust binaries using musl-libc and musl-gcc, with static versions of useful C libraries. Supports openssl and diesel crates. "
Reports of GLIBC dependency hell (Version mismatch):
1.5.2 The GLIBC Problem
GLIBC - Problem Illustration with a Sample Project
GIST Containing all files used in this experiment:
File: Makefile
build: cmake --config Debug -H. -B_build2 cmake --build _build2 --target
File: CMakeLists.txt
cmake_minimum_required(VERSION 3.9) project(Simple_Cmake_Project) set(CMAKE_CXX_STANDARD 17) set(CMAKE_VERBOSE_MAKEFILE ON) #========== Targets Configurations ============# add_executable( filesys filesys.cpp ) target_link_libraries( filesys stdc++fs)
File: filesys.cpp
#include <iostream> #include <string> #include <iterator> #include <iomanip> #include <filesystem> namespace fs = std::filesystem; template<typename Range, typename Function> auto dotimes(size_t n, Range&& iterable, Function fun) { size_t i = 0; auto it = fs::begin(iterable); auto end = fs::end(iterable); while(i < n && it != end ){ fun(it); ++it; i++; } } int main(){ std::cout << std::boolalpha; std::cout << "\n ===== Listing directory /etc =====" << std::endl; // Show first 10 files of directory /etc dotimes(10, fs::directory_iterator("/etc"), [](auto p){ auto path = p->path(); std::cout << std::left << std::setw(0) << path.filename().string() << " " << std::setw(35) << std::right << std::setw(40) << path << std::endl; }); return EXIT_SUCCESS; }
Steps for reproducing the problem
STEP 1: Get current machine infomation.
- GLIBC Version is: 2.34-2.fc32
# Current machine $ cat /etc/fedora-release Fedora release 32 (Thirty Two) # Current GLIBC Version $ ld --version GNU ld version 2.34-2.fc32 ... ... ... ...
STEP 2: Build the application.
$ git clone https://gist.github.com/0557cd0fa1d5370723b015e443a7c036 gist && cd gist $ cmake --config Debug -H. -B_build1 $ cmake --build _build1 --target $ file _build1/filesys _build1/filesys: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=d1a3628f794c66bcfb29303cfa4a528910f4e3e2 , for GNU/Linux 3.2.0, not stripped
STEP 3: Run in the current machine (newer version of GLIBC)
$ _build1/filesys ===== Listing directory /etc ===== chrony.conf "/etc/chrony.conf" xl2tpd "/etc/xl2tpd" group "/etc/group" profile.d "/etc/profile.d" kde4rc "/etc/kde4rc" resolv.conf.8LUAL0 "/etc/resolv.conf.8LUAL0" geoclue "/etc/geoclue" bash_completion.d "/etc/bash_completion.d" cups "/etc/cups" mime.types "/etc/mime.types"
STEP 4: Attempt to run the binary in a distribution using an older version of GLIBC. The distribution used was a QEMU-KVM virtual machine running MX-Linux 19.2, distribution based on Debian, with GLIBC version .
GLIBC Failure on MX-Linux Virtual Machine:
#---------- Failure!! GLIC Incompa ------------------------------------# $ _build1/filesys _build1/filesys: /lib/x86_64-linux-gnu/libstdc++.so.6: version 'GLIBCXX_3.4.26' not found (required by _build1/filesys)
Virtual Machine Information:
#----------- Machine Info (MX Linux 19.2) -------------# # Kernel version $ uname -r 4.19.0-9-amd64 # Distro name $ cat /etc/issue Welcome to MX 19.2 (patito feo) 64-bit! Powered by Debian. ... ... ... ... ............ .... ... ... .. $ ld --version GNU ld (GNU Binutils for Debian) 2.31.1 ... ... ... ... ... ... ... ... ... ... ... ...
1.5.3 Solution 1 - via linking against older GLIBC
As GLIBC is forward compatible, but not backward compatible. The solution for this issue is to build the application on a system with the oldest possible version of GLIBC. System in this context, means: a Linux virtual machine; a Chroot environment; or Docker image with an older version of GLIBC. Other alternative is to replace GLIBC with MUSL CRT (C Runtime library), but the drawback is that MUSL does not support dynamic loading (_dlopen_, dlsym APIs).
The solution adopted for solving this issue was to use a Docker image based on holy-build-box, which is a Docker image based on Centos 6 Linux distribution containing a modern version of GCC compiler, CMake and an older version of GLIBC.
See:
File: Dockerfile (Available at gist url)
# $ docker run -it --rm -v $PWD:/work -w /work phusion/holy-build-box-64:latest bash FROM phusion/holy-build-box-64:latest ENTRYPOINT [ "/hbb_exe/activate-exec", "bash" ]
Build docker image:
# Run at the directory where is Dockerfile $ docker build -f Dockerfile -t linux-build .
Enter in the docker image shell:
$ docker run --rm -it -v $PWD:/cwd -w /cwd linux-build Holy build box activated Prefix: /hbb_exe CFLAGS/CXXFLAGS: -g -O2 -fvisibility=hidden -I/hbb_exe/include LDFLAGS: -L/hbb_exe/lib -static-libstdc++ STATICLIB_CFLAGS: -g -O2 -fvisibility=hidden -I/hbb_exe/include SHLIB_CFLAGS: -g -O2 -fvisibility=hidden -I/hbb_exe/include SHLIB_LDFLAGS: -L/hbb_exe/lib -static-libstdc++ [root@88c9630a2008 cwd]# ls _build1 CMakeLists.txt Dockerfile filesys.cpp Makefile
Building the application:
[root@88c9630a2008 cwd]# make cmake --config Debug -H. -B_build2 -- Configuring done -- Generating done -- Build files have been written to: /cwd/_build2 cmake --build _build2 --target gmake[1]: Entering directory '/cwd/_build2' /hbb/bin/cmake -S/cwd -B/cwd/_build2 --check-build-system CMakeFiles/Makefile.cmake 0 ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... [root@88c9630a2008 cwd]# ls _build2 CMakeCache.txt CMakeFiles cmake_install.cmake filesys Makefile
Running the application _build2/filesys in MX-Linux:
# Built on Fedora 32 with newer version of GLIBC [FAILURE] $ _build1/filesys _build1/filesys: /lib/x86_64-linux-gnu/libstdc++.so.6: version 'GLIBCXX_3.4.26' not found (required by _build1/filesys) # Built on Centos6 docker with older version of GLIBC [OK, WORKS] #-------------------------------------- $ _build2/filesys ===== Listing directory /etc ===== .java "/etc/.java" .pwd.lock "/etc/.pwd.lock" ImageMagick-6 "/etc/ImageMagick-6" NetworkManager "/etc/NetworkManager" UPower "/etc/UPower" X11 "/etc/X11" acpi "/etc/acpi" adduser.conf "/etc/adduser.conf" adjtime "/etc/adjtime" alsa "/etc/alsa"
1.5.4 Solution 2 - via linking against MUSL libC
Other alternative to avoid the GLIBC dependency problem is to build the application statically linking against MUSL - LibC which is an CRT (C runtime library), initially developed for embedded systems, but that has become an alternative to GLIBC. The easiest way to build statically linking against MUSL is to use an Alpine docker image:
- Note: CRT - is a central component of Unix-like operating systems. It encapsulates system-calls and other operating system services for user-space applications.
See:
- Musl - Wikipedia
- Musl - Debian
- Static Binaries for a C++ Application - ArangoDB
- MUSL support for fully static binaries - Rust Docs
- Comparison of C/POSIX standard library implementations for Linux
- Elinux/Linaro - Bionic and musl - room for cooperation?
File: MuslBuilder.docker (Available at gist)
FROM alpine:latest RUN apk add musl cmake make g++
Building:
Buildilg the docker containing MUSL and development tools:
$ git clone https://gist.github.com/0557cd0fa1d5370723b015e443a7c036 && cd gist
$ docker build -f MuslBuilder.docker -t musl-builder .
Running a container of this docker image:
$ docker run -it --rm -v $(pwd):/cwd -w /cwd -e UID=$(id -u) -e GID=$(id -g) musl-builder /cwd $ cmake --config Debug -H. -B_build-musl -DCMAKE_EXE_LINKER_FLAGS="-static -Os" /cwd $ cmake --build _build-musl --target
Exiting from the container and check the executable:
$ du -h _build-musl/filesys 8.7M _build-musl/filesys 8.7M total $ file _build-musl/filesys _build-musl/filesys: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV) , statically linked, with debug_info, not stripped # Check dependencies $ ldd _build-musl/filesys statically linked
Attempt to run from MX-Linux 19.2 (Older GLIBC)
$ _build-musl/filesys ===== Listing directory /etc ===== .java "/etc/.java" .pwd.lock "/etc/.pwd.lock" ImageMagick-6 "/etc/ImageMagick-6" NetworkManager "/etc/NetworkManager" UPower "/etc/UPower" X11 "/etc/X11" acpi "/etc/acpi" adduser.conf "/etc/adduser.conf" adjtime "/etc/adjtime" alsa "/etc/alsa"
1.5.5 Portable binary using Golang
Applications built with GO programming language are not affect by GLIBC compatibility problems since the GO compiler implementation performs system calls without relying on the GLIBC. This feature allows deploying GO binaries in any Linux distribution without worrying about GLIBC binary compatibility.
Further Reading
- How does Go make system calls? - Stack Overflow
- The Go runtime scheduler's way of dealing with system calls | Hacker News
- https://utcc.utoronto.ca/~cks/space/blog/programming/GoSchedulerAndSyscalls][Chris's Wiki">blog/programming/GoSchedulerAndSyscalls]]
- go - Does golang depend on c runtime? - Stack Overflow
- OpenBSD system-call-origin verification - LWN.net
- all: stop using direct syscalls on OpenBSD · Issue #36435 · golang/go · GitHub
- src/syscall/ - The Go Programming Language (GOLang Source Code - Syscalls)
- /dev/dump: On Go, Portability, and System Interfaces
- Notes on the Go 1.4 Run-Time - Francisco J. Ballesteros - TR LSUB-15-2 (rev 1)
- "This TR contains notes about the Go run time as of Go 1.4. They are intended to aid in the construction of a kernel for Clive. The description here will be updated in the future without publising a new TR, the TR revision number can be used to see if the version is up to date or not."
Demonstration
File: goapp.go
package main import ( "fmt" "io/ioutil" "net/http" _ "bufio" _ "log" ) func main() { fmt.Println(" => Started Ok") // ------- List root directory ---------// fmt.Println(" ---- List Root Directory ------") // No error handler for the sake of breviety. files, _ := ioutil.ReadDir("/") for _, fn := range files { fmt.Println(" => " + fn.Name() ) } // ---- Http Request ------------------// fmt.Println(" ------- Http Request -------------") resp, _ := http.Get("https://www.httpbin.org/get") body, _ := ioutil.ReadAll(resp.Body) fmt.Println(string(body)) }
Compile sample GO application:
# Compiled $ go build goapp.go # Remove debug symbols $ strip goapp # Get file size in megabytes $ >> du -h goapp 4.9M goapp # Check binary $ >> file goapp goapp: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, Go BuildID=rXh5UDymAYyqH6QHp4N5/KktyZonaNBes2MLq_8eV/4lmCJtW64uAqpYg_AIb_/5S4-otZPFzRnjGyq3bBL , not stripped # Check shared libraries dependencies $ >> ldd goapp linux-vdso.so.1 (0x00007ffee9b99000) libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fc87c0c6000) libc.so.6 => /lib64/libc.so.6 (0x00007fc87befc000) /lib64/ld-linux-x86-64.so.2 (0x00007fc87c10d000)
Run application in Fedora 32 distribution (Newer version of GLIBC):
$ >> ./goapp => Started Ok ---- List Root Directory ------ => .autorelabel => bin => boot => dev => etc ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... => usr => var ------- Http Request ------------- { "args": {}, "headers": { "Accept-Encoding": "gzip", "Host": "www.httpbin.org", "User-Agent": "Go-http-client/2.0", "X-Amzn-Trace-Id": "Root=2-5f5650e9-4830a22ac821b598c22defbc" }, "url": "https://www.httpbin.org/get" }
Run application in MX-Linux distribution, based on Debian (Older version of GLIBC):
$ ./goapp => Started Ok ---- List Root Directory ------ => .cache => .config => .fehbg => bin => boot ... ... ... ... .... ... ... ... ... .... ... ... ... ... ... .... ... ... ... ... .... ... => var => vmlinuz ------- Http Request ------------- { "args": {}, "headers": { "Accept-Encoding": "gzip", "Host": "www.httpbin.org", "User-Agent": "Go-http-client/2.0", "X-Amzn-Trace-Id": "Root=1-5f56523a-6cfd5015acb4421e26947afe" }, "url": "https://www.httpbin.org/get" }
1.6 Fully Statically linked executables for embedded Linux systems
1.6.1 Overview
This sections presents a procedure for cross-compiling a fully statically linked executable, without any dependency, that can be deployed on any ARMV7-based Embedded Linux system, including Beaglebone Black development board and Android (Armv7 based processor).
This procedure uses muslcc/i686:arm-linux-musleabi docker image which contains a cross-compiling toolchain capable of compiling C or C++ applications for Linux kernel running on Armv7 based processors. This toolchain also uses Musl libC, instead of glibC (GNU C runtime library). Musl allows building fully statically applications that does not rely on any system dependency. As a result, applications, built with Musl, can be run on any Linux distribution without being affected by GNU GlibC mismatch problems which arises when an application or object-code, that was built linked against an newer version of GlibC, is run on a system using an older version of GlibC resulting in a runtime linking error.
References and reading:
- https://hub.docker.com/r/muslcc/i686/tags
- More toolchains at: https://elinux.org/Toolchains
- CMake - Fwd: Save stripped debugging information
- How to remove unused C/C++ symbols with GCC and ld? - Stack Overflow
- Transparently running binaries from any architecture in Linux with QEMU and binfmt_misc – Own your bits
1.6.2 Project Files
All files are available at:
File: Dockerfil.docker
FROM muslcc/i686:armv7l-linux-musleabihf RUN apk add make cmake # ENTRYPOINT ["cmake"]
File: Makefile
- Makefile for running project commands and documenting the building process.
DOCKER_COMMAND=docker run -e UID=$(shell id -u) -e GID=$(shell id -g) --volume=$(shell pwd):/cwd --workdir=/cwd musl-armv7 # Builds docker image: musl-armv7 docker: docker build -f Dockerfile.docker -t musl-armv7 . # Build testing application build: ${DOCKER_COMMAND} cmake -H. -B_build-armv7 -DCMAKE_BUILD_TYPE=Debug ${DOCKER_COMMAND} cmake --build _build-armv7 --target clean: rm -rf -v _build-armv7
File: CMakeLists.txt
cmake_minimum_required(VERSION 3.9) project(embedded-linux-armv7) set(CMAKE_CXX_STANDARD 17) set(CMAKE_VERBOSE_MAKEFILE ON) #====== Global compiler and linker settings ======# # (-static) => Fully statically link Musl-LibC # (-Wl,--gc-sections) => Remove unused code # (requires: -fdata-sections and -ffunction-sections) set(CMAKE_EXE_LINKER_FLAGS "-static -Wl,--gc-sections") # Separate data and code sections in order to reduce binary size. add_definitions( -fdata-sections -ffunction-sections ) # Copy target executable, for instance my-exe to my-exe.debug # and strip debugging symbols from my-exe. # # Reference: https://cmake.org/pipermail/cmake/2011-October/046743.html macro(STRIP_DEBUG_SYMBOLS target) ADD_CUSTOM_COMMAND(TARGET ${target} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:${target}> ${CMAKE_BINARY_DIR}/${target}.debug COMMAND ${CMAKE_STRIP} -g $<TARGET_FILE:${target}> ) endmacro() #========== Targets Configurations ============# add_executable( app portable-app.cpp ) target_link_libraries( app stdc++fs) STRIP_DEBUG_SYMBOLS( app )
File: portable-app.cpp
#include <iostream> #include <string> #include <algorithm> #include <iomanip> #include <filesystem> #include <fstream> #include <cassert> namespace fs = std::filesystem; int main(int argc, char** argv) { assert( argc >= 2 && "Supposed to have at least one arguments"); std::cout << std::boolalpha; std::string command = argv[1]; if(command == "version") { auto ifs = std::ifstream("/proc/version"); assert( ifs.good() && "File supposed to exist" ); std::cout << ifs.rdbuf(); return EXIT_SUCCESS; } std::cout << "\n ===== Listing directory /etc =====" << std::endl; if(command == "list") { // Show first 10 files of directory /etc auto iter = fs::directory_iterator(argv[2]); std::for_each(fs::begin(iter), fs::end(iter) ,[](auto p){ auto path = p.path(); std::cout << std::left << std::setw(0) << path.filename().string() << " " << std::setw(35) << std::right << std::setw(40) << path << std::endl; }); return EXIT_FAILURE; } return EXIT_SUCCESS; }
1.6.3 Building and running
STEP 1: Downloading the source code.
$ git clone https://gist.github.com/b411be3b2a4364c7d4de18edd37da6a3 musl-armv7 && cd musl-armv7
STEP 2: Building the docker image:
$ docker build -f Dockerfile.docker -t musl-armv7 . Sending build context to Docker daemon 7.168kB Step 1/2 : FROM muslcc/i686:armv7l-linux-musleabihf ---> 4141d3dcca3f Step 2/2 : RUN apk add make cmake ---> Using cache ---> c18ba0aa2335 Successfully built c18ba0aa2335 Successfully tagged musl-armv7:latest
STEP 3: Cross-compile the sample application:
Cross-compiling from command line:
# Run the docker shell (sh) $ docker run --interactive --tty -e UID=$(id -u) -e GID=$(id -g) --volume=$(pwd):/cwd --workdir=/cwd musl-armv7 /cwd # # Build the application /cwd $ cmake -H. -B_build-armv7 -DCMAKE_BUILD_TYPE=Debug /cwd $ cmake --build _build-armv7 --target # Exit the docker shell /cwd $ cmake --build _build-armv7 --target
Cross-compile in a single step using the helper Makefile:
$ make build
Check the compiled executables:
$ ls _build-armv7/ CMakeFiles/ app* app.debug* CMakeCache.txt cmake_install.cmake Makefile # Version with debug symbols $ file _build-armv7/app _build-armv7/app: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, not stripped $ du -h _build-armv7/app 736K _build-armv7/app 736K total # Version without debugging symbols $ file _build-armv7/app.debug _build-armv7/app.debug: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV) , statically linked, with debug_info, not stripped $ du -h _build-armv7/app.debug 9.4M _build-armv7/app.debug 9.4M total
STEP 4: Attempt to run the built executable on different machines.
Attempt to run the previous executables on the host machine (x86-64 processor - 64 bits):
$ _build-armv7/app bash: _build-armv7/app: cannot execute binary file: Exec format error $ _build-armv7/app.debug bash: _build-armv7/app.debug: cannot execute binary file: Exec format error
Run ARMv7 Linux binary on a x86-64 host machine using QEMU emulator: (OwnYourBits)
# Install Fedora Packages $ sudo dnf install -y qemu-user.x86_64 qemu-user-static.x86_64 # Run the Armv7 binary using QEMU-user $ qemu-arm ./_build-armv7/app version Linux version 5.8.4-200.fc32.x86_64 (mockbuild@bkernel01.iad2.fedoraproject.org) ... ... $ qemu-arm ./_build-armv7/app list / ===== Listing directory /etc ===== srv "/srv" sys "/sys" opt "/opt" run "/run" media "/media" bin "/bin" .autorelabel "/.autorelabel" root "/root" var "/var" etc "/etc" tmp "/tmp"
Run application in Beaglebone Black (Armv7) embedded linux development board, similar to Raspberry-Pi, but with more IO ports, peripherals and ADC analog-to-digital converters:
debian@beaglebone:~$ file app app: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, not stripped debian@beaglebone:~$ du -h app 736K app debian@beaglebone:~$ file app app: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, not stripped debian@beaglebone:~$ debian@beaglebone:~$ ./app Assertion failed: argc >= 2 && "Supposed to have at least one arguments" (/cwd/portable-app.cpp: main: 14) Aborted debian@beaglebone:~$ debian@beaglebone:~$ ./app version Linux version 4.14.71-ti-r80 (root@b2-am57xx-beagle-x15-2gb) (gcc version 6.3.0 20170516 (Debian 6.3.0-18+deb9u1)) #1 SMP PREEMPT Fri Oct 5 23:50:11 UTC 2018 debian@beaglebone:~$ debian@beaglebone:~$ ./app list / ===== Listing directory /etc ===== sys "/sys" opt "/opt" sbin "/sbin" home "/home" dev "/dev" nfs-uEnv.txt "/nfs-uEnv.txt" usr "/usr" bin "/bin" etc "/etc" proc "/proc" boot "/boot" ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
Run in Android Armv-7 phone via ADB (Android Debug Bridge):
# Transver application to phone via ADB $ adb push _build-armv7/app.debug /data/local/tmp _build-armv7/app.debug: 1 file pushed. 11.1 MB/s (9793936 bytes in 0.842s) # Enter ADB shell and run the application. $ >> adb shell bali:/ $ cd /data/local/tmp bali:/data/local/tmp $ file app.debug app.debug: ELF executable, 32-bit LSB arm, static, not stripped bali:/data/local/tmp $ du -h app.debug 9.3M app.debug 134|bali:/data/local/tmp $ ./app.debug version Linux version 4.4.146+ (jenkins@jenkins-bali-wxg) ... ... bali:/data/local/tmp $ 1|bali:/data/local/tmp $ ./app.debug list /dev ===== Listing directory /etc ===== wmtWifi "/dev/wmtWifi" stpgps "/dev/stpgps" fm "/dev/fm" stpbt "/dev/stpbt" stpwmt "/dev/stpwmt" wmtdetect "/dev/wmtdetect" radio "/dev/radio" ttyGS3 "/dev/ttyGS3" ttyGS2 "/dev/ttyGS2" ttyGS1 "/dev/ttyGS1" ttyGS0 "/dev/ttyGS0" mtp_usb "/dev/mtp_usb" usb_accessory "/dev/usb_accessory" ... ... ... ... ... ... ... ... ... ... ... ...
1.7 Creating command line applications with CLI11 C++ Library
1.7.1 Overview
Despite that GUI Graphical user interfaces applications are more used nowadays than CLI command line applications, it still worth developing CLI applications. Among other things, the benefits of CLI applications over GUI can be summarized as:
- More precision
- They are more precise than GUI for performing task. It is easier to guide someone by showing the command line steps rather than describing all buttons, menus, context menus that someone should click in order to accomplish some task.
- Scriptability
- CLI applications are scriptable and they can be automated; called from other applications or scripts.
- Reproducibility
- Unlike GUIs, CLI applications are reproducible and the sequence of steps for performing some task can be stored as text and later replayed by pasting the commands in a terminal or running them from a script.
- They can be combined
- Unlike GUI, CLI applications can be combined with each other via stdout, stderr redirection. Example: CLI tools such as 'grep', 'echo', 'ls', 'find', 'awk', 'sed' are often combined with each other in shell scripts for system configuration or automation.
Anotomy of CLI Applications
Simple command line application:
$ ./<COMMAND> <P0> <P1> ... <PN> --flag0 --flag1 --switch0 value0 --switch1 value1 --switch value2 ... # Or: $ ./<COMMAND> <P0> <P1> ... <PN> \ --flag0 --flag1 \ --switch0 value0 --switch1 value1 --switch value2 ... $ ./<COMMAND> <P0> <P1> ... <PN> --flag0 --flag1 \ --switch0=value0 --switch1=value1 --switch=value2 ...
Command line application with multiple subcommands: (i.e: git, docker)
$ ./<COMMAND> <SUBCOMMAND> <P0> <P1> ... <PN> \ --flag0 --flag1 \ --switch0 value0 --switch1 value1 --switch value2 ... # OR: $ ./<COMMAND> <SUBCOMMAND> <P0> <P1> ... <PN> \ --flag0 --flag1 \ --switch0=value0 --switch1=value1 --switch=value2 ...
Where:
- <COMMAND>
- Appplication or executable.
- <SUBCOMMAND>
- Metaphor: an action or verb performed on subjects (positional arguments).
- Subcommand, similar to 'git checkout' or'docker run'. A subcommand or command represents an
- <P0>, <P1>, …, <PN>
- Metaphor: subjects
- Positional arguments (without '-' dash prefix) - they are essential (required) arguments for running an application. If they are not provided, an error happens.
- -flag0 -flag1 ….
- Metaphor: adjectives
- A flag is an optional command line switch which represents a boolean value. The usage of a flag sets the represented value as true and absence, sets the represented value as false.
- –switch0=value0 –switch1=value1 ….
- Metaphor: adjectives
- Switches are optional command line arguments associated to some program state.
Example - Breaking down command line parts:
$ docker run -i -t --rm --volume=$PWD:/cwd -w=/cwd xdxd/docker-binwalk bash
- Command:
- docker
- Sub-command:
- run
- Positional arguments:
- (xdxd/docker-binwalk) and (bash)
- Flags:
- (-i), (-t), (–rm)
- Switches:
- (–volume), (-w)
General recommendations
- DO ONLY ONE THING => The application should only do one thing. If it needs to perform more than one task, the best choice is to create a subcommand for every task.
- STATUS CODE => The status code allows scripts or any other
application calling the current one to know whether the process
execution was successful.
- => Return status code '0' (EXIT_SUCCESS) when the applications is terminated successfully
- => Return any other value different than zero in case of failure.
- DEFAULT VALUES
- Use reasonable default values for command line switches values and flags.
- Show the default values of command line switches
- EXAMPLES
- Add commands that shows usage examples.
- SIMPLE TASKS SHOULD BE SIMPLE
- Create subcommands for the most common use-cases for making simple tasks simple: make simple things simple and complex things possible (borrowed from Alan Kay).
- STDERR - for non essential information
- Print non essential, logging or debug information to stderr, standard error output as it allows discarding separating the stdout (standard output) from stderr, redirecting stderr to file or discarding it by redirecting stderr to /dev/null.
- VERSION (–version)
- Add switch for printing application version.
- DRY-RUN (–dry-run)
- Create a flag option (–dry-run) if the command performs any permanent non-reversible change, such as remove file. When the application receives the –dry-run option, the application only shows the non-reversible actions that would be executed, without actually performing them.
Subcommand for common tasks (Simple tasks should be simple):
For instance, an alternative docker client could have a subcommand
'shell' for just running the shell as entrypoint. $ dockw shell IMAGE could
be translated as the following docker command line. The subcommand
'shell' could assume that the current directory ($PWD) is mounted to
the directory /cwd in the docker image and this option could be
changed using the command line switch --workdir=<SOME_DIR>
.
# 'dockw shell MY_DOCKER_IMAGE ' subcommand from a hypothetical dockw tool # is translated as: $ docker run --rm -it -v $PWD:/cwd -w /cwd --entrypoint=sh MY_DOCKER_IMAGE
1.7.2 Sample application with multiple sub-comamnds
The following code contains a sample CLI application containing multiple sub-commands:.
GIST:
CLI11 Library Documentation:
- Introduction · CLI11 Tutorial
- Subcommands and the App · CLI11 Tutorial
- CLI11: Command line parsing made simple
Files
File: CMakeLists.txt
cmake_minimum_required(VERSION 3.9) project(cliapp) #========== Global Configurations =============# #----------------------------------------------# set(CMAKE_CXX_STANDARD 17) set(CMAKE_VERBOSE_MAKEFILE ON) set(CMAKE_CXX_EXTENSIONS OFF) # ------------ Download CPM CMake Script ----------------# ## Automatically donwload and use module CPM.cmake file(DOWNLOAD https://raw.githubusercontent.com/TheLartians/CPM.cmake/v0.26.2/cmake/CPM.cmake "${CMAKE_BINARY_DIR}/CPM.cmake") include("${CMAKE_BINARY_DIR}/CPM.cmake") #----------- Add dependencies --------------------------# CPMAddPackage( NAME cli11 URL https://github.com/CLIUtils/CLI11/archive/v1.9.0.zip DOWNLOAD_ONLY YES ) include_directories( ${cli11_SOURCE_DIR}/include ) message([TRACE] " cli11_SOURCE_DIR = ${cli11_SOURCE_DIR} ") #----------- Set targets -------------------------------# add_executable(cliapp cliapp.cpp) target_link_libraries(cliapp stdc++fs) install( TARGETS cliapp RUNTIME DESTINATION bin)
File: cliapp.cpp
#include <iostream> #include <string> #include <vector> #include <functional> #include <filesystem> #include <functional> #include <CLI/CLI.hpp> using namespace std::string_literals; namespace fs = std::filesystem; struct Command_list { std::string m_path = "."; bool m_filter_file = false; bool m_filter_dir = false; bool m_recursive = false; Command_list(CLI::App& app, std::string name) { this->install(app, name); } void install(CLI::App& app, std::string name) { CLI::App* cmd = app.add_subcommand( name, "List directory" ); cmd->callback([this](){ try { this->list_directory(); // Status code 0 => OK finished successfuly return true; } catch(std::exception const& ex) { std::cout << " Error: " << ex.what() << '\n'; return false; } }); cmd->add_option("<PATH>", m_path, "Directory to be listed." )->required(); cmd->add_flag("-f,--file", m_filter_file, "Only list files"); cmd->add_flag("-d,--directory", m_filter_dir, "Only list directories"); cmd->add_flag("-r,--recursive", m_recursive, "List direcotyr in recursive way"); } private: void list_directory() { if(m_filter_file && m_filter_dir) throw std::runtime_error("Error: --file and --dir flags cannot be simultaneously true."); using Predfun = std::function<bool (fs::path const&)>; auto predicate = Predfun{}; auto iterate_dir = [&predicate](auto&& iterable){ for(auto const& path : iterable ) { if(predicate(path)) std::printf(" => %s\n", path.path().c_str()); } }; using ptr = bool (*) (fs::path const&); if(!m_filter_file && !m_filter_dir) predicate = [](fs::path const& p){ return true; }; if(m_filter_file) predicate = (ptr) &fs::is_regular_file; if(m_filter_dir) predicate = (ptr) &fs::is_directory; if(!m_recursive) iterate_dir( fs::directory_iterator(m_path) ); else iterate_dir( fs::recursive_directory_iterator(m_path) ); } }; class MiniWebServer { int m_port = 8080; std::string m_host = "0.0.0.0"; std::string m_path = "."; bool m_auth = false; public: // Set server TCP Port void set_port(int port ){ m_port = port; } // Hostnames that server will listen to void set_host(std::string host ){ m_host = host; } // Set path containing server data void set_path(std::string path ){ m_path = path; } void set_auth(bool flag ){ m_auth = flag; } int get_port(){ return m_port; } void run_server() { std::printf(" [INFO] Running web server => port = %d ; host = %s; path = %s \n" , m_port, m_host.c_str(), m_path.c_str()); std::printf(" [INFO] Server has authentication => %s \n", m_auth ? "TRUE" : "FALSE" ); } }; template<typename T, typename Klass> auto bind_setter(Klass& cls, void (Klass::* setter) (T&&) ) { return [&](T&& q){ (cls.*setter)( std::forward(q) ); }; } template<typename T, typename Klass> auto bind_setter(std::shared_ptr<Klass> const& cls, void (Klass::* setter) (T) ) { return [cls, setter](T q){ ((*cls).*setter)(q); }; } void command_server( CLI::App& app) { CLI::App* cmd = app.add_subcommand( "server", "Run HTTP server for sharing files from some folder." ); cmd->footer("Run local file sharing web server"); // Created with shared_ptr in order to the object survive this scope // and avoid reference to destroyed object when it is referenced from callback. auto server = std::make_shared<MiniWebServer>(); cmd->callback([=] { std::cout << " [TRACE] ----- Callback invoked OK ------ \n"; if(server->get_port() < 0 || server->get_port() > 65535) { std::cerr << " [ERROR] " << " Invalid TCP port range. " << '\n'; // Returns non-zero status code return false; } server->run_server(); return true; }); cmd->add_option_function<std::string>( "<PATH>" , bind_setter<std::string>(server, &MiniWebServer::set_path) , "Directory to be shared in the web server." )->required(); cmd->add_option_function<int>( "-p,--port" , [=](auto q){ server->set_port(q); } , "Server TCP port, default: 8080" ); cmd->add_option_function<std::string>( "--host" , [=](auto q){ server->set_host(q); } , "Hostname that server will listen to (default: 0.0.0.0)" ); cmd->add_flag( "-a,--auth" // , [](auto q){ server->set_auth(q); } , bind_setter<bool>(server, &MiniWebServer::set_auth) , "Shows the command line parameters passed to QEMU." ); } void command_version( CLI::App& app, std::string name) { auto cmd = app.add_subcommand(name, "Show application version."); cmd->callback([=]{ std::printf("cliapp version 0.1 - Your favorite U-NIX swiss army knife \n"); // Return true for idnicating successful status code return true; }); } int main(int argc, char** argv) { CLI::App app("cliapp"); app.footer("\n Command line demo toolbox."); command_version(app, "version"); command_version(app, "v"); Command_list cmd_list1(app, "ls"); Command_list cmd_list2(app, "list"); command_server(app); // ----------- Parse Arguments ---------------// try { if(argc == 1){ std::cout << app.help() << "\n"; return 0; } app.require_subcommand(); app.validate_positionals(); app.parse(argc, argv); } catch(const CLI::ParseError &e) { return app.exit(e); } catch (std::exception& ex) { std::cout << ex.what() << std::endl; return EXIT_FAILURE; } return 0; }
Building on MacOSX
Install GCC 9.0 via brew. Note: Apple's default Clang compiler does not have the C++17 file system library.
$ brew install gcc@9
Get the code:
$ git clone https://gist.github.com/caiorss/1b6826b9722ba07667eefed2f82d667e src $ cd src
Override CMake default compiler:
$ export CC=/usr/local/Cellar/gcc@9/9.3.0/bin/gcc-9 $ export CXX=/usr/local/Cellar/gcc@9/9.3.0/bin/g++-9
Configuration step:
$ cmake --config Release -H. -B_build ... .... ... ... .. ... ... -- Checking whether CXX compiler supports OSX deployment target flag - yes -- Check for working CXX compiler: /usr/local/Cellar/gcc@9/9.3.0/bin/g++-9 -- Check for working CXX compiler: /usr/local/Cellar/gcc@9/9.3.0/bin/g++-9 - works -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Detecting CXX compile features -- Detecting CXX compile features - done -- CPM: adding package cli11@ () [TRACE] cli11_SOURCE_DIR = /Volumes/data/cliapp/_build/_deps/cli11-src -- Configuring done -- Generating done -- Build files have been written to: /Volumes/data/cliapp/_build
Building step:
$ cmake --build _build --target all ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... [ 50%] Building CXX object CMakeFiles/cliapp.dir/cliapp.cpp.o /usr/local/Cellar/gcc@9/9.3.0/bin/g++-9 -I/Volumes/data/cliapp/_build/_deps/cli11-src/include -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk -std=c++17 -o CMakeFiles/cliapp.dir/cliapp.cpp.o -c /Volumes/data/cliapp/cliapp.cpp [100%] Linking CXX executable cliapp /usr/local/Cellar/cmake/3.17.3/bin/cmake -E cmake_link_script CMakeFiles/cliapp.dir/link.txt --verbose=1 /usr/local/Cellar/gcc@9/9.3.0/bin/g++-9 -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk -Wl,-search_paths_first -Wl,-headerpad_max_install_names CMakeFiles/cliapp.dir/cliapp.cpp.o -o cliapp -lstdc++fs [100%] Built target cliapp /usr/local/Cellar/cmake/3.17.3/bin/cmake -E cmake_progress_start /Volumes/data/cliapp/_build/CMakeFiles 0
Check executable:
$ file _build/cliapp
_build/cliapp: Mach-O 64-bit executable x86_64
Show command line help:
$ _build/cliapp cliapp Usage: [OPTIONS] [SUBCOMMAND] Options: -h,--help Print this help message and exit Subcommands: version Show application version. ls List directory list List directory server Run HTTP server for sharing files from some folder. Command line demo toolbox.
Show help for 'server' subcommands:
$ _build/cliapp server -h Run HTTP server for sharing files from some folder. Usage: _build/cliapp server [OPTIONS] <PATH> Positionals: <PATH> TEXT REQUIRED Directory to be shared in the web server. Options: -h,--help Print this help message and exit -p,--port INT Server TCP port, default: 8080 --host TEXT Hostname that server will listen to (default: 0.0.0.0) -a,--auth Shows the command line parameters passed to QEMU. Run local file sharing web server
Show help for 'ls' subcommand:
$ _build/cliapp ls -h List directory Usage: _build/cliapp ls [OPTIONS] <PATH> Positionals: <PATH> TEXT REQUIRED Directory to be listed. Options: -h,--help Print this help message and exit -f,--file Only list files -d,--directory Only list directories -r,--recursive List direcotyr in recursive way Command line demo toolbox.
Run server subcommand:
$ _build/cliapp server /var/www [TRACE] ----- Callback invoked OK ------ [INFO] Running web server => port = 8080 ; host = 0.0.0.0; path = /var/www [INFO] Server has authentication => FALSE $ _build/cliapp server /var/www --port 9010 --host 127.0.0.1 [TRACE] ----- Callback invoked OK ------ [INFO] Running web server => port = 9010 ; host = 127.0.0.1; path = /var/www [INFO] Server has authentication => FALSE $ _build/cliapp server /var/www --port 9010 --host 127.0.0.1 --auth [TRACE] ----- Callback invoked OK ------ [INFO] Running web server => port = 9010 ; host = 127.0.0.1; path = /var/www [INFO] Server has authentication => TRUE $ _build/cliapp server /var/www --port=9010 --host=127.0.0.1 --auth [TRACE] ----- Callback invoked OK ------ [INFO] Running web server => port = 9010 ; host = 127.0.0.1; path = /var/www [INFO] Server has authentication => TRUE
Run ls subcommand:
$ _build/cliapp ls <PATH> is required Run with --help for more information. $ _build/cliapp ls /System/Applications => /System/Applications/Siri.app => /System/Applications/Music.app => /System/Applications/FindMy.app => /System/Applications/QuickTime Player.app => /System/Applications/Chess.app => /System/Applications/Photo Booth.app => /System/Applications/Books.app ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... $ _build/cliapp ls /System/Applications/Utilities/Terminal.app --file --recursive ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... => /System/Applications/Utilities/Terminal.app/Contents/Resources/ca.lproj/TTFindPanel.strings => /System/Applications/Utilities/Terminal.app/Contents/Info.plist => /System/Applications/Utilities/Terminal.app/Contents/PkgInfo => /System/Applications/Utilities/Terminal.app/Contents/version.plist
1.8 System information via virtual file systems
1.8.1 Overview
The Linux kernel provides two official APIs (Application Programming Interfaces) for user-space applications. The system-calls, that are stable and well documented and often made available via GLIBC (GNU C library). And VSF - virtual file system, which are in-memory pseudo file systems which allow user-space applications to retrieve system information; tune kernel runtime parameters and control device drivers by reading and writing VSF files.
The following code demonstrates, retrives several system information by reading files in Linux's /proc (procfs) and /sys (sysfs) virtual file systems.
Useful files in procfs or (/proc) file system
Some files that contains useful system information are:
- /proc/version
- => Linux Kernel Version
- /proc/cpuinfo
- => Contains information about current CPU, such as vendor; architecture; supported vector (SIMD) extensions; number of cores and so on.
- /proc/mounts (command: $ mounts )
- => Lists all mount points and mounted file systems.
- /proc/partitions
- => Contain all system partitons (sda, sda1, …, sda[N])
- /proc/filesystems
- => Lists all supported file system types by the current kernel.
- /proc/modules (command: $ lsmod)
- => Contains listing of all kernel modules loaded by the kernel. The CLI tool lsmod uses this file to display the loaded modules.
- /proc/meminfo (command: $ free -m)
- => Contains information about current memory.
- /proc/uptime (command: $ uptime)
- => Stores the CPU uptime, how long the machine is running since boot time.
- /proc/loadvg (command: $ uptime)
- => Shows system load average.
- See:
- proc/sys/kernel (Directory) - The tool sysctl, for tweaking
Kernel parameters, manipulates the file in this directory.
- => This directory contains, several files which allows tuning kernel parameters at runtime, by just reading or writing to those files.
- /proc/sys/kernel/hostname
- => Contains current machine hostname, that can be changed by writiing to this file.
- /proc/sys/kernel/ostype
- => Operating system type, always set to "Linux".
- /proc/sys/kernel/osrelease
- => Linux kernel version.
- /proc/sys/kernel/sysrq
- => If this file is set to '1', it allows using the magic SysRQ key makes possible to kill hogging processes when the machine hangs or freeze due to high memory usage.
Further Reading
General:
- proc(5) - Linux manual page - PROCFS - pseudo file sytem.
- 3.3. Directories within proc
- Interface Requirements | Android Open Source Project
- procfs - Gentoo Wiki
- On IDs (Forensic information from Linux machines)
Kernel Runtime Settings (/proc/sys/kernel) and sysctl tool:
- Linux Administration: All you need to know about /proc/sys to manipulate a running kernel
- Kernel panic - LinuxReviews
- How to use Magic SysRq tool in CentOS / RHEL – The Geek Diary
- sysctl - ArchWiki (Manipulates kernel parameters at runtime - directory: /proc/sys/kernel)
- Linux hardening with sysctl settings - Linux Audit
Linux Load Average (/proc/loadavg)
- Linux Load Averages: Solving the Mystery (Brendan Gregg)
- linux /proc/loadavg - Stack Overflow (How to interpret the file /proc/loadavg)
- E.2.15. /proc/loadavg
1.8.2 Sample Code
Code available at:
File: linux-info.cpp
/* Summary: Gets Linux runtime information from VSF virtual file system. * */ #include <iostream> #include <fstream> #include <sstream> #include <string> #include <iomanip> // std::seprecision, std::fixed // See: https://man7.org/linux/man-pages/man2/sysinfo.2.html #include <sys/sysinfo.h> void get_memory_info(); void show_sysinfo1(); void show_uptime(); void show_mount_points(); void show_kernel_runtime_config(); std::string readFile(std::string const& file); int main(int argc, char** argv) { if(argc < 2){ std::cerr << " [ERROR] Usage: $ " << argv[0] << " <COMMAND> " << '\n'; return EXIT_FAILURE; } auto command = std::string{ argv[1] }; if(command == "version"){ std::cout << "===== Kernel Version ====================================" << '\n'; auto version = readFile("/proc/version"); std::cout << " System version = " << version << '\n'; return EXIT_SUCCESS; } if(command == "memory") { std::cout << " ==== Memory (Reported by command $ free -m ) ========" << '\n'; get_memory_info(); return EXIT_SUCCESS; } // Using the header: <sys/sysinfo.h> // Note: this information is reported by the command $ free -m if(command == "sysinfo1") { std::cout << " ===== System Info 1 (Reported by $ free -m and $ uptime ) === " << '\n'; show_sysinfo1(); return EXIT_SUCCESS; } if(command == "uptime") { std::cout << "===== System uptime since boot (Reported by $ uptime) ====" << '\n'; show_uptime(); return EXIT_SUCCESS; } if(command == "load-avg") { std::cout << " ==== System Load Average ================================" << '\n'; auto loadavg = readFile("/proc/loadavg"); std::cout << "Load average = " << loadavg << '\n'; return EXIT_SUCCESS; } if(command == "mount") { std::cout << " ====== System mount points ( Reported by command $ mount ) ===== " << '\n'; show_mount_points(); return EXIT_SUCCESS; } if(command == "sysctl") { std::cout << " ===== Kernel Runtime Information (Reported by $ sysctl ) ======= " << '\n'; show_kernel_runtime_config(); return EXIT_SUCCESS; } if(command == "dmesg") { std::cout << " ===== Kernel debug message (Reported by $ dmesg ) ==========" << '\n'; auto ifs = std::ifstream("/dev/kmsg"); std::string line; while( std::getline(ifs, line) ){ std::cout << line << '\n'; } } if(command == "cpuinfo") { std::cout << "======= CPU Information =====================================" << '\n'; auto ifs = std::ifstream("/proc/cpuinfo"); std::string line; while( std::getline(ifs, line) ){ std::cout << line << '\n'; } } return EXIT_SUCCESS; } void get_memory_info() { constexpr const char* meminfo_file = "/proc/meminfo"; // factor = 1 Gigabytes = 1024 * 1024 kbytes constexpr double factor = 1024 * 1024; auto ifs = std::ifstream{meminfo_file}; if(!ifs.good()){ throw std::runtime_error("Error: unable to memory-info file."); } std::string line, label; std::uint64_t value; while( std::getline(ifs, line) ) { std::stringstream ss{line}; ss >> label >> value; if(label == "MemTotal:") std::cout << " Total memory (Gb) = " << (value / factor) << '\n'; if(label == "MemAvailable:") std::cout << " Memory Available (Gb) = " << (value / factor) << '\n'; } } void show_sysinfo1() { struct sysinfo info; ::sysinfo(&info); // 1 Gigabyte = 1024 megabytes = 1024 * 1024 kbytes = 1024 * 1024 * 1024 bytes; constexpr double factor = 1024 * 1024 * 1024; constexpr std::uint64_t one_day_to_seconds = 24 * 60 * 60; std::cout << " [*] System uptime since boot (seconds) = " << info.uptime << '\n' << " [*] System uptime since boot (days) = " << info.uptime / one_day_to_seconds << '\n' << " [*] Total RAM memory (Gb) = " << info.totalram / factor << '\n' << " [*] Free RAM memory (Gb) = " << info.freeram / factor << '\n' << " [*] Total SWAP (Gb) = " << info.totalswap / factor << '\n' << " [*] Free SWAP (Gb) = " << info.freeswap / factor << '\n' << " [*] Number of processes running = " << info.procs << '\n' << '\n'; } // Reported by command $ uptime void show_uptime() { auto ifs = std::ifstream("/proc/uptime"); if( !ifs.good() ){ throw std::runtime_error("Error: unable to open uptime file "); } double seconds; ifs >> seconds; uint64_t factor = 24 * 60 * 60; std::cout << " Uptime in hours = " << seconds / ( 60 * 60) << '\n'; std::cout << " Uptime in days = " << seconds / factor << '\n'; } void show_kernel_runtime_config() { auto show_field= [](std::string const& label, std::string const& file) { std::cout << " [*] " << std::setw(40) << std::right << (label + " => ") << std::setw(50) << std::left << readFile(file) << '\n'; }; std::cout << '\n'; show_field("Machine Hostname", "/proc/sys/kernel/hostname"); show_field("Kernel Version", "/proc/sys/kernel/osrelease"); show_field("Os type", "/proc/sys/kernel/ostype"); show_field("Boot unique ID identifier", "/proc/sys/kernel/random/boot_id"); show_field("Date which kernel was compiled", "/proc/sys/kernel/version"); show_field("Sysrq enabled (Emergency keys)", "/proc/sys/kernel/sysrq"); show_field("Max number of Cgroups namespaces", "/proc/sys/user/max_cgroup_namespaces"); show_field("Max number of Mount namespaces", "/proc/sys/user/max_net_namespaces"); } void show_network_interfaces() { throw std::runtime_error("Error: not implemented yet."); } // Show mounted file systems, but ignore 'cgroup', 'overlay', 'fusectl' void show_mount_points() { auto ifs = std::ifstream("/proc/mounts"); if(!ifs){ throw std::runtime_error("Error: unable to open file /proc/mounts"); } std::cout << '\n'; std::cout << " " << std::setw(20) << std::left << "File system" << std::setw(20) << std::left << "Type" << std::setw(10) << std::left << "Mount point" << '\n'; std::cout << " " << std::setw(20) << std::left << "------------" << std::setw(20) << std::left << "----" << std::setw(10) << std::left << "-----------" << '\n'; std::string line, fsystem, type, mount_directory; while( std::getline(ifs, line) ) { auto ss = std::stringstream(line); ss >> fsystem >> mount_directory >> type; if( type != "cgroup" && type != "overlay" && type != "cgroup2") { std::cout << " " << std::setw(20) << std::left << fsystem << std::setw(20) << std::left << type << std::setw(10) << std::left << mount_directory << '\n'; } } } // Requires: <string>, <stream>, <sstream> std::string readFile(std::string const& file) { std::ifstream is(file); if( !is.good() ){ throw std::runtime_error("Error: stream has errors."); } std::stringstream ss; ss << is.rdbuf(); std::string m; // Remove ending line character '\n' or '\r\n'. std::getline(ss, m); return m; }
1.8.3 Running
Building:
$ g++ linux-info.cpp -o linux-info -std=c++1z -Wall -Wextra -g
Running (Tested on Linux distribution PopOS Live CD running in a virtual machine)
Get Kernel Version:
$ >> ./linux-info [ERROR] Usage: $ ./linux-info <COMMAND> $ >> ./linux-info version # Get Kernel version ===== Kernel Version ==================================== System version = Linux version 5.4.0-7634-generic (buildd@lcy01-amd64-003) (gcc version 9.3.0 (Ubuntu 9.3.0-10ubuntu2)) #38~1591219791~20.04~6b1c5de-Ubuntu SMP Thu Jun 4 02:56:10 UTC 2 $ >>
Get RAM memory available (equivalent to $ free -m)
$ >> ./linux-info memory # RAM memory ==== Memory (Reported by command $ free -m ) ======== Total memory (Gb) = 1.9175 Memory Available (Gb) = 1.01926
System uptime and RAM memory available:
$ >> ./linux-info sysinfo1 ===== System Info 1 (Reported by $ free -m and $ uptime ) === [*] System uptime since boot (seconds) = 760 [*] System uptime since boot (days) = 0 [*] Total RAM memory (Gb) = 1.9175 [*] Free RAM memory (Gb) = 0.20657 [*] Total SWAP (Gb) = 0 [*] Free SWAP (Gb) = 0 [*] Number of processes running = 444
Load average:
$ >> ./linux-info load-avg ==== System Load Average ================================ Load average = 0.27 0.28 0.28 4/443 2532
Mounted file systems (file: /proc/mounts)
$ >> ./linux-info mount ====== System mount points ( Reported by command $ mount ) ===== File system Type Mount point ------------ ---- ----------- sysfs sysfs /sys proc proc /proc udev devtmpfs /dev devpts devpts /dev/pts tmpfs tmpfs /run /dev/sr0 iso9660 /cdrom /dev/loop0 squashfs /rofs securityfs securityfs /sys/kernel/security tmpfs tmpfs /dev/shm tmpfs tmpfs /run/lock ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... debugfs debugfs /sys/kernel/debug tracefs tracefs /sys/kernel/tracing fusectl fusectl /sys/fs/fuse/connections configfs configfs /sys/kernel/config tmpfs tmpfs /tmp tmpfs tmpfs /run/user/999 gvfsd-fuse fuse.gvfsd-fuse /run/user/999/gvfs /dev/fuse fuse /run/user/999/doc /dev/fuse fuse /root/.cache/doc gvfsd-fuse fuse.gvfsd-fuse /root/.cache/gvfs binfmt_misc binfmt_misc /proc/sys/fs/binfmt_misc
Kernel runtime information (directory /proc/sys/kernel)
$ >> ./linux-info sysctl # Show some kernel runtime options ===== Kernel Runtime Information (Reported by $ sysctl ) ======= [*] Machine Hostname => pop-os [*] Kernel Version => 5.4.0-7634-generic [*] Os type => Linux [*] Boot unique ID identifier => 756989d9-f596-4e4f-ac50-d5c3026d041a [*] Date which kernel was compiled => #38~1591219791~20.04~6b1c5de-Ubuntu SMP Thu Jun 4 02:56:10 UTC 2 [*] Sysrq enabled (Emergency keys) => 176 [*] Max number of Cgroups namespaces => 7490 [*] Max number of Mount namespaces => 7490
Kernel messages (file: /dev/kmsg)
$ >> ./linux-info dmesg ===== Kernel debug message (Reported by $ dmesg ) ========== 5,0,0,-;Linux version 5.4.0-7634-generic (buildd@lcy01-amd64-003) (gcc version 9.3.0 (Ubuntu 9.3.0-10ubuntu2)) #38~1591219791~20.04~6b1c5de-Ubuntu SMP Thu Jun 4 02:56:10 UTC 2 (Ubuntu 5.4.0-7634.38~1591219791~20.04~6b1c5de-generic 5.4.41) 6,1,0,-;Command line: BOOT_IMAGE=/casper_pop-os_20.04_amd64_intel_debug_19/vmlinuz.efi initrd=/casper_pop-os_20.04_amd64_intel_debug_19/initrd.gz boot=casper live-media-path=/casper_pop-os_20.04_amd64_intel_debug_19 hostname=pop-os username=pop-os noprompt --- 6,2,0,-;KERNEL supported cpus: 6,3,0,-; Intel GenuineIntel .... .... .... .... .... .... .... .... .... .... .... .... .... .... DEVICE=+hdaudio:hdaudioC0D0 6,649,16644546,-;snd_hda_codec_generic hdaudioC0D0: inputs: SUBSYSTEM=hdaudio DEVICE=+hdaudio:hdaudioC0D0 7,650,25338660,-;rfkill: input handler disabled 5,651,40422354,-;random: crng init done 5,652,40422357,-;random: 7 urandom warning(s) missed due to ratelimiting
Testing with Fedora 32 x86-64 (64 bits)
Get mount points (file: /proc/mounts)
$ ./linux-info mount ====== System mount points ( Reported by command $ mount ) ===== File system Type Mount point ------------ ---- ----------- sysfs sysfs /sys proc proc /proc devtmpfs devtmpfs /dev securityfs securityfs /sys/kernel/security tmpfs tmpfs /dev/shm devpts devpts /dev/pts tmpfs tmpfs /run tmpfs tmpfs /sys/fs/cgroup pstore pstore /sys/fs/pstore efivarfs efivarfs /sys/firmware/efi/efivars none bpf /sys/fs/bpf configfs configfs /sys/kernel/config /dev/sda3 ext4 / systemd-1 autofs /proc/sys/fs/binfmt_misc tmpfs tmpfs /tmp fusectl fusectl /sys/fs/fuse/connections /dev/sda1 vfat /boot/efi sunrpc rpc_pipefs /var/lib/nfs/rpc_pipefs tmpfs tmpfs /run/user/42 tmpfs tmpfs /run/user/1000 gvfsd-fuse fuse.gvfsd-fuse /run/user/1000/gvfs nsfs nsfs /run/docker/netns/77290c7a4863 binfmt_misc binfmt_misc /proc/sys/fs/binfmt_misc
1.9 Disk Space Usage on Linux and BSD-variants
The following code gets the disk space usage on Linux-derived operating systems by iterating over the file /proc/mounts for determining mounted file systems and then uses the statfs() function for getting disk usage information from each file system mount point. Unlike, Linux On BSD variants such, as FreeBSD, OpenBSD and Mac-OSX, there is no PROCFS virtual file system. On those operating systems, the information about disk space usage can be obtained in a single step by using the function getmntinfo().
Functions used
- Documentation: $ man statfs
- The functions statfs and fstatfs provide information about mounted file systems. The parameter buf, must be allocated by the caller. When the function fails, it returns (-1).
#include <sys/vfs.h> /* or <sys/statfs.h> */ int statfs(const char *path, struct statfs *buf); int fstatfs(int fd, struct statfs *buf); struct statfs { __fsword_t f_type; /* Type of filesystem (see below) */ __fsword_t f_bsize; /* Optimal transfer block size */ fsblkcnt_t f_blocks; /* Total data blocks in filesystem */ fsblkcnt_t f_bfree; /* Free blocks in filesystem */ fsblkcnt_t f_bavail; /* Free blocks available to unprivileged user */ fsfilcnt_t f_files; /* Total file nodes in filesystem */ fsfilcnt_t f_ffree; /* Free file nodes in filesystem */ fsid_t f_fsid; /* Filesystem ID */ __fsword_t f_namelen; /* Maximum length of filenames */ __fsword_t f_frsize; /* Fragment size (since Linux 2.6) */ __fsword_t f_flags; /* Mount flags of filesystem (since Linux 2.6.36) */ __fsword_t f_spare[xxx]; /* Padding bytes reserved for future use */ };
Sample Code
File: df-size.cpp
// ----- Get Disk Space Usage -------------// #include <iostream> #include <vector> #include <string> #include <algorithm> #include <functional> #include <iomanip> #include <fstream> #include <cmath> // ------ Unix Headers ----// #include <unistd.h> #include <sys/vfs.h> /* Get statfs() */ #include <limits.h> #include <sys/types.h> #include <sys/stat.h> #include <cstring> struct mount_point { // File System std::string fsystem; // Mounting directroy std::string mount_directory; // File system type std::string type; }; template<typename MountpointConsumer> void iterate_mount_points(MountpointConsumer&& consumer); auto read_symlink(std::string const& path) -> std::string ; int main(int argc, char** argv) { iterate_mount_points([](mount_point const& mnt) { struct statfs fs; const char* mount_dir = mnt.mount_directory.c_str(); // =>>> List of file system that will be ignored. // 'static' keyworkd => Variable allocated only once, when // this function is called for the first time. static const auto ignored_fsystem = std::vector<std::string>{ "cgroup", "cgroup2", "proc", "sysfs", "tmpfs", "devtmpfs" , "configfs", "bpf", "mqueue", "hugetlbfs", "tracefs" , "fuse.gvfsd-fuse", "rpc_pipefs", "debugfs" , "overlay", "securityfs", "devpts", "pstore", "efivarfs" , "autofs", "binfmt_misc", "tmpfs", "fusectl", "nsfs" , "selinuxfs", "functionfs" }; // Ignore the following types of file-system, mostly VSF (Virtual File Systems) // or in-memory file systems. auto iter = std::find(ignored_fsystem.cbegin(), ignored_fsystem.cend(), mnt.type ); if( iter != ignored_fsystem.end() ) { return; } if( statfs(mount_dir, &fs) == -1 ) { std::cerr << " [ERROR] Unable to get file system statistics for: " << " Mount point => " << mnt.mount_directory << " / " << mnt.fsystem.c_str() << '\n'; return; } const size_t block_size = fs.f_bsize; // Total disk space in Gigabytes const double total_space = static_cast<double>(fs.f_blocks) * block_size / (1024 * 1024 * 1024); // Free disk space in Gigabytes const double available_space = static_cast<double>(fs.f_bavail) * block_size / (1024 * 1024 * 1024); // Disk space usage in Gigabytes const double used_space = total_space - available_space; // Disk space usage in percent (%) const double used_space_pct = (1.0 - available_space / total_space) * 100.0; auto fsystem_abspath = read_symlink(mnt.fsystem); std::cout << " File System: " << fsystem_abspath << '\n' << " Type: " << mnt.type << '\n' << " Mount Point: " << mnt.mount_directory << '\n'; if(total_space > 1.0) { std::cout << " Total space (Gb): " << total_space << '\n' << " Free space (Gb): " << available_space << '\n' << " Used space (Gb): " << used_space << '\n' << " Used space (%): " << used_space_pct << "%" << '\n' << '\n'; } else { std::cout << " Free space (Mb): " << available_space * 1024 << '\n' << " Total space (Mb): " << total_space * 1024 << '\n' << " Used space (Gb): " << used_space * 1024 << '\n' << " Used space (%): " << used_space_pct << "%" << '\n' << '\n'; } }); return 0; } // ----------------------------------------------------------------------// template<typename MountpointConsumer> void iterate_mount_points(MountpointConsumer&& consumer) { auto ifs = std::ifstream("/proc/mounts"); if(!ifs) throw std::runtime_error("Error: unable to open file /proc/mounts"); std::string line, fsystem, mount_directory, type; while( std::getline(ifs, line) ) { auto ss = std::stringstream(line); ss >> fsystem >> mount_directory >> type; consumer( mount_point{ fsystem, mount_directory, type } ); //if( type != "cgroup" && type != "overlay" && type != "cgroup2") } } // Get absolute path to file or symbolic link auto read_symlink(std::string const& path) -> std::string { // Create a buffer with size PATH_MAX + 1 filled with 0 ('\0'), null characters std::string buffer(PATH_MAX, 0); // ssize_t readlink(const char *pathname, char *buf, size_t bufsiz); ssize_t nread = ::readlink(path.c_str(), &buffer[0], PATH_MAX); if(nread == -1){ return path; } buffer.resize(nread); return buffer; }
Building and Running on Fedora Linux 32 x86-64
$ >> g++ df-size.cpp -o df-size.bin -std=c++1z -Wall -Wextra -g
$ >> ./df-size.bin
File System: /dev/sda3
Type: ext4
Mount Point: /
Total space (Gb): 49.06
Free space (Gb): 23.9494
Used space (Gb): 25.1106
Used space (%): 51.1835%
File System: /dev/sda4
Type: ext4
Mount Point: /home
Total space (Gb): 846.607
Free space (Gb): 510.182
Used space (Gb): 336.424
Used space (%): 39.738%
File System: /dev/sda1
Type: vfat
Mount Point: /boot/efi
Free space (Mb): 279.422
Total space (Mb): 299.82
Used space (Gb): 20.3984
Used space (%): 6.80355%
Disk space via (df) command line tool:
$ >> df -h Filesystem Size Used Avail Use% Mounted on devtmpfs 7.8G 0 7.8G 0% /dev tmpfs 7.8G 315M 7.5G 4% /dev/shm tmpfs 7.8G 2.1M 7.8G 1% /run tmpfs 7.8G 0 7.8G 0% /sys/fs/cgroup /dev/sda3 50G 23G 24G 49% / tmpfs 7.8G 11M 7.8G 1% /tmp /dev/sda4 847G 294G 511G 37% /home /dev/sda1 300M 21M 280M 7% /boot/efi tmpfs 1.6G 16K 1.6G 1% /run/user/42 tmpfs 1.6G 80K 1.6G 1% /run/user/1000
Cross-compiling to Linux Armv7 and run on Android via ADB
Building:
$ export DOCKER_COMMAND="docker run -e UID=$(id -u) -e GID=$(id -g) --volume=$PWD:/cwd --workdir=/cwd muslcc/i686:armv7l-linux-musleabihf " $ $DOCKER_COMMAND g++ df-size.cpp -o df-size.armv7 -std=c++1z -static -Wall -Wextra $ $DOCKER_COMMAND strip df-size.armv7 $ file df-size.armv7 df-size.armv7: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, stripped $ du -h df-size.armv7 820K df-size.armv7 820K total
Transfer executable to Android:
$ adb push df-size.armv7 /data/local/tmp df-size.armv7: 1 file pushed. 12.1 MB/s (839348 bytes in 0.066s)
Running on Android vai adb shell:
$ adb shell bali:/ $ bali:/ $ cd /data/local/tmp bali:/data/local/tmp $ # Confirm disk space usage metrics bali:/data/local/tmp $ df -h Filesystem Size Used Avail Use% Mounted on /dev/root 2.6G 2.2G 450M 84% / tmpfs 931M 1.3M 930M 1% /dev tmpfs 931M 0 931M 0% /mnt /dev/block/dm-1 1.4G 207M 1.2G 15% /vendor /dev/block/mmcblk0p39 23G 12G 11G 53% /data /dev/block/mmcblk0p38 402M 6.4M 383M 2% /cache /data/media 23G 12G 11G 53% /storage/emulated # Run application bali:/data/local/tmp $ ./df-size.armv7 2> /dev/null File System: /dev/root Type: ext4 Mount Point: / Total space (Gb): 2.66402 Free space (Gb): 0.439049 Used space (Gb): 2.22497 Used space (%): 83.5193% File System: /dev/block/dm-1 Type: ext4 Mount Point: /vendor Total space (Gb): 1.45304 Free space (Gb): 1.23495 Used space (Gb): 0.218094 Used space (%): 15.0094% File System: /dev/block/mmcblk0p39 Type: f2fs Mount Point: /data Total space (Gb): 23.4941 Free space (Gb): 11.2018 Used space (Gb): 12.2923 Used space (%): 52.3209% File System: /dev/block/mmcblk0p38 Type: ext4 Mount Point: /cache Free space (Mb): 383.078 Total space (Mb): 402.445 Used space (Gb): 19.3672 Used space (%): 4.81238% File System: /dev/block/mmcblk0p35 Type: ext4 Mount Point: /product Free space (Mb): 444.348 Total space (Mb): 476.59 Used space (Gb): 32.2422 Used space (%): 6.76519% File System: /data/media Type: sdcardfs Mount Point: /storage/emulated Total space (Gb): 23.4941 Free space (Gb): 11.2018 Used space (Gb): 12.2923 Used space (%): 52.3209%
Further Reading
- Unix OpenGroup - fstatvfs
- Chris's Wiki - blog/unix/StatfsPeculiarities
- Understanding disk usage in Linux – Own your bits
- Understanding disk usage in Linux – Own your bits (Web Archive)
- filesystems - How is free disk space on ext4 calculated? - Unix & Linux Stack Exchange
- c++ - How to obtain total available disk space in Posix systems? - Stack Overflow
- FreeBSD Docs / getmntinfo() => get information about mounted file systems
- MacOSX / getmntinfo()
- Edenwaith : Blog - Calculating Free Space on macOS
1.10 Low level open, read, write, close - IO sycalls overview
The open(), read(), write() and close() system calls are the hallmark of Unix-based operating system as they embody the concept "every is a file" or "everything is a file descriptor". Those fundamental system calls are able to operate over any type of file descriptors such as regular files; symbolic links; in-memory files; anonymous pipes; named pipes; sockets; unix domain sockets IPC (Inter-Process Communication); character device files representing hardware; block device files, which represents non-volatile storage device and so on.
It is worth noting that, those system-calls are not used directly by user-space applications. Instead, applications uses C function wrappers provided by the CRT (C Runtime Library), which is GLIBC in most Linux distributions.
Special File Descriptors
- STDOUT_FILENO => File descritor for stdout (process standard output)
- STDERR_FILENO => File descritor for stderr (process standard error output)
- STDIN_FILENO => File descritor for stdin (process standard input)
Headers
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h>
Functions
- close() => Close a file descriptor.
- Doc: $ man 2 close
int close(int fd);
- open() => Encapsulates open system call => Returns a file descritor
number. When fails, it returns (-1) setting the global variable
errno.
- Doc: $ man 2 open
int open(const char *pathname, int flags);
- creat() => Create file. Note: the name is not misspelled. There is
really a missing 'e' letter.
- Doc: $ man 2 creat
int creat(const char *pathname, mode_t mode)
- read() => Read bytes from a file descriptor into a buffer, returning the
number of bytes read. If there is an error, the functions returns
(-1) setting the global variable errno.
- Doc: $ man 2 read
- Note: If the number of bytes returned is zero, it means that no more bytes can be read in the case of file or that socket connection was closed by the counterpart (socket server or client).
- Note: The number of bytes returned can smaller than the buffer size in the case of socket file descriptors (partial read).
- Note: The buffer argument can be a pointer to any POD (Plain-Old Data) type allocated by the calling code. A POD type, does not have any internal pointers or STL containers.
- Note: A negative return value (-1), as in most Unix APIs, indicates an error. In order to get more information about the error, the calling code must check the errno global variable.
ssize_t read(int fd, void *buf, size_t count);
- write() => Write N bytes from a buffer to a file descriptor.
- Doc: $ man 2 write
ssize_t write(int fd, const void *buf, size_t count);
1.11 Low level IO - read() syscall
Summary:
- Read data from file descriptor to a buffer. Where buffer a pointer to any storage location within the process virtual memory. The 'buffer' argument can be a pointer to any POD type plain-old data, which continuous allocated in the memory, without any internal pointer.
- This system call can be used with any type of file descriptors, namely: ordinary file descriptors; standard input from terminal, stdin (character device); sockets; unix domain sockets; anonymous pipes; named pipes; character device files, which often represent hardware; and block devices which represent mounted file systems.
#include <unistd.h> ssize_t read(int fd, void* buffer, size_t len); ssize_t read( int fd // => File descriptor , void* buf // => Pointer to buffer // It can be a pointer to any POD type, plain-old data. // without any internal pointers. , size_t len // => Buffer size or length in bytes. );
Possible Return Values
The system call wrapper function read() can return the following results:
- Number of bytes read, which may be less or equal to the buffer size. The bytes read are stored in the buffer.
- Return value: (0) indicating EOF (End-Of-File) - there is nor more bytes to be read.
- Return value: (-1) and errno set to EINTR indicating that before any byte was read, a signal was set to the current process. In this case, read() can be called again.
- Return value: (-1) and errno set to EAGAIN with a file descriptor in non-blocking mode. This case indicates that a read operation on the file descriptor would block the current thread while there is no data available.
- Return value: (-1) in the case of failure, in this case, the thread-local storage variable errno is set.
Semantics of read()
- Ordinary file descriptor
- A call to read() never blocks the current thread and returns 0 indicating (EOF) when the end of file is reached.
- STDIN_FILENO (value 0) file descriptor, which represents the
console/terminal input.
- A call to read() blocks the current thread while there is no bytes to be read and returns (0) indicating EOF when the user types the (CTRL D) sequence.
- Sockets file descriptors in blocking mode (default):
- A call to read blocks the curren thread while there is no bytes available in the kernel-side buffer. When a read() call returns 0, it indicates that no more bytes can be read as the other side (client or server) has closed the connection and by using close() or shutdown() calls.
Handling partial-reads
The following algorithm reads all bytes until the buffer is filled for handling partial reads which can happen with socket file descriptors in blocking mode. Example about partial read: The server-side socket sends 1000 bytes, then the client-side socket counterpart performs a read(fd_server, buffer, 1000) call. The number of bytes returned on the first call may be less than the buffer size, for instance it could be 200. Then the client-side socket would have to continue to read all bytes until the buffer is filled.
//----------------------------------------// // Inputs // //----------------------------------------// // Buffer length in bytes const size_t buffer_len = 400; char buffer[buffer_len]; //----------------------------------------// // Algorithm // //----------------------------------------// // Pre-condition assert( bufffer_len <= SSIZE_MAX ); size_t len = buffer_len; ssize_t nr = -1; char* pbuf = buffer; while( // Stop iterations when buffer is filled. len != 0 // Stop interations when 'nr' is EOF, there is no more bytes left // or the socket connection has been closed. && ( nr = read(fd, pbuf, len) ) != 0 ) { if(ret == -1 && errno == EINTR){ continue; } if(ret == -1) { perror(" An error has happened, check ERRNO variable. \n"); break; } len = len - nr; pbuf = pbuf + nr; }
1.12 Low level IO - open(), read(), write() syscall functions
This code demonstrates the usage of the open(), read(), write(), close() low-level IO library-calls for file descriptors which encapsulates system-calls with the same name.
GIST:
File: unix-low-level-io.cpp
#include <iostream> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <cstring> // Import: char* strerror(in errnum); const char* errno_to_cstring(int err) { // No such file or directory if(err == ENOENT) return "ENOENT"; // Operation not permitted if(err == EPERM) return "EPERM"; // Onput/Output error if(err == EIO) return "EIO"; if(err == EAGAIN) return "EAGAIN"; if(err == EPERM) return "EPERM"; if(err == EPIPE) return "EPIPE"; return "<UNKNOWN>"; } /** Check whether file descriptor is regular file */ bool fd_is_regular_file(int fd) { struct stat fd_info; // int fstat(int fd, struct stat *statbuf); int r = fstat(fd, &fd_info); return S_ISREG(fd_info.st_mode); } bool fd_is_directory(int fd) { struct stat fd_info; // int fstat(int fd, struct stat *statbuf); int r = fstat(fd, &fd_info); return S_ISDIR(fd_info.st_mode); } void print_errno_details(int err) { std::fprintf(stderr , "\n => errno(int) = %d" "\n => errno(str) = %s" "\n => errno message = %s \n" , err, errno_to_cstring(err), strerror(err)); std::fflush(stderr); } int main(int argc, char** argv) { std::puts(" [INFO] Program started. "); if(argc < 3){ std::fprintf(stderr, " Usage: \n"); std::fprintf(stderr, " => To read a file: \n"); std::fprintf(stderr, " $ %s file <FILE> \n", argv[0]); std::fprintf(stderr, " => To read stdin (console input): \n"); std::fprintf(stderr, " $ %s file -stdin \n", argv[0]); return 0; } // Compare two c-strings return 0 (zero) when they are equal. // int strcmp(const char *s1, const char *s2) if( strcmp(argv[1], "file") != 0 ) { std::fprintf(stderr, " [ERROR] Expected command file. \n"); return EXIT_FAILURE; } // Variable for holding a file descriptor int fd; // The library-call open() attempts to open a file and returns a "file-descriptor" // (integer number ) when the operation is successful. The library-call // returns (-1) when the operation fails. // Note: It encapsulates the 'open' system call. // if( strcmp(argv[2], "-stdin") == 0) fd = STDIN_FILENO; else fd = open(argv[2], O_RDONLY); if(fd == -1){ // Get error flag 'errno' to get more details about current error. int err = errno; std::fprintf(stderr ," [ERROR] Failed to open file. "); print_errno_details(err); return EXIT_FAILURE; } std::fprintf(stdout, " [INFO] ?? File is regular file = %s \n" , fd_is_regular_file(fd) ? "TRUE" : "FALSE" ); std::fprintf(stdout, " [INFO] ?? File is directory file = %s \n" , fd_is_directory(fd) ? "TRUE" : "FALSE" ); // Flush file => Force changes to be immeditely written. std::fflush(stdout); // Buffer maximum size in bytes constexpr size_t BUFFER_MAX_SIZE = 200; char buffer[BUFFER_MAX_SIZE]; // Stream BUFFER_MAX_SIZE bytes from file descriptor // to STDOUT_FILENO (file descriptor). //--------------------------------------------------- ssize_t ret; do { ret = read(fd, buffer, BUFFER_MAX_SIZE); if(ret == -1) { int err = errno; std::fprintf(stderr, " [ERROR] An error has happened => "); print_errno_details(err); close(fd); return EXIT_FAILURE; } ::write(STDOUT_FILENO, buffer, ret); } while( ret != 0); // Always close the file descriptor. close(fd); return 0; }
Building:
$ g++ unix-low-level-io.cpp -o unix-low-level-io.bin -std=c++1z -Wall -Wextra
Running:
- Run 1
$ >> ./unix-low-level-io.bin
[INFO] Program started.
Usage:
=> To read a file:
$ ./unix-low-level-io.bin file <FILE>
=> To read stdin (console input):
$ ./unix-low-level-io.bin file -stdin
- Run 2:
$ >> ./unix-low-level-io.bin file /proc/filesystems [INFO] Program started. [INFO] ?? File is regular file = TRUE [INFO] ?? File is directory file = FALSE nodev sysfs nodev tmpfs nodev bdev nodev proc nodev cgroup nodev cgroup2 ... ... ... $ >> ./unix-low-level-io.bin file /etc/resolv.conf [INFO] Program started. [INFO] ?? File is regular file = TRUE [INFO] ?? File is directory file = FALSE # Generated by NetworkManager nameserver 194.165.12.10
- Run 3: (Error)
$ >> ./unix-low-level-io.bin file /etc/resosad [INFO] Program started. [ERROR] Failed to open file. => errno(int) = 2 => errno(str) = ENOENT => errno message = No such file or directory $ >> ./unix-low-level-io.bin file /etc/shadow [INFO] Program started. [ERROR] Failed to open file. => errno(int) = 13 => errno(str) = <UNKNOWN> => errno message = Permission denied $ >> ./unix-low-level-io.bin file / [INFO] Program started. [INFO] ?? File is regular file = FALSE [INFO] ?? File is directory file = TRUE [ERROR] An error has happened => => errno(int) = 21 => errno(str) = <UNKNOWN> => errno message = Is a directory
- Run 4 - read stdin file descriptor STDIN_FILENO
$ >> ./unix-low-level-io.bin file -stdin [INFO] Program started. [INFO] ?? File is regular file = FALSE [INFO] ?? File is directory file = FALSE Hello world Hello world Unix-linux file descriptors - Low level IO Unix-linux file descriptors - Low level IO # User types Ctrl+D to close STDIN
1.13 Low level IO - creat()
Creates a file using Unix-low level IO function creat() which encapsulates the creat() system-call.
File: unix-creat.cpp
#include <iostream> #include <string> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <cstring> void print_errno_details(int err); int main(int argc, char** argv) { // File is created with read, write permissions for owner // ,groups and others mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; int fd = creat("/tmp/my-sample-file.txt", mode); if(fd == -1){ print_errno_details(errno); return EXIT_FAILURE; } constexpr size_t BUFFER_SIZE = 500; char buffer[BUFFER_SIZE]; // Fill the whole buffer with '\0' null char characters memset(buffer, '\0', BUFFER_SIZE); // strcpy => Copy string literal to buffer. strcpy(buffer, " [LINE 0] Write this message to buffer\n"); fprintf(stdout, " Buffer size (non blank chars) = %zu \n", strlen(buffer)); // Write strlen(buffer) bytes to file descriptor write(fd, buffer, strlen(buffer)); strcpy(buffer, " [LINE 1] Unix, BSD, OSX, LINUX, AIX, POSIX\n"); fprintf(stdout, " Buffer size (non blank chars) = %zu \n", strlen(buffer)); fprintf(stdout, " Buffer content = '%s' \n", buffer); // Write strlen(buffer) bytes to file descriptor write(fd, buffer, strlen(buffer)); strcpy(buffer, " [LINE 2] FreeRTOS Linux RTAI Real-time RTMES QNX\n"); write(fd, buffer, strlen(buffer)); close(fd); return EXIT_SUCCESS; } // Ruires: #include <string> void print_errno_details(int err) { std::fprintf(stderr , "\n => errno(int) = %d" "\n => errno message = %s \n" , err, strerror(err)); std::fflush(stderr); }
Building:
$ g++ unix-creat.cpp -o unix-creat.bin -std=c++1z -Wall -Wextra -g
Running:
$ >> ./unix-creat.bin Buffer size (non blank chars) = 39 Buffer size (non blank chars) = 44 Buffer content = ' [LINE 1] Unix, BSD, OSX, LINUX, AIX, POSIX '
Content of generated file:
$ >> cat /tmp/my-sample-file.txt [LINE 0] Write this message to buffer [LINE 1] Unix, BSD, OSX, LINUX, AIX, POSIX [LINE 2] FreeRTOS Linux RTAI Real-time RTMES QNX
Trace library-calls with ltrace application:
- $ ltrace ./unix-creat.bin
$ >> ltrace ./unix-creat.bin _ZNSt8ios_base4InitC1Ev(0x4040a9, 0xffff, 0x7ffcfe326598, 224) = 0 __cxa_atexit(0x4010e0, 0x4040a9, 0x402008, 6) = 0 creat(0x402010, 420, 0x7ffcfe326598, 256) = 3 memset(0x7ffcfe326280, '\0', 500) = 0x7ffcfe326280 strlen(" [LINE 0] Write this message to "...) = 39 fprintf(0x7f95f2612500, " Buffer size (non blank chars) ="..., 39 Buffer size (non blank chars) = 39 ) = 37 strlen(" [LINE 0] Write this message to "...) = 39 write(3, " [LINE 0] Write this message to "..., 39) = 39 strlen(" [LINE 1] Unix, BSD, OSX, LINUX,"...) = 44 fprintf(0x7f95f2612500, " Buffer size (non blank chars) ="..., 44 Buffer size (non blank chars) = 44 ) = 37 fprintf(0x7f95f2612500, " Buffer content = '%s' \n", " [LINE 1] Unix, BSD, OSX, LINUX,"... Buffer content = ' [LINE 1] Unix, BSD, OSX, LINUX, AIX, POSIX ' ) = 66 strlen(" [LINE 1] Unix, BSD, OSX, LINUX,"...) = 44 write(3, " [LINE 1] Unix, BSD, OSX, LINUX,"..., 44) = 44 strlen(" [LINE 2] FreeRTOS Linux RTAI Re"...) = 50 write(3, " [LINE 2] FreeRTOS Linux RTAI Re"..., 50) = 50 close(3) = 0 _ZNSt8ios_base4InitD1Ev(0x4040a9, 0, 0x4010e0, 1) = 0x7f95f2965e40 +++ exited (status 0) +++
Trace system-calls with strace application:
- $ strace ./unix-creat.bin
$ strace ./unix-creat.bin execve("./unix-creat.bin", ["./unix-creat.bin"], 0x7fff489b8e40 /* 83 vars */) = 0 brk(NULL) = 0x57c000 arch_prctl(0x3001 /* ARCH_??? */, 0x7fff751bd910) = -1 EINVAL (Invalid argument) access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... mprotect(0x7fed4e032000, 4096, PROT_READ) = 0 munmap(0x7fed4dfe4000, 136250) = 0 brk(NULL) = 0x57c000 brk(0x59d000) = 0x59d000 creat("/tmp/my-sample-file.txt", 0644) = 3 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0xc), ...}) = 0 write(1, " Buffer size (non blank chars) ="..., 37) = 37 write(3, " [LINE 0] Write this message to "..., 39) = 39 write(1, " Buffer size (non blank chars) ="..., 37) = 37 write(1, " Buffer content = ' [LINE 1] Uni"..., 63) = 63 write(1, "' \n", 3) = 3 write(3, " [LINE 1] Unix, BSD, OSX, LINUX,"..., 44) = 44 write(3, " [LINE 2] FreeRTOS Linux RTAI Re"..., 50) = 50 close(3) = 0 exit_group(0) = ? +++ exited with 0 +++
1.14 Reading/Listings directory contents
API Documentations:
Functions used:
DIR *opendir(const char *name); int readdir( unsigned int fd , struct old_linux_dirent* dirp, , unsigned int count);
File: unix-readdir.cpp
#include <iostream> // #include <string> #include <sys/types.h> #include <dirent.h> // Get function opendir #include <errno.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> template<typename Callback> void iterate_directory(const std::string& path, Callback&& callback) { DIR *dir; struct dirent *dp; dir = opendir(path.c_str()) ; // To determine the cause of error - It is necessary to check the error code. if (dir == nullptr) throw std::runtime_error("Error: Cannot read directory"); while ((dp = readdir(dir)) != nullptr) { if(path == "/") callback(dp->d_name); else callback(path + "/" + dp->d_name); }; closedir(dir); } int main(int argc, char** argv) { std::cout << " List directory contents " << std::endl; if(argc < 2) { std::cout << "Usage: ./unix-readdir <DIRECTORY>" << "\n"; return EXIT_SUCCESS; } int idx = 0; iterate_directory(argv[1], [&idx](auto const& path) { std::cout << " [" << idx++ << "] path => " << path << "\n"; }); return EXIT_SUCCESS; }
Building on Linux:
$ g++ unix-readdir.cpp -o unix-readdir.bin -std=c++1z -g -Wall -Wextra
Building on MacOSX:
# Note: It is not (GNU GCC), g++ command refers to CLang $ g++ unix-readdir.cpp -o unix-readdir.bin -std=c++1z -g -Wall -Wextra # Same as previous command (CLang compiler) $ clang++ unix-readdir.cpp -o unix-readdir.bin -std=c++1z -g -Wall -Wextra $ file unix-readdir.bin unix-readdir.bin: Mach-O 64-bit executable x86_64 $ du -h unix-readdir.bin 44K unix-readdir.bin # List shared libraries dependencies: $ otool -L unix-readdir.bin unix-readdir.bin: /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 902.1.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1281.100.1)
Running on Linux:
$ >> ./unix-readdir.bin / List directory contents [0] path => srv [1] path => sys [2] path => .. [3] path => opt [4] path => . [5] path => run [6] path => media ... ... ... ... .. .... ... ... $ >> ./unix-readdir.bin /boot List directory contents [0] path => /boot/.. [1] path => /boot/. [2] path => /boot/initramfs-5.6.12-300.fc32.x86_64.img [3] path => /boot/grub2 [4] path => /boot/vmlinuz-0-rescue-a1ac43b933e24659bba5edf2b9cec1e1 ... ... ... ... ... ... ... ... ... ... ... ...
Running on MacOSX Catalina VM:
- Note: on MacOSX, the command g++ is not GCC, it refers to Clang.
./unix-readdir / List directory contents [0] path => . [1] path => .. [2] path => home [3] path => usr [4] path => .DS_Store [5] path => bin [6] path => sbin [7] path => .file [8] path => etc [9] path => var [10] path => Library [11] path => System [12] path => .VolumeIcon.icns [13] path => .fseventsd [14] path => private [15] path => .vol [16] path => Users [17] path => Applications [18] path => opt [19] path => dev [20] path => Volumes [21] path => tmp [22] path => cores ./unix-readdir /Applications/iTerm.app/Contents List directory contents [0] path => /Applications/iTerm.app/Contents/. [1] path => /Applications/iTerm.app/Contents/.. [2] path => /Applications/iTerm.app/Contents/CodeResources [3] path => /Applications/iTerm.app/Contents/_CodeSignature [4] path => /Applications/iTerm.app/Contents/MacOS [5] path => /Applications/iTerm.app/Contents/Resources [6] path => /Applications/iTerm.app/Contents/XPCServices [7] path => /Applications/iTerm.app/Contents/Frameworks [8] path => /Applications/iTerm.app/Contents/Info.plist [9] path => /Applications/iTerm.app/Contents/PkgInfo
1.15 Process Inputs, Outputs and States and PROCFS on Linux
Any process may have the following inputs, outputs and states:
Some Processes States:
- Native executable used for creating the process. Every process has a native executable. A python process is the Python interpreter (python.exe on Windows) running python scripts. A Java process is the Java virtual machine (java.exe) running java *.class bytecodes or *.jar java packages.
- CWD => Current Working Directory
- IP => Instruction Pointer (process context) => CPU registry that stores the current machine instruction to be run.
- SP => Stack Poitner (process context) => CPU registry used for keeping track of the process call stack.
- Note: all thoses states can be viewed and manipulated with a debugger, such as GDB, LLDB or Windows Debugger. A debugger can monitor, explore and manipulate the virtual memory of any process.
Inputs:
- Stdin (Standard Input) => User input read from console in some shell (cmd.exe on Windows) or bash, zsh on Unix-like OSes.
- Command line argument passed to main function
- Environment variables set during the process instantiation with env or export commands on Unix-like operating systems.
- Configuration files => read at a default fixed location such as
/.bashrc (bash configuration file at ~/home/user/.bashrc/
on Linux ); ~/.vimrc (vim configuration file) - Windows Registry entry for configuration during process initialization
- Input files read after initialization.
Outputs:
- Stdout (Standard Output) => Process output print by functions such as printf; std::cout << … visible in a terminal. (Unix heritage)
- Stderr (Standard Error Ouput) => Process error or logging output print by default in a terminal. stderr exists for allowing separating the output (stdout) from the error or logging output (stderr).
- Statuds code => Value returned by function int main(int argc, char** argv)
- Value 0 indicates success and that the process terminated successfuly.
- Value different than zero indicates that the process terminated with errors.
- Output file(s)
Both Input/Output:
- Sockets
- IPC Inter Process Communication
- GUI - Graphical User Interface
Command line arguments int arc, char** argv (input) Configuration file: >>-------+ +--<<-- Input file read at fixed location \ / Or Windows Registry entry \ | | | \/ \ / +---------------------------------+ | Process: | | +--------->> Stdout (output) ---------->>| EXE = /bin/some/executable | Stdin (input) | PID = 9381 (Unique ID) +--------->> Stderr (output) | CWD = /home/user/some/data | | | | IP - Instruction Pointer +--------->>> Process status code: (output) ---------->>| SP - Stack pointer | Environment +-----------------------+--------+ int main(int argc, char** argv) Variables (input) | { | .... ... ... export PATH=$PATH:/opt/bin/program/bin | int status_code = 0 export APP_LOG_LEVEL=INFO | return status_code; \ / } Output File
Example: View Process Information on Linux
All mentioned process internal states can be viewed with a debugger such as GDB (GNU Debugger). On Linux, they can also be accessed with the /proc pseudo-file system created by the Linux kernel which exposes many processes informations.
Example: View information about the process which the PID is 19529
- Start a Kotlin REPL process and get a PID:
$ kotlinc-jvm Welcome to Kotlin version 1.3.50 (JRE 1.8.0_162-b12) Type :help for help, :quit for quit >>> >>> java.lang.management.ManagementFactory.getRuntimeMXBean().getName() res1: kotlin.String! = 23127@localhost.localdomain >>>
- Proc-File system for PID 23127
$ ls /proc/23127 attr/ cmdline loginuid personality statm fd/ comm maps projid_map status fdinfo/ coredump_filter mem root@ syscall map_files/ cpuset mountinfo sched timers net/ cwd@ mounts schedstat timerslack_ns ns/ environ mountstats sessionid uid_map task/ exe@ numa_maps setgroups wchan autogroup gid_map oom_adj smaps auxv io oom_score smaps_rollup cgroup latency oom_score_adj stack clear_refs limits pagemap stat
- Get process executable:
- File: /proc/<PID>/exe is a symbolic link to the process' native executable.
$ readlink /proc/23127/exe /home/archbox/opt/java/bin/java
- Get process current directory:
- File: /proc/<PID>/cwd is a symbolic link to process' current directory.
$ readlink /proc/23127/cwd /home/archbox/projects/appdroid/build
- Get command line arguments used to start the process.
- File: /proc/<PID>/cmdline contains the command line arguments used to start the process, passed to main(int argc, char** argv).
$ cat /proc/23127/cmdline | strings -1 /home/archbox/opt/java/bin/java -Xmx256M -Xms32M -noverify -cp /home/archbox/.sdkman/candidates/kotlin/1.3.50/lib/kotlin-preloader.jar org.jetbrains.kotlin.preloading.Preloader -cp /home/archbox/.sdkman/candidates/kotlin/1.3.50/lib/kotlin-compiler.jar org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
- Get process environment variables.
- File: /proc/<PID>/environ contains process' environment variables.
$ cat /proc/23127/environ | strings -1 QTINC=/usr/lib64/qt-3.3/include LC_ALL=en_US.UTF-8 ... ... ... ... ... ... ... ... ... ... USERNAME=archbox JAVA_HOME=/home/archbox/opt/java KDEDIRS=/usr KOTLIN_HOME=/home/archbox/.sdkman/candidates/kotlin/1.3.50 XDG_VTNR=2 SSH_AUTH_SOCK=/tmp/ssh-7z2L0gyqQeeP/agent.3961 ROOTSYS=/home/archbox/opt/root XDG_SESSION_ID=2 MODULES_CMD=/usr/share/Modules/libexec/modulecmd.tcl ENV=/usr/share/Modules/init/profile.sh DESKTOP_SESSION=lxqt IMSETTINGS_MODULE=none PWD=/home/archbox/projects/appdroid/build
- List all process' file descriptors.
$ ls /proc/23127/fd 0@ 10@ 12@ 14@ 16@ 18@ 2@ 21@ 24@ 28@ 3@ 31@ 4@ 6@ 8@ 1@ 11@ 13@ 15@ 17@ 19@ 20@ 22@ 27@ 29@ 30@ 33@ 5@ 7@ 9@ $ ls -l /proc/23127/fd total 0 lrwx------ 1 archbox archbox 64 Nov 4 12:11 0 -> /dev/pts/4 lrwx------ 1 archbox archbox 64 Nov 4 12:11 1 -> /dev/pts/4 lr-x------ 1 archbox archbox 64 Nov 4 12:11 10 -> /home/archbox/.sdkman/candidates/kotlin/1.3.50/lib/kotlin-script-runtime.jar lr-x------ 1 archbox archbox 64 Nov 4 12:11 11 -> /home/archbox/.sdkman/candidates/kotlin/1.3.50/lib/trove4j.jar lr-x------ 1 archbox archbox 64 Nov 4 12:11 12 -> /home/archbox/opt/java/jre/lib/jsse.jar lr-x------ 1 archbox archbox 64 Nov 4 12:11 13 -> /dev/random lr-x------ 1 archbox archbox 64 Nov 4 12:11 14 -> /dev/urandom ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... lr-x------ 1 archbox archbox 64 Nov 4 12:11 8 -> /home/archbox/.sdkman/candidates/kotlin/1.3.50/lib/kotlin-stdlib.jar lr-x------ 1 archbox archbox 64 Nov 4 12:11 9 -> /home/archbox/.sdkman/candidates/kotlin/1.3.50/lib/kotlin-reflect.jar
- List shared libraries loaded by process: (Reference 5103443)
$ cat /proc/23127/maps | awk '{print $6}' | grep '\.so' | sort | uniq /home/archbox/opt/java/jre/lib/amd64/libjava.so /home/archbox/opt/java/jre/lib/amd64/libmanagement.so ... ... ... ... ... ... ... ... ... ... ... ... /usr/lib64/libc-2.27.so /usr/lib64/libdl-2.27.so /usr/lib64/libm-2.27.so /usr/lib64/libnss_files-2.27.so /usr/lib64/libpthread-2.27.so /usr/lib64/librt-2.27.so /usr/lib64/libutil-2.27.so
See also:
1.16 Information about file permissions, owner and group - fstat and stat syscalls
1.16.1 Overview
The following family of common UNIX functions allows obtaining information about file permissions, owner, group and stick bits, namely, set-uid and set-gid special bits.
#include <sys/stat.h> // Obtain permission and file information from file path int stat(const char * path, struct stat* sb); // Obtain permission and file information from file path int lstat(const char* path, struct stat* sb); // Obtain permission and file information from file-descriptor int fstat(int fd, struct stat *sb); int fstatat(int fd, const char* path, struct stat *sb, int flag);
Fstat manpage:
struct stat { dev_t st_dev; /* ID of device containing file */ ino_t st_ino; /* Inode number */ mode_t st_mode; /* File type and mode */ nlink_t st_nlink; /* Number of hard links */ uid_t st_uid; /* User ID of owner */ gid_t st_gid; /* Group ID of owner */ dev_t st_rdev; /* Device ID (if special file) */ off_t st_size; /* Total size, in bytes */ blksize_t st_blksize; /* Block size for filesystem I/O */ blkcnt_t st_blocks; /* Number of 512B blocks allocated */ /* Since Linux 2.6, the kernel supports nanosecond precision for the following timestamp fields. For the details before Linux 2.6, see NOTES. */ struct timespec st_atim; /* Time of last access */ struct timespec st_mtim; /* Time of last modification */ struct timespec st_ctim; /* Time of last status change */ #define st_atime st_atim.tv_sec /* Backward compatibility */ #define st_mtime st_mtim.tv_sec #define st_ctime st_ctim.tv_sec };
See also:
Documentation:
- Linux Manpage - fstat
- FreeBSD sytem calls - stat, lstat, fstat - Get file status
- Linux Manpage - getgrgid() - Query /etc/group database
- FreeBSD - getgrid() - Query /etc/group database.
- OpenGroup - Unix Specification - getpwuid()
1.16.2 Sample code
File: file-info.cpp
#include <iostream> #include <string> #include <functional> #include <cstring> // -- Unix-specific Headers -------// #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <pwd.h> #include <grp.h> std::string get_descriptor_type(struct stat const& fs) { int ftype = fs.st_mode & S_IFMT; if(ftype == S_IFDIR) return "Directory"; if(ftype == S_IFREG) return "Regular file"; if(ftype == S_IFLNK) return "Symbolic link"; if(ftype == S_IFBLK) return "Block device file"; if(ftype == S_IFCHR) return "Character device file"; if(ftype == S_IFIFO) return "FIFO or pipe"; if(ftype == S_IFSOCK) return "Socket"; return "Unknown"; }; int main(int argc, char** argv) { if(argc != 2) { std::printf(" Usage: %s <FILE> \n", argv[0]); return 0; } struct stat fs; struct passwd* fs_user = nullptr; struct group* fs_group = nullptr; //int fd = open(argv[1], O_RDONLY); if( ::stat(argv[1], &fs ) == -1) { std::fprintf(stderr, "Error: unable to open file => error = %s \n", strerror(errno) ); return -1; } if( ( fs_user = ::getpwuid(fs.st_uid)) == nullptr ) { std::fprintf(stderr, " Error: unable to get owner information \n"); return -1; } if( ( fs_group = ::getgrgid(fs.st_gid)) == nullptr ) { std::fprintf(stderr, " Error: unable to get group information \n"); return -1; } // Print boolean as 'true' or 'false' std::cout << std::boolalpha; std::cout << " [*] File size in bytes: " << fs.st_size << '\n'; std::cout << " [*] File size in Kilo bytes: " << static_cast<double>(fs.st_size) / 1024 << '\n'; std::cout << " [*] File size in Mega bytes: " << static_cast<double>(fs.st_size) / (1024 * 1024) << '\n'; std::cout << " [*] File type: " << get_descriptor_type(fs) << '\n'; std::cout << "\n TEST FILE TYPE \n"; std::cout << " [*] ? Is regular file =>> " << static_cast<bool>( S_ISREG(fs.st_mode) ) << '\n'; std::cout << " [*] ? Is directory =>> " << static_cast<bool>( S_ISDIR(fs.st_mode) ) << '\n'; std::cout << " [*] ? Is symbolic link =>> " << static_cast<bool>( S_ISLNK(fs.st_mode) ) << '\n'; std::cout << " [*] ? Is block device =>> " << static_cast<bool>( S_ISBLK(fs.st_mode) ) << '\n'; std::cout << " [*] ? Is character device =>> " << static_cast<bool>( S_ISCHR(fs.st_mode) ) << '\n'; std::cout << "\n ACCESS TIME \n"; std::cout << " [*] Time => last change: " << ctime(&fs.st_ctime); std::cout << " [*] Time => last access: " << ctime(&fs.st_atime); std::cout << " [*] Time => last modified: " << ctime(&fs.st_mtime); std::cout << "\n OWNER and GROUP \n"; std::cout << " [*] Owner: uid = " << fs.st_uid << " ; user = " << fs_user->pw_name << '\n'; std::cout << " [*] Group: gid = " << fs.st_gid << " ; group = " << fs_group->gr_name << '\n'; std::cout << "\n FILE PERMISSIONS \n"; std::cout << " =>> Stick Bits: " << (fs.st_mode & S_ISUID ? "suid" : "-") << " " << (fs.st_mode & S_ISGID ? "sgid" : "-") << '\n'; std::cout << " =>> Owner: " << (fs.st_mode & S_IRUSR ? "r" : "-") // File is readable (can be read) << " " << (fs.st_mode & S_IWUSR ? "w" : "-") // File is writeable (can be written) << " " << (fs.st_mode & S_IXUSR ? "x" : "-") // File is executable (can be executed - execv syscall) << '\n'; std::cout << " =>> Group: " << (fs.st_mode & S_IRGRP ? "r" : "-") << " " << (fs.st_mode & S_IWGRP ? "w" : "-") << " " << (fs.st_mode & S_IXGRP ? "x" : "-") << '\n'; std::cout << " =>> Others: " << (fs.st_mode & S_IROTH ? "r" : "-") << " " << (fs.st_mode & S_IWOTH ? "w" : "-") << " " << (fs.st_mode & S_IXOTH ? "x" : "-") << '\n'; std::cout << " \n- Note: (r - read) ; (w - write); (x - execute) ; (suid - set-UID bit set) ; (sgid - set-GID bit set) \n"; return 0; }
Building:
# Build using GNU GCC (G++) compiler $ g++ file-info.cpp -o file-info -std=c++1z -g -Wall -Wextra # Build using LLVM/Clang compiler $ clang++ file-info.cpp -o file-info -std=c++1z -g -Wall -Wextra
Running:
- Inspect directory /etc
$ ./file-info /etc [*] File size in bytes: 12288 [*] File size in Kilo bytes: 12 [*] File size in Mega bytes: 0.0117188 [*] File type: Directory TEST FILE TYPE [*] ? Is regular file =>> false [*] ? Is directory =>> true [*] ? Is symbolic link =>> false [*] ? Is block device =>> false [*] ? Is character device =>> false ACCESS TIME [*] Time => last change: Sun Aug 2 05:19:31 2020 [*] Time => last access: Wed Aug 5 04:50:20 2020 [*] Time => last modified: Sun Aug 2 05:19:31 2020 OWNER and GROUP [*] Owner: uid = 0 ; user = root [*] Group: gid = 0 ; group = root FILE PERMISSIONS =>> Stick Bits: - - =>> Owner: r w x =>> Group: r - x =>> Others: r - x - Note: (r - read) ; (w - write); (x - execute) ; (suid - set-UID bit set) ; (sgid - set-GID bit set)
- Inspect executable file: /bin/sudo
$ ./file-info /bin/sudo [*] File size in bytes: 182456 [*] File size in Kilo bytes: 178.18 [*] File size in Mega bytes: 0.174004 [*] File type: Regular file TEST FILE TYPE [*] ? Is regular file =>> true [*] ? Is directory =>> false [*] ? Is symbolic link =>> false [*] ? Is block device =>> false [*] ? Is character device =>> false ACCESS TIME [*] Time => last change: Tue May 12 01:52:08 2020 [*] Time => last access: Sun Aug 2 05:20:57 2020 [*] Time => last modified: Fri Mar 27 05:50:36 2020 OWNER and GROUP [*] Owner: uid = 0 ; user = root [*] Group: gid = 0 ; group = root FILE PERMISSIONS =>> Stick Bits: suid - =>> Owner: - - x =>> Group: - - x =>> Others: - - x - Note: (r - read) ; (w - write); (x - execute) ; (suid - set-UID bit set) ; (sgid - set-GID bit set)
- Inspect regular file: ~/bin/fserver.jsh
$ ./file-info ~/bin/fserver.jsh [*] File size in bytes: 9968346 [*] File size in Kilo bytes: 9734.71 [*] File size in Mega bytes: 9.50656 [*] File type: Regular file TEST FILE TYPE [*] ? Is regular file =>> true [*] ? Is directory =>> false [*] ? Is symbolic link =>> false [*] ? Is block device =>> false [*] ? Is character device =>> false ACCESS TIME [*] Time => last change: Tue May 12 12:35:32 2020 [*] Time => last access: Tue Aug 4 03:21:06 2020 [*] Time => last modified: Mon May 11 22:52:14 2020 OWNER and GROUP [*] Owner: uid = 1000 ; user = myuser [*] Group: gid = 1000 ; group = myuser FILE PERMISSIONS =>> Stick Bits: - - =>> Owner: r w x =>> Group: r w - =>> Others: r - - - Note: (r - read) ; (w - write); (x - execute) ; (suid - set-UID bit set) ; (sgid - set-GID bit set)
- Inspect block device file /dev/sda that represets the main disk partition.
$ ./file-info /dev/sda [*] File size in bytes: 0 [*] File size in Kilo bytes: 0 [*] File size in Mega bytes: 0 [*] File type: Block device file TEST FILE TYPE [*] ? Is regular file =>> false [*] ? Is directory =>> false [*] ? Is symbolic link =>> false [*] ? Is block device =>> true [*] ? Is character device =>> false ACCESS TIME [*] Time => last change: Wed Jul 8 13:48:48 2020 [*] Time => last access: Wed Jul 8 13:49:09 2020 [*] Time => last modified: Wed Jul 8 13:48:48 2020 OWNER and GROUP [*] Owner: uid = 0 ; user = root [*] Group: gid = 6 ; group = disk FILE PERMISSIONS =>> Stick Bits: - - =>> Owner: r w - =>> Group: r w - =>> Others: - - - - Note: (r - read) ; (w - write); (x - execute) ; (suid - set-UID bit set) ; (sgid - set-GID bit set)
- Inspect character device file /dev/mem that represents the physical memory (RAM memory).
$ ./file-info /dev/mem [*] File size in bytes: 0 [*] File size in Kilo bytes: 0 [*] File size in Mega bytes: 0 [*] File type: Character device file TEST FILE TYPE [*] ? Is regular file =>> false [*] ? Is directory =>> false [*] ? Is symbolic link =>> false [*] ? Is block device =>> false [*] ? Is character device =>> true ACCESS TIME [*] Time => last change: Wed Jul 8 13:48:49 2020 [*] Time => last access: Wed Jul 8 13:48:49 2020 [*] Time => last modified: Wed Jul 8 13:48:49 2020 OWNER and GROUP [*] Owner: uid = 0 ; user = root [*] Group: gid = 9 ; group = kmem FILE PERMISSIONS =>> Stick Bits: - - =>> Owner: r w - =>> Group: r - - =>> Others: - - - - Note: (r - read) ; (w - write); (x - execute) ; (suid - set-UID bit set) ; (sgid - set-GID bit set)
1.17 Information about current process
File: current-process.cpp
#include <iostream> #include <string> #include <cstring> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <limits.h> // PATH_MAX constant (macro) auto get_cwd() -> std::string; auto set_cwd(const std::string& path) -> void; auto read_symlink(std::string const& path) -> std::string; int main(int argc, char** argv) { // Unique process ID (identifier) number fprintf(stdout, " => Process PID = %d \n", ::getpid()); // PID for parent process fprintf(stdout, " => Process PID = %d \n", ::getppid()); // Current directory fprintf(stdout, " => Current directory = %s \n", get_cwd().c_str()); fprintf(stdout, "\n Change the curren directory to /etc \n"); set_cwd("/etc"); fprintf(stdout, " => Current directory = %s \n", get_cwd().c_str()); // Note: It only works on Linux. The directory /proc/self is a pseudo-directory // whith pseudo-files containing information about the current process. // // the file /proc/self/exe is a symbolic link to the current executable. std::string exe_file = read_symlink("/proc/self/exe"); fprintf(stdout, " => Absolute Path of current executable = %s \n", exe_file.c_str()); // Current directory std::string exe_dir = read_symlink("/proc/self/cwd"); fprintf(stdout, " => Current directory of this executable = %s \n", exe_dir.c_str()); fprintf(stdout, " [INFO] Finish Ok. \n"); return 0; } // ----------- Functions Implementations ------------// // /** Get current working directory of current process */ auto get_cwd() -> std::string { char* buffer = ::getcwd(nullptr, 0); auto cwd = std::string(buffer); // Note buffer was allocated with malloc free(buffer); return cwd; } /** Set current working directory for current process. */ auto set_cwd(const std::string& path) -> void { int status = ::chdir(path.c_str()); if(status < 0) throw std::runtime_error("Failed to change directory."); } /** Read value of symbolic link * * Requires: <limits.h>, <sys/types.h>, <sys/stats.h> */ auto read_symlink(std::string const& path) -> std::string { // Create a buffer with size PATH_MAX + 1 filled with 0 ('\0'), null characters std::string buffer(PATH_MAX, 0); // ssize_t readlink(const char *pathname, char *buf, size_t bufsiz); ssize_t nread = ::readlink(path.c_str(), &buffer[0], PATH_MAX); if(nread == -1){ fprintf(stderr, " Error: %s \n", strerror(errno)); throw std::runtime_error("Error: unable to read symlink. Check 'errno' variable"); } buffer.resize(nread); return buffer; }
Building:
$ >> g++ current-process.cpp -o current-process.elf -Wall -Wextra -ggdb
Running:
$ >> ./current-process.elf => Process PID = 719155 => Process PID = 678179 => Current directory = /home/mxpkf8/temp-projects/unix-explore Change the curren directory to /etc => Current directory = /etc => Absolute Path of current executable = /home/mxpkf8/temp-projects/unix-explore/current-process.elf => Current directory of this executable = /etc [INFO] Finish Ok.
1.18 Dynamic Loading Shared Libraries / dlopen() API
1.18.1 Overview
The dlopen API provide access to the dynamic linker services allowing the current process to load and unload shared objects or shared libraries in its address space (virtual memory).
This API for loading shared libraries is not only available on Linux, it also can be found in FreeBSD, NetBSD, MacOSX, Android and so on.
Use cases:
- Load new code at-runtime
- Load third-party code a runtime
- Plugin systems, extensions or addons.
- Load native code extensions in languages with interpreters written in C, such as Python, Ruby, Lua and so on. Note: Python native extensions are shared libraries.
The Dlopen() API is available on most Unix-like operating systems, such as:
- Linux
- MacOSX
- FreeBDS
- NetBSD
- OpenBSD
- QNX
Documentation:
- http://gnu.wiki/man3/dlopen.3.php
- Dlopen API - Linux Manpages
- Free BSD - dlopen API documentation
- Dlopen - Oracle Documentation
- Dlopen - QNX documentation
- Dynamically Loaded (DL) Libraries
Further Reading:
- Inside the history on shared libraries and dynamic loading
- It 's all in the libs - Building a Plugin system using Dynamic Loading
- Building And Using Static And Shared "C" Libraries
- More Shared Libraries-Dynamic Loading and Unloading
- https://grugq.github.io/docs/subversiveld.pdf
- universal-dynamic-loader for Linux - "min-dl: minimal dynamic linker implementation"
Headers and libraries:
- Header: #include <dlfcn.h>
- Linking: (-ldl) flag
Function dlopen():
- Doc: $ man dlopen
- Loads a shared library and returns a handle or opaque pointer casted as void pointer. The term opaque means that the implementation is hidden and the pointer is only meant to be passed around to other functions.
- Note: When this function fails, it returns null pointer.
void* dlopen(const char* filename, int flags);
Function dlsym():
- Doc: $ man dlsym
- Obtains address of a shared library symbol from handle (from dlopen).
- Params:
- handle => Shared library handle obtained from dlopen
- symbol => Name of symbol to be loaded.
- Return:
- Address of symbol casted as void*. It can be a function-pointer or a pointer to global variable. If the symbol is not found, the function returns a null pointer.
void* dlsym(void* handle, const char* symbol);
Function dlvsym():
- Doc: $ man dlvsym
- Loads a specific version of a symbol.
void* dlvsym(void *handle, char *symbol, char *version);
Function dlclose():
- Doc: $ man dlclose()
int dlclose(void *handle);
Function dlerror()
- Obtaines error messages from dlopen API.
- Doc: $ man dlerror()
char* dlerror(void);
Simplified type signatures with a C++-friendly notation
// Opaque pointer for shared library (shared object) handle using HND = void* ; // Opaque pointer for shared library symbol using SYM = void* ; HND dlopen( const char* filename, int flags); SYM dlsym( HND handle, const char* symbol); SYM dlvsym( HND handle, char *symbol, char *version); int dlclose( HND handle );
Usage example
#include <dlfcn.h> // Opaque pointer for shared library (shared object) handle using HND = void* ; // Opaque pointer for shared library symbol using SYM = void* ; void load_library() { // Load shared library HND hnd = dlopen("/path/to/shared-library.so", RTLD_LAZY | RTLD_GLOBAL); if(hnd == nullptr) { std::cerr << " [ERROR] " << dlerror() << "\n"; throw std::runtime_error("Error: unable to load shared library"); } // Load symbol SYM hsym = dlsym(hnd, "name_of_function"); // if(!hsym) if(hsym == nullptr) { throw std::runtime_error("Error: symbol not found"); } // Type alias for function pointer in modern C++ // => Note: The function to be loaded can only have C-linkage. using name_of_function_t = void (*) (int param0, double param1, const char* param3); // Type alias for function pointer in old C++ (C++98) typedef void (*name_of_function_t) (int param0, double param1, const char* param3); auto name_of_function_ptr = reinterpret_cast<name_of_function_t>(hsym); // Call loaded function (Function pointer) name_of_function_ptr(100, 2.51, "string"); // Unload shared library dlclose(hsym); }
1.18.2 Loading Gtk2/Gtk3 - low level
The following sample code is a GTk3 or GTK2 GUI graphical user interface application with GTK functions loaded from a GTK shared library, without any compile-time linking. The advantage of this approach is the greater portability across different versions of GTk and the ability to switch between GT3 and GT2.
GIST:
File: CMakeLists.txt
cmake_minimum_required(VERSION 3.9) project(gtk-sample) #========== Global Configurations =============# #----------------------------------------------# set(CMAKE_CXX_STANDARD 17) set(CMAKE_VERBOSE_MAKEFILE ON) add_executable( gtk-dlopen gtk-dlopen.cpp) target_link_libraries( gtk-dlopen dl )
File: gtk-dlopen.cpp
#include <iostream> #include <cstring> #include <cassert> // Uses: dlopen(), dlclose(), ... #include <dlfcn.h> // ---- Copied from GTK headers ------// typedef char gchar; typedef short gshort; typedef long glong; typedef int gint; typedef gint gboolean; typedef unsigned char guchar; typedef unsigned short gushort; typedef unsigned long gulong; typedef unsigned int guint; typedef float gfloat; typedef double gdouble; typedef void* gpointer; typedef const void* gconstpointer; enum class GtkWindowType { GTK_WINDOW_TOPLEVEL, GTK_WINDOW_POPUP }; enum class GConnectFlags { G_CONNECT_AFTER = 1 << 0, G_CONNECT_SWAPPED = 1 << 1 }; template<typename TFun> TFun load_symbol(void* hnd, std::string symbol) { void* hSym = dlsym(hnd, symbol.c_str()); if(hSym == nullptr) { std::string msg = std::string(" [Error] symbol not found: ") + symbol; throw std::runtime_error(msg); } return reinterpret_cast<TFun>(hSym); } // Opaque type (aka incomplete type) struct GClosure; // -------- Function Pointers type aliases -------------------// using GCallback = void (*) (void); using GClosureNotify = void (*) (gpointer data, GClosure* closure); using g_signal_connect_data_t = gulong (*) ( gpointer instance , const gchar* detailed_signal , GCallback c_handler , gpointer data , GClosureNotify destroy_data , GConnectFlags connect_flags ); int main(int argc, char** argv) { std::string shared_lib = [&]() -> std::string { if(argc < 2) return "libgtk-3.so.0"; return argv[1]; }(); std::cout << " [INFO] Loading shared library: " << shared_lib << "\n"; // void *dlopen(const char *filename, int flags); // Handle to shared library void* hnd = dlopen(shared_lib.c_str(), RTLD_NOW | RTLD_GLOBAL); if(hnd == nullptr){ fprintf(stderr, "%s\n", dlerror()); } assert( hnd != nullptr ); void* hSym = nullptr; // --------- Load gtk_init_check function pointer ------- // hSym = dlsym(hnd, "gtk_init_check"); assert(hSym != nullptr); using gtk_init_check_t = gboolean (*) (int* argc, char*** argv); auto gtk_init_check = reinterpret_cast<gtk_init_check_t>(hSym); // ------- Load remaining function pointers (symbols) --------// //-----------------------------------------------------------// // opaque pointer struct GtkWidget; using gkt_window_new_t = GtkWidget* (*) (int); auto gtk_window_new = load_symbol<gkt_window_new_t>(hnd, "gtk_window_new"); auto gtk_widget_show = load_symbol<void (*) (GtkWidget*)>(hnd, "gtk_widget_show"); auto gtk_main = load_symbol<void (*) ()>(hnd, "gtk_main"); using gtk_window_set_title_t = void (*) (GtkWidget* window, const gchar* title); auto gtk_window_set_title = load_symbol<gtk_window_set_title_t>(hnd, "gtk_window_set_title"); using gtk_widget_set_size_t = void (*) (GtkWidget*, gint, gint); auto gtk_widget_set_size = load_symbol<gtk_widget_set_size_t>(hnd, "gtk_widget_set_size_request"); auto gtk_main_quit = load_symbol<void (*) ()>(hnd, "gtk_main_quit"); auto gtk_signal_connect_data = load_symbol<g_signal_connect_data_t>(hnd, "g_signal_connect_data"); /** ------- Build Window GUI - Graphical User Interface ----------**/ // Call function pointer gtk_init_check(&argc, &argv); GtkWidget* window = gtk_window_new( (int) GtkWindowType::GTK_WINDOW_TOPLEVEL); gtk_widget_set_size(window, 400, 500); gtk_window_set_title(window, "My GTK Window"); gtk_widget_show(window); gtk_signal_connect_data( window // Widget , "destroy" // Event name , gtk_main_quit // Callback , nullptr // Pointer to data (closure ) , nullptr , (GConnectFlags) 0 // GConnect flags ); std::cout << " [INFO] Window running. OK! " << "\n"; gtk_main(); // Always close shared library handler. dlclose(hnd); std::cout << " [INFO] Shutdown gracefully. Ok. \n" ; return EXIT_SUCCESS; }
Building:
$ git clone https://gist.github.com/d81dd9c422dc93ad4104b5e79259afdf && cd gist $ g++ gtk-dlopen.cpp -o gtk-dlopen.bin -std=c++1z -ldl -ggdb -Wall -Wextra
Checking the dependencies of gtk-dlopen.bin
$ >> ldd gtk-dlopen.bin linux-vdso.so.1 (0x00007ffd23fc4000) libdl.so.2 => /lib64/libdl.so.2 (0x00007f6a78428000) libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007f6a78238000) libm.so.6 => /lib64/libm.so.6 (0x00007f6a780f2000) libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f6a780d7000) libc.so.6 => /lib64/libc.so.6 (0x00007f6a77f0d000) /lib64/ld-linux-x86-64.so.2 (0x00007f6a78453000)
Running: (load GTK shared library /lib64/libgtk-3.so.0)
$ >> ./gtk-dlopen.bin [INFO] Loading shared library: /lib64/libgtk-3.so.0 [INFO] Window running. OK! [INFO] Shutdown gracefully. Ok.
Running: (load GK2 shared library )
$ >> ./gtk-dlopen.bin /usr/lib64/libgtk-x11-2.0.so [INFO] Loading shared library: /usr/lib64/libgtk-x11-2.0.so [INFO] Window running. OK! [INFO] Shutdown gracefully. Ok.
Verify exported symbols by gtk3 shared library:
$ >> nm -D /lib64/libgtk-3.so.0 | grep init ... ... ... ... .. ... ... ... ... .. U g_async_initable_new_async U g_async_initable_new_finish U g_datalist_init U g_hash_table_iter_init U g_initable_get_type U g_initable_new U g_initially_unowned_get_type U g_mutex_init U g_once_init_enter ... ... ... ... .. ... ... ... ... ..
Screenshot:
1.18.3 Loading Gtk2/Gtk3 - high level C++ wrapper
This proof-of-concept code contains a high-level wrapper class SharedLibrary for abstracting away the dlopen() API. This class is used for loading GTK3 or GTk2 shared library at runtime and then showing modal notifications dialogs. The advantage of the dynamic loading approach is the flexibility, portability and easier deployment as the binary does not depends on any particular GTK shared library version.
Note:
- The code of the class SharedLibrary can be reused on any other unix-like operating system with dlopen() API, such as FreeBDS, NetBSD, MacOSX and so on.
- The Windows API for dynamic loading is LoadLibrary(), GetProcAddress().
GIST:
Project Files
File: CMakeLists.txt
cmake_minimum_required(VERSION 3.9) project(gtk-dynamic-load-dialog) set(CMAKE_CXX_STANDARD 17) set(CMAKE_VERBOSE_MAKEFILE ON) # ---- TARGETS Configuration ---------------# add_executable( gtk-dialog gtk-dialog.cpp gtk_types.hpp) target_link_libraries( gtk-dialog dl )
File: gtk-dialog.cpp
#include <iostream> #include <string> #include <cstring> #include <cassert> // Uses: dlopen(), dlclose(), ... #include <dlfcn.h> #include "gtk_types.hpp" /** Requires header: <dlfcn.h> and linking flag (-ldl) */ class SharedLibrary { public: using HND = void*; using HSYM = void*; private: HND m_hnd = nullptr; std::string m_lib = "" ; int m_flags = 0; public: SharedLibrary(){} ~SharedLibrary(){ this->unload(); } // Constructor delegation SharedLibrary(std::string library) : SharedLibrary(library, RTLD_NOW | RTLD_GLOBAL) { } SharedLibrary(std::string library, int flags) { this->load(library, flags); } // Disable copy constructor and copy-assignment operator SharedLibrary(SharedLibrary const &) = delete; SharedLibrary& operator=(SharedLibrary const &) = delete; // Move constructor SharedLibrary(SharedLibrary&& rhs) { std::swap(m_hnd, rhs.m_hnd); std::swap(m_lib, rhs.m_lib); std::swap(m_flags, rhs.m_flags); } // Move assignment operator SharedLibrary& operator=(SharedLibrary&& rhs) { this->unload(); std::swap(m_hnd, rhs.m_hnd); std::swap(m_lib, rhs.m_lib); std::swap(m_flags, rhs.m_flags); return *this; } HND handler() const { return m_hnd; } bool is_loaded() const { return m_hnd != nullptr; } std::string library() const { return m_lib; } /** Retrieve symbol from shared library */ HSYM symbol_ptr(std::string name) const { return ::dlsym(m_hnd, name.c_str()); } /** @brief Get symbol from shared library * Note: The caller is resposible for checking if the symbol is (null) nullptr. */ template<typename TFun> TFun symbol(std::string name) const { HSYM sym = ::dlsym(m_hnd, name.c_str()); return reinterpret_cast<TFun>(sym); } /** @brief Attempts to load symbol and throws exception, if symbol is not found. */ template<typename TFun> TFun symbol_or_fail(std::string name) const { HSYM sym = ::dlsym(m_hnd, name.c_str()); if(!sym){ std::string msg = " [ERROR] Dlopen / dlsym() => unable find symbol"; msg = msg + " <" + name + ">"; throw std::runtime_error(msg); } return reinterpret_cast<TFun>(sym); } void load(std::string library, int flags) { if(!this->is_loaded()) { this->unload(); } m_lib = library; m_flags = flags; m_hnd = dlopen(library.c_str(), RTLD_NOW | RTLD_GLOBAL); if(m_hnd == nullptr){ std::string msg = " [ERROR] DlopenError: "; msg = msg + dlerror(); throw std::runtime_error(msg); } } void reload() { this->unload(); this->load(m_lib, m_flags); } void unload() { if(!this->is_loaded()) { return; } ::dlclose(m_hnd); m_hnd = nullptr; } }; // --- End of class SharedLibrary() ---- // using namespace GtkPP::fpointer; using namespace GtkPP::enums; struct GtkLib { SharedLibrary slib; GtkLib(std::string library) : slib( std::move(SharedLibrary(library)) ) { gtk_init_check = slib.symbol_or_fail<gtk_init_check_t>("gtk_init_check"); gtk_main_quit = slib.symbol_or_fail<gtk_main_quit_t>("gtk_main_quit"); // gtk_main = slib.symbol_or_fail<void (*) ()>("gtk_main"); gtk_message_dialog_new = slib.symbol_or_fail<gtk_message_dialog_new_t>("gtk_message_dialog_new"); gtk_widget_destroy = slib.symbol_or_fail<gtk_widget_destroy_t>("gtk_widget_destroy"); gtk_dialog_run = slib.symbol_or_fail<gtk_dialog_run_t>("gtk_dialog_run"); gtk_window_set_title = slib.symbol<gtk_window_set_title_t>("gtk_window_set_title"); } gtk_init_check_t gtk_init_check; gtk_main_quit_t gtk_main_quit; // gtk_main_t gtk_main; gtk_message_dialog_new_t gtk_message_dialog_new; gtk_widget_destroy_t gtk_widget_destroy; gtk_dialog_run_t gtk_dialog_run; gtk_window_set_title_t gtk_window_set_title; // Message box notification void msgbox( std::string title , std::string text , GtkMessageType mtype = GtkMessageType::GTK_MESSAGE_INFO ) { GtkWidget* dialog = this->gtk_message_dialog_new( nullptr , (int) GtkDialogFlags::GTK_DIALOG_MODAL , (int) mtype , (int) GtkButtonsType::GTK_BUTTONS_OK , text.c_str() ); assert( dialog != nullptr ); this->gtk_window_set_title(dialog, title.c_str()); this->gtk_dialog_run(dialog); std::cout << " [INFO] Dialog destroyed OK " << "\n"; this->gtk_widget_destroy(dialog); } }; int main(int argc, char** argv) { // User can select his own GTK shared library, if it is not // found. He can also select GTK2 shared library. auto shared_lib = [&]() -> std::string { if(argc < 2) return "libgtk-3.so.0"; return argv[1]; }(); std::cout << " [INFO] Loading shared library: " << shared_lib << "\n"; auto gtk = GtkLib{shared_lib}; // Alwas start the library before calling any GTK function gtk.gtk_init_check(&argc, &argv); gtk.msgbox("User notification", "Download completed. Ok.", GtkMessageType::GTK_MESSAGE_INFO ); gtk.msgbox("Runtime error", "KERNEL PANIC!! Reboot NOW!!", GtkMessageType::GTK_MESSAGE_ERROR ); std::cout << " [INFO] Shutdown gracefully. Ok. \n" ; return EXIT_SUCCESS; }
File: gtk_types.hpp
// ------ C++ Wrappers for dynamically loading GTK ----- // #ifndef _GTK_TYPES_HPP #define _GTK_TYPES_HPP namespace GtkPP { namespace base { using gchar = char; using gshort = short; using glong = long; using gint = int; using gboolean = gint; using guchar = unsigned short; using gulong = unsigned long; using guint = unsigned int; using gfloat = float; using gdouble = double; using gpointer = void*; using gconstpointer = const void*; }; // ------- Opaque Types / incomplete types ---------// namespace widget { struct GtkWidget; struct GClosure; struct GtkWidget; struct GtkWindow; struct GtkDialog; }; namespace enums { enum class GtkWindowType { GTK_WINDOW_TOPLEVEL, GTK_WINDOW_POPUP }; enum class GConnectFlags { G_CONNECT_AFTER = 1 << 0, G_CONNECT_SWAPPED = 1 << 1 }; enum class GtkMessageType { GTK_MESSAGE_INFO, GTK_MESSAGE_WARNING, GTK_MESSAGE_QUESTION, GTK_MESSAGE_ERROR, GTK_MESSAGE_OTHER }; enum class GtkButtonsType { GTK_BUTTONS_NONE, GTK_BUTTONS_OK, GTK_BUTTONS_CLOSE, GTK_BUTTONS_CANCEL, GTK_BUTTONS_YES_NO, GTK_BUTTONS_OK_CANCEL }; enum class GtkDialogFlags { GTK_DIALOG_MODAL = 1 << 0, GTK_DIALOG_DESTROY_WITH_PARENT = 1 << 1, GTK_DIALOG_USE_HEADER_BAR = 1 << 2 }; }; /** @brief Function pointers type aliases */ namespace fpointer { using namespace base; using namespace widget; using enums::GConnectFlags; using GtkDialogFlags_t = int; using GtkMessageType_t = int; using GtkButtonsType_t = int; using gtk_init_check_t = gboolean (*) (int* argc, char*** argv); using gtk_dialog_run_t = gint (*) (void* dialog); using gtk_widget_destroy_t = void (*) (GtkWidget* widget); using gtk_window_set_title_t = void (*) (GtkWidget* window, const gchar* title); using gtk_main_quit_t = void (*) (); using gtk_window_set_title_t = void (*) (GtkWidget* window, const gchar* title); using GCallback = void (*) (void); using GClosureNotify = void (*) (gpointer data, GClosure* closure); using g_signal_connect_data_t = gulong (*) ( gpointer instance , const gchar* detailed_signal , GCallback c_handler , gpointer data , GClosureNotify destroy_data , GConnectFlags connect_flags ); using gtk_message_dialog_new_t = GtkWidget* (*) ( GtkWindow *parent, GtkDialogFlags_t flags, GtkMessageType_t type, GtkButtonsType_t buttons, const gchar* message_format ); }; }; #endif
Building and running:
# Download $ >> git clone https://gist.github.com/6bbb3b9491d4fad01d3875a559b7365b gtk-dialog && cd gtk-dialog # Configure $ >> cmake --config Debug -H. -B_build # Build $ >> cmake --build _build --target all # Run $ >> _build/gtk-dialog [INFO] Loading shared library: libgtk-3.so.0 [INFO] Dialog destroyed OK [INFO] Dialog destroyed OK [INFO] Shutdown gracefully. Ok.
Screenshots:
1.18.4 Loading Gtk2/Gtk3 using D DLang language
The following code creates Gtk graphics user interface by loading Gtk function pointers from Gtk shared library using D (DLang) programming language.
File: gtk_dlopen.d
import core.stdc.stdio; import io = std.stdio; import std.range: empty; import std.algorithm : map, joiner; import std.string; import str = std.string; import std.array: array; import std.conv: to; import DLL = core.sys.posix.dlfcn; import core.sys.posix.dlfcn; struct ButtonState { int counter = 0; gtk_message_dialog_new_t gtk_message_dialog_new = null; gtk_window_set_title_t gtk_window_set_title = null; gtk_dialog_run_t gtk_dialog_run = null; gtk_widget_destroy_t gtk_widget_destroy = null; } extern(C) void buttonCallback(GtkWidget* widget, gpointer data) { auto state = cast(ButtonState*) data; state.counter = state.counter + 1; io.writeln(" [TRACE] Button Called => Counter = ", state.counter); auto message = " [INFO] Counter changed to " ~ to!string(state.counter); auto dialog = state.gtk_message_dialog_new( null , GtkDialogFlags.GTK_DIALOG_MODAL , GtkMessageType.GTK_MESSAGE_INFO , GtkButtonsType.GTK_BUTTONS_OK , message.toStringz ); state.gtk_window_set_title(dialog, "Gtk Info dialog from DLang"); // io.writeln(" [TRACE] Run message dialog "); state.gtk_dialog_run(dialog); // Dispose GTk object state.gtk_widget_destroy(dialog); } int main(string[] args) { printf(" [TRACE] Application started Ok. \n"); void* dll = DLL.dlopen("libgtk-3.so.0", DLL.RTLD_GLOBAL | DLL.RTLD_LAZY); if (!dll) { fprintf(stderr, " [ERROR] dlopen error: %s\n", DLL.dlerror()); return 1; } //-------------------------------------------// // Load GTK Function pointers // //-------------------------------------------// auto gtk_init_check = cast(gtk_init_check_t)( dll.dlsym("gtk_init_check") ); auto gtk_window_new = cast(gtk_window_new_t)( dll.dlsym("gtk_window_new") ); auto gtk_widget_show = cast(gtk_widget_show_t)( dll.dlsym("gtk_widget_show") ); auto gtk_main = cast(gtk_main_t)( dll.dlsym("gtk_main") ); auto gtk_window_set_title = cast(gtk_window_set_title_t)( dll.dlsym("gtk_window_set_title") ); auto gtk_widget_set_size = cast(gtk_widget_set_size_t)( dll.dlsym("gtk_widget_set_size_request") ); auto gtk_main_quit = cast(gtk_main_quit_t)( dll.dlsym("gtk_main_quit") ); auto g_signal_connect_data = cast(g_signal_connect_data_t)( dll.dlsym("g_signal_connect_data") ); auto gtk_widget_show_all = cast(gtk_widget_show_all_t)( dll.dlsym("gtk_widget_show_all") ); auto gtk_button_new_with_label = cast(gtk_button_new_with_label_t)( dll.dlsym("gtk_button_new_with_label") ); auto gtk_container_add = cast(gtk_container_add_t)( dll.dlsym("gtk_container_add") ); auto gtk_widget_set_size_request = cast(gtk_widget_set_size_request_t)( dll.dlsym("gtk_widget_set_size_request") ); auto gtk_dialog_run = cast(gtk_dialog_run_t)( dll.dlsym("gtk_dialog_run") ); auto gtk_message_dialog_new = cast(gtk_message_dialog_new_t)( dll.dlsym("gtk_message_dialog_new") ); auto gtk_widget_destroy = cast(gtk_widget_destroy_t)( dll.dlsym("gtk_widget_destroy") ); auto gtk_get_major_version = cast(gtk_get_major_version_t)( dll.dlsym("gtk_get_major_version") ); assert(gtk_message_dialog_new != null); if( gtk_get_major_version == null) { printf(" [TRACE] Running Gtk - 2.X \n"); } else { printf(" [TRACE] Running Gtk - 3.X \n"); } //------------------------------------// // Initialize GTK // //------------------------------------// int n = cast(int) args.length; auto argarr = args.map!( a => (a.dup ~ '\0' ).ptr ).array; auto argv = argarr.ptr; // This function must always be called before // calling any other GTK function/procedure. gtk_init_check(&n, &argv); //------------------------------------// // Build GTK Widgets // //------------------------------------// auto window = gtk_window_new( GtkWindowType.GTK_WINDOW_TOPLEVEL ); assert( window != null ); gtk_widget_set_size_request(window, 399, 500); gtk_window_set_title(window, "My Gtk Window from Dlang (D)".toStringz ); auto button = gtk_button_new_with_label("Click me now"); assert( button != null ); gtk_container_add(window, button); gtk_widget_show_all( window ); //--------------------------------------// // Set up event handlers // //--------------------------------------// // Call gtk_main_quit() function when the user closes the window. g_signal_connect_data( window ,"destroy".toStringz , gtk_main_quit , null , null , cast(GConnectFlags) 0 ); ButtonState state; state.counter = 100; state.gtk_message_dialog_new = gtk_message_dialog_new; state.gtk_widget_destroy = gtk_widget_destroy; state.gtk_window_set_title = gtk_window_set_title; state.gtk_dialog_run = gtk_dialog_run; gtk_widget_destroy = gtk_widget_destroy; g_signal_connect_data( button , "clicked".toStringz , &buttonCallback , &state , null , cast(GConnectFlags) 0 ); printf(" [TRACE] Connect button event "); //--------------------------------------// // Run Event Loop // //--------------------------------------// // Blocks current thread and run GTK event loop. gtk_main(); return 0; } // ------------------------------------------ // alias gchar = char; alias gshort = short; alias glong = long; alias gint = int; alias gboolean = gint; alias guchar = ushort; // unsigned short alias gulong = ulong; // unsigned long alias guint = uint; alias gfloat = float; alias gdouble = double; alias gpointer = void*; alias gconstpointer = const void*; alias GClosure = void*; alias GtkWidget = void; alias GtkWindow = void; alias GtkDialog = void; enum GtkWindowType { GTK_WINDOW_TOPLEVEL = 0 ,GTK_WINDOW_POPUP = 1 } enum GConnectFlags { G_CONNECT_AFTER = 1 , G_CONNECT_SWAPPED = 2 } enum GtkMessageType { GTK_MESSAGE_INFO , GTK_MESSAGE_WARNING , GTK_MESSAGE_QUESTION , GTK_MESSAGE_ERROR , GTK_MESSAGE_OTHER } enum GtkDialogFlags { GTK_DIALOG_MODAL = 1 << 0 , GTK_DIALOG_DESTROY_WITH_PARENT = 1 << 1 , GTK_DIALOG_USE_HEADER_BAR = 1 << 2 } enum GtkButtonsType { , GTK_BUTTONS_NONE , GTK_BUTTONS_OK , GTK_BUTTONS_CLOSE , GTK_BUTTONS_CANCEL , GTK_BUTTONS_YES_NO , GTK_BUTTONS_OK_CANCEL } alias gtk_init_check_t = extern(C) gboolean function (int* argc, char*** argv); alias gtk_dialog_run_t = extern(C) gint function (void* dialog); alias gtk_widget_destroy_t = extern(C) void function (GtkWidget* widget); alias gtk_main_quit_t = extern(C) void function (); alias gtk_window_set_title_t = extern(C) void function (GtkWidget* window, const gchar* title); // alias GCallback = extern(C) void function (); alias GCallback = extern(C) void function(GtkWidget* widget, gpointer data); alias GClosureNotify = extern(C) void function (gpointer data, GClosure* closure); alias gtk_window_new_t = extern(C) GtkWidget* function (int); alias gtk_widget_show_t = extern(C) void function(GtkWidget*); alias gtk_widget_set_size_t = extern(C) void function (GtkWidget* widget, gint width, gint height); alias gtk_widget_show_all_t = extern(C) void function (GtkWidget* widget); alias gtk_main_t = extern(C) void function(); alias gtk_button_new_with_label_t = extern(C) GtkWidget* function(const char* label); alias gtk_container_add_t = extern(C) void function(GtkWidget* container, GtkWidget* widget); alias gtk_widget_set_size_request_t = extern(C) void function(GtkWidget* widget, gint width, gint height); alias gtk_get_major_version_t = extern(C) void function (); alias g_signal_connect_data_t = extern(C) gulong function ( gpointer instance , const gchar* detailed_signal , void* c_handler // , GCallback c_handler , gpointer data , GClosureNotify destroy_data , GConnectFlags connect_flags ); alias gtk_message_dialog_new_t = extern(C) GtkWidget* function ( GtkWindow *parent, GtkDialogFlags flags, GtkMessageType type, GtkButtonsType buttons, const gchar* message_format );
Building:
$ dmd -g gtk_dlopen.d
Building with docker (run the compiler without installing it):
$ docker run --rm --interactive --tty --volume="$PWD":/pwd --workdir=/pwd dlangchina/dlang-dmd dmd -g gtk_dlopen.d
Running:
$ ./gtk_dlopen
1.18.5 Loading Gtk2/Gtk3 using Nim language
The following code creates a sample Gtk2 or Gtk3 graphics interface by dynamic loading Gtk symbols using dynlib Nim module, a dlopen()/dlclose() cross-platform wrapper, without linking against neither Gtk2 nor Gtk3 shared libraries at compile-time.
Reading:
- https://docs.gtk.org/gtk3/index.html
- => Search GTK functions and their type signatures.
- https://nim-lang.org/
- Nim (programming language)
- UFS - Universal Function Call
- https://nim-lang.org/docs/dynlib.html
- => Cross-platform wrapper to dlopen() on Unix-like operating systems and LoadLibrary() on Windows.
File: gtk_dlopen.nim
import std/dynlib import os type gchar = cchar # char gshort = cshort # short glong = clong # long gint = cint # int gboolean = gint guchar = cuchar # unsigned char gushort = cushort # unsigned short gulong = culong # unsigned long guint = cint # unsigned int gfloat = cfloat gdouble = cdouble gpointer = pointer # void* (Opaque pointer) gconstpointer = pointer # const void* type GtkWidget = pointer GConnectFlags = cint GtkDialogFlags = cint GtkMessageType = cint GtkButtonsType = cint # Enumeration: GtkWindowType const GTK_WINDOW_TOPLEVEL = 0 GTK_WINDOW_POPUP = 1 # Enumeration: GConnectFlags G_CONNECT_AFTER = 1 G_CONNECT_SWAPPED = 2 # Enumeration: GtkMessageType const GTK_MESSAGE_INFO = 0 GTK_MESSAGE_WARNING = 1 GTK_MESSAGE_QUESTION = 2 GTK_MESSAGE_ERROR = 3 GTK_MESSAGE_OTHER = 4 # Enumeration: GtkDialogFlags const GTK_DIALOG_MODAL = 1 GTK_DIALOG_DESTROY_WITH_PARENT = 2 GTK_DIALOG_USE_HEADER_BAR = 4 # Enumeration: GtkButtonsType const GTK_BUTTONS_NONE = 0 GTK_BUTTONS_OK = 1 GTK_BUTTONS_CLOSE = 2 GTK_BUTTONS_CANCEL = 3 GTK_BUTTONS_YES_NO = 4 GTK_BUTTONS_OK_CANCEL = 5 #-------------------------------------------------# # GTK Function Pointers Declarations # #-------------------------------------------------# type # using gtk_init_check_t = gboolean (*) (int* argc, char*** argv); # See: https://stackoverflow.com/questions/55646129/nim-argv-equivalent gtk_init_check_t = proc (argc: ptr cint, argv: cstringArray): gboolean {.gcsafe, stdcall.} # using gkt_window_new_t = GtkWidget* (*) (int); gtk_window_new_t = proc(param: cint): GtkWidget {.gcsafe, stdcall.} # void (*) (GtkWidget*) gtk_widget_show_t = proc(widget: GtkWidget): void {.gcsafe, stdcall.} # void (*) () gtk_main_t = proc(): void {.gcsafe, stdcall.} # using gtk_window_set_title_t = void (*) (GtkWidget* window, const gchar* title); gtk_window_set_title_t = proc(window: GtkWidget, title: cstring): void {.gcsafe, stdcall.} # using gtk_widget_set_size_t = void (*) (GtkWidget*, gint, gint); gtk_widget_set_size_t = proc(widget: GtkWidget, width: gint, height: gint): void {.gcsafe, stdcall.} # void gtk_widget_show_all ( GtkWidget* widget) gtk_widget_show_all_t = proc (widget: GtkWidget): void {.gcsafe, stdcall.} # GtkWidget* gtk_button_new_with_label ( const gchar* label) gtk_button_new_with_label_t = proc(label: cstring): GtkWidget {.gcsafe, stdcall.} # void gtk_container_add ( GtkContainer* container, GtkWidget* widget) # See: https://docs.gtk.org/gtk3/method.Container.add.html gtk_container_add_t = proc(container: GtkWidget, widget: GtkWidget ): void {.gcsafe, stdcall.} # void gtk_widget_set_size_request ( GtkWidget* widget, gint width, gint height) # See: https://docs.gtk.org/gtk3/method.Widget.set_size_request.html gtk_widget_set_size_request_t = proc( widget: GtkWidget , width: gint , height: gint) {.gcsafe, stdcall.} # void (*) () gtk_main_quit_t = proc(): void {.gcsafe, stdcall.} # void* (void pointer - opaque pointer) GClosure = pointer # void (*) (void); GCallback = proc(): void {.gcsafe, stdcall.} # void (*) (gpointer data, GClosure* closure); GClosureNotify = proc(data: gpointer, closure: GClosure): void ClickedCallback = proc(): void {.gcsafe, stdcall.} g_signal_connect_data_t = proc( instance: gpointer , detailed_signal: cstring , c_handler: GCallback , data: gpointer , destroy_data: GClosureNotify , connect_flag: GConnectFlags ): gulong {.gcsafe, stdcall.} gtk_message_dialog_new_t = proc( parent: GtkWidget , flags: GtkDialogFlags , ttype: GtkMessageType , buttons: GtkButtonsType , message_format: cstring ): GtkWidget {.gcsafe, stdcall.} # gint (*) (void* dialog); gtk_dialog_run_t = proc(dialog: GtkWidget): gint {.gcsafe, stdcall.} # using gtk_widget_destroy_t = void (*) (GtkWidget* widget); gtk_widget_destroy_t = proc(widget: GtkWidget): void {.gcsafe, stdcall.} # guint gtk_get_major_version (void) gtk_get_major_version_t = proc(): void {.gcsafe, stdcall.} #---------------------------------------------------# # Load Gtk function GTK from shared library # # # # GTK functions' type signature can be searched at: # # - https://docs.gtk.org/gtk3 # #---------------------------------------------------# proc loadSym(lib: dynlib.LibHandle, symbol: string): pointer = echo " [TRACE] Loading symbol = " & symbol let p = lib.symAddr(symbol) assert p != nil, ("Cannot load symbol = " & symbol) return p echo "\n\n ======== Program started Ok ==============" let dll = os.getEnv("GTK_LIB", "libgtk-3.so.0") let lib = dynlib.loadLib(dll) assert lib != nil, "Cannot load shared library" echo " [TRACE] Shared library " & dll & " loaded Ok." let gtk_init_check = cast[gtk_init_check_t]( lib.loadSym("gtk_init_check") ) let gtk_window_new = cast[gtk_window_new_t]( lib.loadSym("gtk_window_new") ) let gtk_widget_show = cast[gtk_widget_show_t]( lib.loadSym("gtk_widget_show") ) let gtk_main = cast[gtk_main_t]( lib.loadSym("gtk_main") ) let gtk_window_set_title = cast[gtk_window_set_title_t]( lib.loadSym("gtk_window_set_title") ) let gtk_widget_set_size = cast[gtk_widget_set_size_t]( lib.loadSym("gtk_widget_set_size_request") ) let gtk_main_quit = cast[gtk_main_quit_t]( lib.loadSym("gtk_main_quit") ) let g_signal_connect_data = cast[g_signal_connect_data_t]( lib.loadSym("g_signal_connect_data") ) let gtk_widget_show_all = cast[gtk_widget_show_all_t]( lib.loadSym("gtk_widget_show_all") ) let gtk_button_new_with_label = cast[gtk_button_new_with_label_t]( lib.loadSym("gtk_button_new_with_label") ) let gtk_container_add = cast[gtk_container_add_t]( lib.loadSym("gtk_container_add") ) let gtk_widget_set_size_request = cast[gtk_widget_set_size_request_t]( lib.loadSym("gtk_widget_set_size_request") ) let gtk_dialog_run = cast[gtk_dialog_run_t]( lib.loadSym("gtk_dialog_run") ) let gtk_message_dialog_new = cast[gtk_message_dialog_new_t]( lib.loadSym("gtk_message_dialog_new") ) let gtk_widget_destroy = cast[gtk_widget_destroy_t]( lib.loadSym("gtk_widget_destroy") ) let gtk_get_major_version = cast[gtk_get_major_version_t]( lib.symAddr("gtk_get_major_version") ) if gtk_get_major_version == nil: echo " [TRACE] Running Gtk - 2.X" else: echo " [TRACE] Running Gtk - 3.X" # gtk_main_quit() wrapper for logging proc gtk_app_quit(): void {.gcsafe, stdcall.} = echo " [TRACE] Quit GTK - Shutdown GTK event loop and unblocks current thread." gtk_main_quit() proc on_clicked(button: GtkWidget, callback: ClickedCallback): void = echo "I was clicked" discard g_signal_connect_data(button, "clicked", callback, nil, nil, cint 0) proc msgbox_info(parent: GtkWidget, title: cstring, text: cstring): void = let dialog = gtk_message_dialog_new( parent, GTK_DIALOG_MODAL, GTK_MESSAGE_INFO , GTK_BUTTONS_OK, text) assert dialog != nil gtk_window_set_title dialog, title discard gtk_dialog_run dialog gtk_widget_destroy dialog # Start GTK widgets proc gtk_start(): void = var nargv = newSeq[string](paramCount()) var x = 0 while x < paramCount(): nargv[x] = paramStr(x+1) # first is program name x += 1 var argv: cStringArray = nargv.allocCStringArray() var argc: cint = cint paramCount() # Call gboolean gtk_init_check (int* argc, char*** argv); discard gtk_init_check(addr argc, argv) echo " [TRACE] Gtk started. Ok." #---------------------------------------------------# # Create GTK GUI # #---------------------------------------------------# echo " [TRACE] Shared library library loaded Ok." # Encapsulates gtk_init_check(int &argc, char **argv) # gtk_init_check must always be called before calling any other GTK # function, otherwise a runtime error will happen. gtk_start() let window: GtkWidget = gtk_window_new( GTK_WINDOW_TOPLEVEL ) assert window != nil # Alternative ways to write the same code in the next non-comment line: # =>> gtk_widget_set_size( window, 400, 500) # =>> gtk_widget_set_size window, 400, 500 # =>> window.gtk_widget_set_size( 400, 500 ) # =>> window.gtk_widget_set_size 400, 500 window.gtk_widget_set_size 400, 500 window.gtk_window_set_title "My GTK Windows called from Nim" let button = gtk_button_new_with_label("Click me now!") assert button != nil, "Failed to create button" button.gtk_widget_set_size_request 70, 30 gtk_container_add(window, button) # gtk_widget_show( window ) ->> UFS - Universal Function Call # window.gtk_widget_show() gtk_widget_show_all(window) # Discard annotation discards the function return value discard g_signal_connect_data( window, "destroy", gtk_app_quit, nil, nil, cint 0) var counter = 0 # Set button callback using a lambda syntax sugar # for better code asthetics and readability button.on_clicked do: counter = counter + 1 let message = " [INFO] Button was clicked " & $counter & " times. " echo message msgbox_info(nil, "Notification", message) # Run GTK event loop and block current thread gtk_main() echo " [TRACE] GTK application shutdown OK."
Screenshot
Building and Running
Building:
$ nim c gtk_dlopen.nim
Running loading Gtk 3:
$ ./gtk_dlopen ======== Program started Ok ============== [TRACE] Shared library libgtk-3.so.0 loaded Ok. [TRACE] Loading symbol = gtk_init_check [TRACE] Loading symbol = gtk_window_new [TRACE] Loading symbol = gtk_widget_show [TRACE] Loading symbol = gtk_main [TRACE] Loading symbol = gtk_window_set_title ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... [TRACE] Loading symbol = gtk_message_dialog_new [TRACE] Loading symbol = gtk_widget_destroy [TRACE] Running Gtk - 3.X [TRACE] Shared library library loaded Ok. [TRACE] Gtk started. Ok. I was clicked [INFO] Button was clicked 1 times. [INFO] Button was clicked 2 times. [INFO] Button was clicked 3 times. [INFO] Button was clicked 4 times. [INFO] Button was clicked 5 times. [INFO] Button was clicked 6 times. [TRACE] Quit GTK - Shutdown GTK event loop and unblocks current thread. [TRACE] GTK application shutdown OK.
Running loading Gtk 2:
$ >> env GTK_LIB="libgtk-x11-2.0.so.0" ./gtk_dlopen ======== Program started Ok ============== [TRACE] Shared library libgtk-x11-2.0.so.0 loaded Ok. [TRACE] Loading symbol = gtk_init_check [TRACE] Loading symbol = gtk_window_new [TRACE] Loading symbol = gtk_widget_show [TRACE] Loading symbol = gtk_main [TRACE] Loading symbol = gtk_window_set_title [TRACE] Loading symbol = gtk_widget_set_size_request .... .... .... .... .... .... .... .... .... .... .... .... .... .... [TRACE] Running Gtk - 2.X [TRACE] Shared library library loaded Ok. [TRACE] Gtk started. Ok. I was clicked [INFO] Button was clicked 1 times. [INFO] Button was clicked 2 times. .... .... .... .... .... .... .... .... .... .... .... .... .... .... [INFO] Button was clicked 15 times. [TRACE] Quit GTK - Shutdown GTK event loop and unblocks current thread. [TRACE] GTK application shutdown OK.
1.19 Library calls hooking/interception with LD_PRELOAD
1.19.1 Overview
The dynamic linker of many Unix-like operating systems, including Linux-based ones and BSD variants, provide a LD_PRELOAD mechanism, which allows intercepting and overriding (aka hooking) library calls performed by applications withou any source code modification. A library call can be overriden by setting the LD_PRELOAD environment variable with a colon (':') separated list containing full paths of shared libraries containing symbols with the same name as the overriden functions.
Some use-cases of this feature are:
- Override library calls and modify the behavior of library calls.
- Performance analysis.
- Instrument library calls.
- Log library-calls
- Trace memory allocation by overrading calloc, malloc and free functions.
- Patch executables without any source code modification or recompilation.
Notes and limitations:
- The dynamic linker LD_PRELOAD feature do not intercept kernel's system-calls, it can only intercept library calls (aka function calls) or GlibC (GNU C library) or any other CRT (C - Runtime Library) wrappers for system calls, informally called syscalls.
- LD_PRELOAD trick cannot intercept functions of executables owned by root and with SETUID bit set.
1.19.2 Intercepting open() and fopen() library calls
All sources available at GIST:
File: build.sh (Build script)
#!/usr/bin/env sh # Build unix/Linux executable gcc unix-executable.c -o unix-executable.bin # Build Linux shared library: hook-files-a.so gcc hook-files-a.c -o hook-files-a.so -fpic -shared -ldl -Wall # Build Linux shared library: hook-files-b.so g++ hook-files-b.cpp -o hook-files-b.so -fpic -Wall -shared -ldl
File: hook-files-a.c / C-version (Shared library, aka shared object, hook-files-a.so)
#define _GNU_SOURCE // Enables RTLD_NEXT constant. #include <stdio.h> #include <assert.h> #include <stdint.h> // ----- Unix-specific headers ------- // #include <dlfcn.h> // Annotation for internal symbols not visible outside // this compilation unit (static storage class). #define HIDDEN static // Anotation for calling function whenever the shared library // is loaded by some process. Note: this is a specific GNU // language extension. #define ON_LIBRARY_LOADED __attribute__((constructor)) typedef uint32_t pid_t; typedef uint32_t mode_t; extern pid_t getpid(void); __attribute__((constructor)) static void init_library1() { fprintf(stderr, " [INFO] init_library1() / Library loaded Ok. \n"); } HIDDEN ON_LIBRARY_LOADED void init_library2() { fprintf(stderr, " [INFO] init_library2() / Library loaded Ok. \n"); } /** Intercepts (aka hooks) and overrides open() function * from libC (GLIBC) that encapsulates open() system-call * (aka syscall). *---------------------------------------------------------*/ int open(const char* pathname, int flags, mode_t mode) { // Type alias for function pointer with same // signature as the open() function. typedef int (* open_t) ( const char* pathname , int flags , mode_t mode); // Load old symbol of original C function that was overriden open_t open_real = (open_t) dlsym(RTLD_NEXT, "open"); // Sanity checking => calls abort() if symbol is not found. assert( open_real ); // Log intercepted functional call // pid =>> Process ID of process that loaded this // shared library. fprintf( stderr , " [TRACE] pid = %d / open() ->> File = %s \n" , getpid(), pathname); // Pass control back to intercepted (hooked) function. return open_real(pathname, flags, mode); } FILE* fopen64(const char* filename, const char* type) { typedef FILE* (* fopen64_t) ( const char* filename , const char* type); fopen64_t old_fun = (fopen64_t) dlsym(RTLD_NEXT, "fopen64"); assert( old_fun != NULL); fprintf( stderr , " [TRACE] pid = %d / fopen64() ->> File = %s \n" , getpid(), filename); return old_fun(filename, type); }
File: hook-files-b.cpp / C++-version (Shared library hook-files-b.so )
#include <iostream> #include <cassert> // Equivalent to C-header <stdint.h> std::uint32_t ... and so on. #include <cstdint> // ----- Unix-specific headers ------- // #include <dlfcn.h> // Annotqtion for defining functions with C-linkage // that must conform to C ABI and calling conventions. #define EXTERN_C extern "C" // Annotation for internal symbols not visible outside // this compilation unit (static storage class). #define HIDDEN static // Anotation for calling function whenever the shared library // is loaded by some process. Note: this is a specific GNU // language extension. #define ON_LIBRARY_LOADED __attribute__((constructor)) // This symbol will be resolved at linking-time by the Linker extern "C" pid_t getpid(void); __attribute__((constructor)) static void init_library1() { fprintf( stderr," [INFO] init_library1() / Library loaded Ok. \n"); } HIDDEN ON_LIBRARY_LOADED void init_library2() { fprintf( stderr, " [INFO] init_library2() / Library loaded Ok. \n"); } /** Intercepts (aka hooks) and overrides open() function * from libC (GLIBC) that encapsulates open() system-call * (aka syscall). *---------------------------------------------------------*/ EXTERN_C int open(const char* pathname, int flags, mode_t mode) { // Type alias for function pointer with same // signature as the open() function. using open_t = int (*) ( const char* pathname , int flags , mode_t mode); // Load old symbol of original C function that was overriden open_t open_real = (open_t) dlsym(RTLD_NEXT, "open"); // Sanity checking => calls abort() if symbol is not found. assert( open_real != nullptr); // Log intercepted functional call // pid =>> Process ID of process that loaded this // shared library. std::fprintf( stderr , " [TRACE] pid = %d / open() ->> File = %s \n" , getpid(), pathname); // Pass control back to intercepted (hooked) function. return open_real(pathname, flags, mode); } EXTERN_C FILE* fopen64(const char* filename, const char* type) { using fopen64_real = FILE* (*) ( const char* filename , const char* type); auto old_fun = (fopen64_real) dlsym(RTLD_NEXT, "fopen64"); assert( old_fun != nullptr); std::fprintf( stderr , " [TRACE] pid = %d / fopen64() ->> File = %s \n" , getpid(), filename); return old_fun(filename, type); }
Building executable and shared libraries (aka shared objects - 'unix terminology'):
$ >> git clone https://gist.github.com/751fad7bf7707949e824ed2c19a73752 api-hooking && cd api-hooking $ >> sh build.sh $ ls build.sh hook-files-a.c hook-files-b.cpp unix-executable.c # Check generated object-codes: #------------------------------------------------------------------# $ >> file hook-files-a.so hook-files-a.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV) ... ... $ >> file hook-files-b.so hook-files-b.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV) ... ... $ >> file unix-executable.bin unix-executable.bin: ELF 64-bit LSB executable, x86-64, ... ... ...
Running executable unix-executable.bin
$ >> ./unix-executable.bin unix-executable.bin: unix-executable.c:8: main: Assertion `argc == 2 && "Missing file argument"' failed. fish: “./unix-executable.bin” terminated by signal SIGABRT (Abort) $ >> ./unix-executable.bin /proc/devices [LINE] 1 = Character devices: [LINE] 2 = 1 mem [LINE] 3 = 4 /dev/vc/0 [LINE] 4 = 4 tty ... ... ... ... ... ... ... ... ... ... [LINE] 67 = 253 device-mapper [LINE] 68 = 254 mdp [LINE] 69 = 259 blkext
Intercepting library-call (ak function) fopen64() using LD_PRELOAD.
# Using hook-files-a.so (C version) #------------------------------------------------- $ >> env LD_PRELOAD=$PWD/hook-files-a.so ./unix-executable.bin /proc/devices [INFO] init_library1() / Library loaded Ok. [INFO] init_library2() / Library loaded Ok. [TRACE] pid = 231690 / fopen64() ->> File = /proc/devices [LINE] 1 = Character devices: [LINE] 2 = 1 mem [LINE] 3 = 4 /dev/vc/0 [LINE] 4 = 4 tty [LINE] 5 = 4 ttyS [LINE] 6 = 5 /dev/tty [LINE] 7 = 5 /dev/console ... ... ... ... ... ... ... ... ... # Using hook-files-b.so (C++ version) #------------------------------------------------- $ >> env LD_PRELOAD=$PWD/hook-files-b.so ./unix-executable.bin /proc/devices [INFO] init_library1() / Library loaded Ok. [INFO] init_library2() / Library loaded Ok. [TRACE] pid = 231790 / fopen64() ->> File = /proc/devices [LINE] 1 = Character devices: [LINE] 2 = 1 mem [LINE] 3 = 4 /dev/vc/0 [LINE] 4 = 4 tty [LINE] 5 = 4 ttyS [LINE] 6 = 5 /dev/tty [LINE] 7 = 5 /dev/console ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
Intercepting library-call (ak function) fopen64() using LD_PRELOAD, but discarding stdout (standard console output) by redirecting it to /dev/null in order to only display the stderr logged by the replacement functions.
# C version $ >> env LD_PRELOAD=$PWD/hook-files-a.so ./unix-executable.bin /proc/devices > /dev/null [INFO] init_library1() / Library loaded Ok. [INFO] init_library2() / Library loaded Ok. [TRACE] pid = 231957 / fopen64() ->> File = /proc/devices # C+++ version $ >> env LD_PRELOAD=$PWD/hook-files-b.so ./unix-executable.bin /proc/devices > /dev/null [INFO] init_library1() / Library loaded Ok. [INFO] init_library2() / Library loaded Ok. [TRACE] pid = 231994 / fopen64() ->> File = /proc/devices
Tracing library calls with ltrace utility:
$ >> ltrace ./unix-executable.bin /proc/devices fopen64("/proc/devices", "r") = 0x19722a0 getline(0x7fff02a9a640, 0x7fff02a9a638, 0x19722a0, 0x7fff02a9a638) = 19 fprintf(0x7f50e3769500, " [LINE] %d = %s ", 1, "Character devices:\n" [LINE] 1 = Character devices: ) = 32 getline(0x7fff02a9a640, 0x7fff02a9a638, 0x19722a0, 0x7fff02a9a638) = 8 fprintf(0x7f50e3769500, " [LINE] %d = %s ", 2, " 1 mem\n" [LINE] 2 = 1 mem ) = 21 getline(0x7fff02a9a640, 0x7fff02a9a638, 0x19722a0, 0x7fff02a9a638) = 14 fprintf(0x7f50e3769500, " [LINE] %d = %s ", 3, " 4 /dev/vc/0\n" [LINE] 3 = 4 /dev/vc/0 ) = 27 getline(0x7fff02a9a640, 0x7fff02a9a638, 0x19722a0, 0x7fff02a9a638) = 8 fprintf(0x7f50e3769500, " [LINE] %d = %s ", 4, " 4 tty\n" [LINE] 4 = 4 tty ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... getline(0x7fff02a9a640, 0x7fff02a9a638, 0x19722a0, 0x7fff02a9a638) = -1 free(0x1972480) = <void> fclose(0x19722a0) = 0 +++ exited (status 0) +++
Tracing system-calls calls with strace utility:
$ >> strace ./unix-executable.bin /proc/devices execve("./unix-executable.bin", ["./unix-executable.bin", "/proc/devices"], 0x7ffc6f5c0ba8 /* 83 vars */) = 0 ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... openat(AT_FDCWD, "/proc/devices", O_RDONLY) = 3 fstat(3, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0 read(3, "Character devices:\n 1 mem\n 4 /"..., 1024) = 694 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0xa), ...}) = 0 write(1, " [LINE] 1 = Character devices:\n", 31 [LINE] 1 = Character devices: ) = 31 write(1, " [LINE] 2 = 1 mem\n", 21 [LINE] 2 = 1 mem ) = 21 write(1, " [LINE] 3 = 4 /dev/vc/0\n", 27 [LINE] 3 = 4 /dev/vc/0 ) = 27 write(1, " [LINE] 4 = 4 tty\n", 21 [LINE] 4 = 4 tty ) = 21 ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
Determining files opened by uptime executable.
$ which uptime /usr/bin/uptime $ file $(which uptime) /usr/bin/uptime: ELF 64-bit LSB shared object, x86-64, ... ... # Indicates how long the machine is running: $ >> uptime 15:45:45 up 3 days, 19:39, 1 user, load average: 0.32, 0.59, 0.58 # Run 1 $ >> env LD_PRELOAD=$PWD/hook-files-b.so uptime [INFO] init_library1() / Library loaded Ok. [INFO] init_library2() / Library loaded Ok. [TRACE] pid = 232426 / open() ->> File = /proc/uptime [TRACE] pid = 232426 / open() ->> File = /proc/loadavg 15:46:39 up 3 days, 19:40, 1 user, load average: 0.55, 0.61, 0.59 # Run 2 - Discard stdout, preserving only stderr $ >> env LD_PRELOAD=$PWD/hook-files-b.so uptime > /dev/null [INFO] init_library1() / Library loaded Ok. [INFO] init_library2() / Library loaded Ok. [TRACE] pid = 232456 / open() ->> File = /proc/uptime [TRACE] pid = 232456 / open() ->> File = /proc/loadavg $ >> cat /proc/uptime 330114.72 1249234.16 $ >> cat /proc/loadavg 0.40 0.55 0.57 1/1838 232504
Determining files opened by free executable which indicates the amount of free RAM memory in megabytes in the current machine:
$ free -m total used free shared buff/cache available Mem: 15897 6603 2236 1768 7057 7378 Swap: 0 0 0 # Intercept library calls #-------------------------------------------------------------------- $ >> env LD_PRELOAD=$PWD/hook-files-b.so free -m [INFO] init_library1() / Library loaded Ok. [INFO] init_library2() / Library loaded Ok. [TRACE] pid = 232965 / open() ->> File = /proc/meminfo total used free shared buff/cache available Mem: 15897 6601 2238 1768 7058 7380 Swap: 0 0 0 # Intercept library calls and discard stdout # , keeping only stderr (standard error output) #-------------------------------------------------------------------- $ >> env LD_PRELOAD=$PWD/hook-files-b.so free -m > /dev/null [INFO] init_library1() / Library loaded Ok. [INFO] init_library2() / Library loaded Ok. [TRACE] pid = 233016 / open() ->> File = /proc/meminfo # Inspect file /proc/meminfo #-------------------------------------------------------------------- $ >> cat /proc/meminfo MemTotal: 16279408 kB MemFree: 2271428 kB MemAvailable: 7552036 kB Buffers: 1178620 kB ... ... ... ... ... ... ... ... ... ... ... ... HugePages_Surp: 0 Hugepagesize: 2048 kB Hugetlb: 0 kB DirectMap4k: 604380 kB DirectMap2M: 12916736 kB DirectMap1G: 4194304 kB
1.19.3 Intercepting dlopen() library calls
The subroutine dlopen() is used for loading shared libraries, plugins and native libraries, in the case of languages such as Python, Ruby, Java, TCL and so on. By intercepting library calls to dlopen(), it is possible check the shared libraries loaded by a given process.
File: dlopen_hook.cpp
#include <iostream> #include <cassert> // Equivalent to C-header <stdint.h> std::uint32_t ... and so on. #include <cstdint> // ----- Unix-specific headers ------- // #include <dlfcn.h> // Annotation for defining functions with C-linkage // that must conform to C ABI and calling conventions. #define EXTERN_C extern "C" // This symbol will be resolved at linking-time by the Linker extern "C" pid_t getpid(void); __attribute__((constructor)) static void init_library1() { std::fprintf(stderr, " [TRACE] Library loaded on process =>> pid = %d \n\n", getpid()); } /** Intercept dlopen() library call. * ********************************************/ #if 1 EXTERN_C void* dlopen(const char* filename, int flags) { // Function type signature - must have the same type signature of // the intercepted function. using fun_t = void* (*) (const char* filename, int flags); // Load old symbol of original C function that was overriden fun_t dlopen_real = (fun_t) dlsym(RTLD_NEXT, "dlopen"); std::fprintf( stderr, " [TRACE] dlopen() [PID = %d] - library = '%s' Ok \n" , getpid(), filename); return dlopen_real(filename, flags); } #endif
Build:
$ >> g++ dlopen_hook.cpp -o dlopen_hook.so -fpic -Wl,--soname=./dlopen_hook.so -shared -Wall -ldl # Identify binary format $ >> file dlopen_hook.so dlopen_hook.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked , BuildID[sha1]=e31a1a84b50c2a4e8c72f4de484f8e245be59b67, not stripped # View dependencies $ >> ldd dlopen_hook.so linux-vdso.so.1 (0x00007ffcb6da7000) libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f64bb201000) libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f64bb020000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f64bae2e000) /lib64/ld-linux-x86-64.so.2 (0x00007f64bb227000) libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f64bacdf000) libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f64bacc4000)
Experiment 1: View shared libraries loaded by a Python process (python rpl - read-print-eval-loop).
$ >> env LD_PRELOAD=$PWD/dlopen_hook.so python3 [TRACE] Library loaded on process =>> pid = 53633 Python 3.8.5 (default, Jul 28 2020, 12:59:40) [GCC 9.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. [TRACE] dlopen() [PID = 53633] - library = '/usr/lib/python3.8/lib-dynload/readline.cpython-38-x86_64-linux-gnu.so' Ok >>> >>> import os >>> >>> os.getpid() 53633 >>> import dbus [TRACE] dlopen() [PID = 53633] - library = '/usr/lib/python3/dist-packages/_dbus_bindings.cpython-38-x86_64-linux-gnu.so' Ok >>> >>> import numpy [TRACE] dlopen() [PID = 53633] - library = '/home/h03f8/.local/lib/python3.8/site-packages/numpy/core/_multiarray_umath.cpython-38-x86_64-linux-gnu.so' Ok [TRACE] dlopen() [PID = 53633] - library = '/home/h03f8/.local/lib/python3.8/site-packages/numpy/core/_multiarray_tests.cpython-38-x86_64-linux-gnu.so' Ok [TRACE] dlopen() [PID = 53633] - library = '/usr/lib/python3.8/lib-dynload/_ctypes.cpython-38-x86_64-linux-gnu.so' Ok [TRACE] dlopen() [PID = 53633] - library = '(null)' Ok [TRACE] dlopen() [PID = 53633] - library = '/home/h03f8/.local/lib/python3.8/site-packages/numpy/linalg/lapack_lite.cpython-38-x86_64-linux-gnu.so' Ok [TRACE] dlopen() [PID = 53633] - library = '/home/h03f8/.local/lib/python3.8/site-packages/numpy/linalg/_umath_linalg.cpython-38-x86_64-linux-gnu.so' Ok [TRACE] dlopen() [PID = 53633] - library = '/home/h03f8/.local/lib/python3.8/site-packages/numpy/fft/_pocketfft_internal.cpython-38-x86_64-linux-gnu.so' Ok [TRACE] dlopen() [PID = 53633] - library = '/home/h03f8/.local/lib/python3.8/site-packages/numpy/random/mtrand.cpython-38-x86_64-linux-gnu.so' Ok [TRACE] dlopen() [PID = 53633] - library = '/home/h03f8/.local/lib/python3.8/site-packages/numpy/random/bit_generator.cpython-38-x86_64-linux-gnu.so' Ok [TRACE] dlopen() [PID = 53633] - library = '/home/h03f8/.local/lib/python3.8/site-packages/numpy/random/_common.cpython-38-x86_64-linux-gnu.so' Ok [TRACE] dlopen() [PID = 53633] - library = '/home/h03f8/.local/lib/python3.8/site-packages/numpy/random/_bounded_integers.cpython-38-x86_64-linux-gnu.so' Ok [TRACE] dlopen() [PID = 53633] - library = '/home/h03f8/.local/lib/python3.8/site-packages/numpy/random/_mt19937.cpython-38-x86_64-linux-gnu.so' Ok [TRACE] dlopen() [PID = 53633] - library = '/home/h03f8/.local/lib/python3.8/site-packages/numpy/random/_philox.cpython-38-x86_64-linux-gnu.so' Ok [TRACE] dlopen() [PID = 53633] - library = '/home/h03f8/.local/lib/python3.8/site-packages/numpy/random/_pcg64.cpython-38-x86_64-linux-gnu.so' Ok [TRACE] dlopen() [PID = 53633] - library = '/home/h03f8/.local/lib/python3.8/site-packages/numpy/random/_sfc64.cpython-38-x86_64-linux-gnu.so' Ok [TRACE] dlopen() [PID = 53633] - library = '/home/h03f8/.local/lib/python3.8/site-packages/numpy/random/_generator.cpython-38-x86_64-linux-gnu.so' Ok [TRACE] dlopen() [PID = 53633] - library = '/usr/lib/python3.8/lib-dynload/_opcode.cpython-38-x86_64-linux-gnu.so' Ok
Experiment 2: View shared libraries loaded by a Ruby process (Ruby repl irb).
$ >> env LD_PRELOAD=$PWD/dlopen_hook.so irb [TRACE] Library loaded on process =>> pid = 53705 [TRACE] dlopen() [PID = 53705] - library = '/usr/lib/x86_64-linux-gnu/ruby/2.7.0/enc/encdb.so' Ok [TRACE] dlopen() [PID = 53705] - library = '/usr/lib/x86_64-linux-gnu/ruby/2.7.0/enc/trans/transdb.so' Ok [TRACE] dlopen() [PID = 53705] - library = '/usr/lib/x86_64-linux-gnu/ruby/2.7.0/monitor.so' Ok [TRACE] dlopen() [PID = 53705] - library = '/usr/lib/x86_64-linux-gnu/ruby/2.7.0/ripper.so' Ok [TRACE] dlopen() [PID = 53705] - library = '/usr/lib/x86_64-linux-gnu/ruby/2.7.0/readline.so' Ok [TRACE] dlopen() [PID = 53705] - library = '/usr/lib/x86_64-linux-gnu/ruby/2.7.0/io/console.so' Ok [TRACE] dlopen() [PID = 53705] - library = '/usr/lib/x86_64-linux-gnu/ruby/2.7.0/pathname.so' Ok [TRACE] dlopen() [PID = 53705] - library = '/usr/lib/x86_64-linux-gnu/ruby/2.7.0/etc.so' Ok irb(main):001:0>
Experiment 3: View shared libraries loaded by Kate editor.
$ >> env LD_PRELOAD=$PWD/dlopen_hook.so kate [TRACE] Library loaded on process =>> pid = 54041 [TRACE] dlopen() [PID = 54041] - library = '/usr/lib/x86_64-linux-gnu/qt5/plugins/platforms/libqxcb.so.avx2' Ok [TRACE] dlopen() [PID = 54041] - library = '/usr/lib/x86_64-linux-gnu/qt5/plugins/platforms/libqxcb.so' Ok [TRACE] dlopen() [PID = 54041] - library = 'libXcursor.so.1' Ok [TRACE] dlopen() [PID = 54041] - library = '/usr/lib/x86_64-linux-gnu/qt5/plugins/platformthemes/KDEPlasmaPlatformTheme.so.avx2' Ok [TRACE] dlopen() [PID = 54041] - library = '/usr/lib/x86_64-linux-gnu/qt5/plugins/platformthemes/KDEPlasmaPlatformTheme.so' Ok ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... [TRACE] dlopen() [PID = 54041] - library = 'libGLX_mesa.so.0' Ok [TRACE] dlopen() [PID = 54041] - library = 'libGLX_mesa.so.0' Ok [TRACE] dlopen() [PID = 54041] - library = '/usr/lib/x86_64-linux-gnu/dri/tls/iris_dri.so' Ok [TRACE] dlopen() [PID = 54041] - library = '/usr/lib/x86_64-linux-gnu/dri/iris_dri.so' Ok [TRACE] dlopen() [PID = 54041] - library = '/usr/lib/x86_64-linux-gnu/qt5/plugins/konsolepart.so.avx2' Ok [TRACE] dlopen() [PID = 54041] - library = '/usr/lib/x86_64-linux-gnu/qt5/plugins/konsolepart.so' Ok
1.20 Launching processes with exec and fork system-call wrappers
This code demonstrates the usage of exec and fork system-calls wrapper functions, namely, execvp() and fork() functions.
Relevant functions signature
Fork
- Linux Manpage: "fork() creates a new process by duplicating the calling process. The new process is referred to as the child process. The calling process is referred to as the parent process."
- Note: The function 'fork' is not the system call fork, but a C wrapper around this sytem call provided by the C runtime library. (GLIBC - GNU C library in the machine where this was tested.)
pid_t fork(void);
Execvp (wraps exec system call)
- The exec system calls is always called when a new process is launched/created. It replaces the process image of the current process with a new one from the launched executable.
- Note: In addition to native executables, on Unix-like operating systems, an executable can also be any scripting file starting with shebang as first line such as "#!/usr/bin/env sh"
int execvp(const char *file, char *const argv[]);
Waitpid => Waits for a state of change of a process.
- It can be used for waiting for process termination.
pid_t waitpid(pid_t pid, int *wstatus, int options);
Documentation
Further Reading
- Fork (system call) - Wikipedia
- Fork() System Calland ProcessesCS449 Spring 2016
- Chapter 0 - Operating system interfaces {PDF}
Sample Code
GIST:
File: unix-process.cpp
// ------ File: unix-process.cpp -----------------------------------------------------------// // Description: Shows hows to launch processes on Unix with Exec and Fork syscall wrappers. //-------------------------------------------------------------------------------------------// #include <iostream> #include <vector> // ----- Unix/Linux headers -----// #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/wait.h> #include <cstring> // Import: char* strerror(in errnum); // -------------- Declarations --------------------------// void print_errno_details(int err); void execvp_test(); void execvp_cpp(std::string app, std::vector<std::string> args); // Launch a new process without terminate this process. // It combines fork + exec system-calls. void fork_exec(std::string app, std::vector<std::string> args); // ------------- MAIN() ------------------------------// int main(int argc, char** argv) { std::puts(" [INFO] Program started. "); if(argc < 2){ std::printf(" Usage: ./unix-process <OPTION>"); return EXIT_SUCCESS; } std::string opt = argv[1]; if(opt == "0") { execvp_test(); } // Test execvp if(opt == "1") { execvp_cpp("thunar", { "/etc" } ); return EXIT_SUCCESS; } // Fork-exec if(opt == "2") { fork_exec("konsole", { "-e", "tmux", "a"} ); } std::puts(" [TRACE] Finish execution. "); return 0; } // ------------- Definitions ------------------------// void print_errno_details(int err) { std::fprintf(stderr ,"\n => errno(int) = %d" "\n => errno message = %s \n" , err, strerror(err)); std::fflush(stderr); } // Test exec system-call wrapper function (execvp) void execvp_test() { const char* app = "thunar"; const char* args[] = { app // Process name, can be anything , "/etc" // Arguments passed to the process , nullptr // Always terminate the argument array with null pointer }; // Encapsulates execv system call. // int execvp(const char *file, char *const argv[]); if( execvp(app, (char *const *) args) == -1) { std::fprintf(stderr, " Error: unable to launch process"); print_errno_details(errno); throw std::runtime_error("Error: failed to launch process"); } } // C++ wrapper for the exevp() library-call // It replaces the current process image with a new one // from other executable. void execvp_cpp( std::string app , std::vector<std::string> args) { std::vector<const char*> pargs; pargs.reserve(args.size() + 1); pargs.push_back(app.c_str()); for(auto const& a: args){ pargs.push_back(a.c_str()); } pargs.push_back(nullptr); // Signature: int execvp(const char *file, char *const argv[]); // execvp(app.c_str(), execvp(app.c_str(), (char* const *) pargs.data() ) int status = execvp(app.c_str(), (char* const*) pargs.data() ); if( status == -1) { std::fprintf(stderr, " Error: unable to launch process"); print_errno_details(errno); throw std::runtime_error("Error: failed to launch process"); } } void fork_exec(std::string app, std::vector<std::string> args) { std::printf(" [TRACE] <BEFORE FORK> PID of parent process = %d \n", getpid()); // PID of child process (copy of this process) pid_t pid = fork(); if(pid == -1) { std::fprintf(stderr, "Error: unable to launch process"); print_errno_details(errno); throw std::runtime_error("Error: unable to launch process"); } if(pid == 0){ std::printf(" [TRACE] Running on child process => PID_CHILD = %d \n", getpid()); // Close file descriptors, in order to disconnect the process from the terminal. // This procedure allows the process to be launched as a daemon (aka service). close(STDOUT_FILENO); close(STDERR_FILENO); close(STDIN_FILENO ); // Execvp system call, replace the image of this process with a new one // from an executable. execvp_cpp(app, args); return; } std::printf(" [TRACE] <AFTER FORK> PID of parent process = %d \n", getpid()); // pid_t waitpid(pid_t pid, int *wstatus, int options); int status; std::printf(" [TRACE] Waiting for child process to finish. "); // Wait for child process termination. // From header: #include <sys/wait.h> if(waitpid(pid, &status, 0) == -1) { print_errno_details(errno); throw std::runtime_error("Error: cannot wait for child process"); } std::printf(" [TRACE] Child process has been terminated Ok."); // -------- Parent process ----------------// }
Building:
$ >> g++ unix-process.cpp -o unix-process.bin -std=c++1z -Wall -Wextra -g
Running:
~/t/unix-explore $ >> ./unix-process.bin 0 [INFO] Program started. ~/t/unix-explore $ >> ./unix-process.bin 1 [INFO] Program started. ~/t/unix-explore $ >> ./unix-process.bin 2 [INFO] Program started. [TRACE] <BEFORE FORK> PID of parent process = 774929 [TRACE] <AFTER FORK> PID of parent process = 774929 [TRACE] Running on child process => PID_CHILD = 774930 [TRACE] Waiting for child process to finish. [TRACE] Child process has been terminated Ok. [TRACE] Finish execution.
1.21 Reading subprocess output via Popen()
The convenience function popen(), which is a combination of fork() + exec() and pipe() functions, allows reading a subprocess output. Note: this function is also available on Windows.
Header:
- <stdio.h> in C
- <cstdio> in C++
Documentation
Functions
Popen:
- This function executes: 'sh -c <COMMAND>' via, fork() + exec() and pipe(), returning a file stream. Note: sh is a unix-shell.
FILE* popen(const char* command, const char* mode);
Pclose
int pclose(FILE* stream);
Sample Application
File: popen-test.cpp
#include <iostream> #include <string> // Provides; popen(), pclose() #include <cstdio> int main(int argc, char** argv) { std::puts("\n === EXPERIMENT 1 - popen() using buffer ========\n"); { // Equivalent to: fork() + exec() + pipe() FILE* fd = popen("ls /", "r"); if(fd == nullptr){ fprintf(stderr, "Error: failed to run command. Check ERRNO variable. \n"); return EXIT_FAILURE; } constexpr size_t BUFFER_SIZE = 500; // Buffer initialized with null char char buffer[BUFFER_SIZE] = {0}; while( fgets(buffer, BUFFER_SIZE, fd) != nullptr ) { fprintf(stdout, "%s", buffer); } pclose(fd); } std::puts("\n === EXPERIMENT 2 - popen() reading line-by-line ========\n"); { FILE* fd = popen("ps -ef", "r"); char* pline = nullptr; size_t size = 0; ssize_t nread = 0; int count = 0; do { nread = getdelim(&pline, &size, '\n', fd); std::cout << " [" << count++ << "]" << " => " << pline; } while( nread != -1 && count < 8); pclose(fd); } return EXIT_SUCCESS; }
Building:
$ g++ popen-test.cpp -o popen-test -std=c++1z -Wall -Wextra -g
Running:
$ ./popen-test
=== EXPERIMENT 1 - popen() using buffer ========
bin
boot
dev
etc
home
. .... ... ... ...
... ... ... ... ...
usr
var
=== EXPERIMENT 2 - popen() reading line-by-line ========
[0] => UID PID PPID C STIME TTY TIME CMD
[1] => root 1 0 0 Jun08 ? 00:48:25 /usr/lib/systemd/systemd ... ... ...
[2] => root 2 0 0 Jun08 ? 00:00:00 [kthreadd]
[3] => root 3 2 0 Jun08 ? 00:00:00 [rcu_gp]
[4] => root 4 2 0 Jun08 ? 00:00:00 [rcu_par_gp]
[5] => root 6 2 0 Jun08 ? 00:00:00 [kworker/0:0H-events_highpri]
[6] => root 9 2 0 Jun08 ? 00:00:00 [mm_percpu_wq]
[7] => root 10 2 0 Jun08 ? 00:00:54 [ksoftirqd/0]
1.22 Unix Pipes - Inter Process Communication
Unix pipe is an inter-process communication facility which allows the output of a process to be the input of a another. It can be used for reading the output of a subprocess.
Documentation
- FreeBSD - pipe()
- Dup2 - system call
- Dup (system call)
- Duplicating Descriptors - GNU GlibC
- CSCI 4061: Input/Output with Files, Pipes
- http://unixwiz.net/techtips/remap-pipe-fds.html
- Inter Process Communication (IPC), pipes
- IPC - I/O Redirection & IPC, & Special Files: Unamed & Named Pipes, FIFOs
- Chapter 5 - Writing Your Own Shell
Main Functions
Function pipe()
- Encapsulates: pipe() system-call
- Linux Manpage: "pipe() creates a pipe, a unidirectional data channel that can be used for interprocess communication. The array pipefd is used to return two file descriptors referring to the ends of the pipe. pipefd[0] refers to the read end of the pipe. pipefd[1] refers to the write end of the pipe."
- pipefd[0] => File descriptor for reading the pipe.
- pipefd[1] => File descriptor for writing to the pipe.
#include <unistd.h> int pipe(int pipefd[2]);
Function dup2()
- Encapsulates: dup2() system-call
- Duplicate file descriptor, used for IO redirection.
- dup2( fd1, STDOUT_FILENO ) => Example: redirect current process' stdout to file descriptor fd1.
int dup2(int oldfd, int newfd);
Sample Code
This code spawns a subprocess using fork() + exec() and uses pipes to read the subprocess output which are stdout and stderr of the "ls -l -a /".
File: pipe-test.cpp
#include <iostream> #include <string> // ---- Unix headers --------// #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> int main(int argc, char** argv) { /** Application to be called or its absolute path */ const char* app = "ls"; // Arguments ('-l' '-a' '/') for ls -l -a / const char* args [] = { /* First argument - process name (can be anything) */ "ls" /* Arguments passed to application */ , "-l", "-a", "/" /* Sentinel value - always nullptr */ , nullptr }; constexpr size_t PIPE_READ = 0; constexpr size_t PIPE_WRITE = 1; int fd[2]; // ----- Create pipe IPC channel --------// if( pipe(fd) == -1){ std::fprintf(stderr, "Error: cannot create pipe \n"); return EXIT_FAILURE; } // ----------- Fork() ----------------// pid_t cpid = fork(); if(cpid == -1) { std::fprintf(stderr, "Error: unable to launch process \n"); return EXIT_FAILURE; } if(cpid == 0) { /** ------- BEGIN of child process ------ **/ std::printf(" [TRACE] Child process => PID_CHILD = %d \n", getpid()); // Connect child process' stdin to read-end of the pipe. dup2( fd[PIPE_READ], STDIN_FILENO ); // Redirect child process' stdout (STDOUT_FILENO) to the write-end of the pipe. dup2( fd[PIPE_WRITE], STDOUT_FILENO ); // Redirect child process' stderr to write-end of the pipe. dup2( fd[PIPE_WRITE], STDERR_FILENO ); // Run process - exec() syscall => the new process image // has the same PID as the child process. execvp(app, (char *const *) args); return 0; /** ------- END of child process ------ **/ } // ---- Continue parent process ---------// std::printf(" [TRACE] <AFTER FORK> PID of parent process = %d \n", getpid()); constexpr size_t BUFSIZE = 200; ssize_t nread = 0; char buffer[BUFSIZE]; // Close unnused end of the pipe. close(fd[PIPE_WRITE]); // Write bytes read from the pipe (write-end), written by child process // , to the stdout. do { nread = read( fd[PIPE_READ], &buffer, BUFSIZE); write(STDOUT_FILENO, buffer, nread); } while ( nread > 0); std::printf(" [TRACE] Waiting for child process termination. \n"); close(fd[PIPE_READ]); // Wait for child process termination ::wait(nullptr); return EXIT_SUCCESS; } // -- End of main() --- //
Building:
$ >> g++ pipe-test.cpp -o pipe-test.bin -std=c++1z -g -Wall -Wextra
Running:
$ >> ./pipe-test.bin [TRACE] <AFTER FORK> PID of parent process = 1352693 [TRACE] Child process => PID_CHILD = 1352694 total 72 dr-xr-xr-x. 18 root root 4096 May 17 21:36 . dr-xr-xr-x. 18 root root 4096 May 17 21:36 .. -rw-r--r-- 1 root root 0 May 17 21:36 .autorelabel lrwxrwxrwx. 1 root root 7 Jan 28 15:30 bin -> usr/bin dr-xr-xr-x. 6 root root 4096 May 17 15:17 boot drwxr-xr-x 21 root root 4200 Jun 28 19:22 dev ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... lrwxrwxrwx. 1 root root 8 Jan 28 15:30 sbin -> usr/sbin drwxr-xr-x. 2 root root 4096 May 12 14:50 srv dr-xr-xr-x 13 root root 0 Jun 8 11:58 sys drwxrwxrwt 31 root root 680 Jun 30 04:02 tmp drwxr-xr-x. 12 root root 4096 Apr 22 19:35 usr drwxr-xr-x. 22 root root 4096 Apr 22 19:39 var [TRACE] Waiting for child process termination.
1.23 Monitor file system changes with Inotify API
1.23.1 Overview
Inotify is an exclusive Linux kernel API for file monitoring, which allows watching file system changes events such as file copy, moving, creation, renaming and deletion without any wasteful and inefficient polling. BSD-variants, such as MacOSX and FreeBSD use the kqueue API.
Inotify API limitations:
- Cannot monitor network mounted file systems.
- Unable to watch /proc (PROCFS) virtual file systems (VSF)
- Cannot monitor sub-directories. It is necessary to add new Inotify watch instances for every sub-directory to be monitored.
Some use-cases of file system monitoring via Inotify API are:
- File system automation
- Building system automation
- Automatic recompilation whenever a dependency changes
- Reloading application if some configuration file changes.
- Reloading of game asset if some file change.
- Executing some action, command or process when a file system event happens.
Functions and Structures
Headers:
- <sys/inotify.h> - See manpage: ( Ubuntu Inotify Manpage )
Functions:
- inotify_init() => Returns a file descriptor (numerical value) for an inotify instance. On failure, returns -1 and sets the errno thread-local-storage global variable.
int inotify_init(void) int inotify_init1(int flags);
- inotify_watch_add() => Add new file system watch.
- Parameters:
- fd => File descriptor of inotify instance, created with inotify_init function.
- pathname => Path to directory or file what will be monitored.
- mask => bitwise mask of flags that selects the types of events that will be monitored.
- RETURN => Returns (-1) on failure and any other value when the operation is successful.
- Parameters:
int inotify_add_watch(int fd, const char* pathname, uint32_t mask);
- inotify_rm_watch() => Removes file system watch.
- Parameters:
- fd => Inotify file descritor created with inotify_init() function.
- wd => Watch file descriptor created with inotify_watch_add.
- Parameters:
int inotify_rm_watch(int fd, int wd);
Structure:
struct inotify_event { int wd; /* Watch (watcher file descriptor) descriptor */ uint32_t mask; /* Mask of events */ uint32_t cookie; /* Unique cookie associating related events (for rename(2)) */ uint32_t len; /* Size of name field => File or directory name. */ char name[]; /* Optional null-terminated name */ };
References
- inotify(7) - Linux man page
- Ubuntu Manpage - inotify - monitoring filesystem events
- inotify - a powerful yet simple file change notification system
- Inotify: Efficient, Real-Time Linux File System Event Monitoring
- Filesystem notification, part 1: An overview of dnotify and inotify
- What is the proper way to use inotify?
1.23.2 Sample code
All files available at:
Files
File: inotify-test.cpp
#include <iostream> #include <string> #include <cstring> // strerror() #include <memory> #include <cassert> #include <map> // ---- Unix-specific headers ----------// #include <unistd.h> #include <limits.h> // ---- Linux-specific headers --------// #include <sys/inotify.h> constexpr int FAILURE = -1; void exit_on_failure(int fd, const char* error_message) { if(fd != FAILURE){ return; } int err = errno; std::fprintf( stderr, " [ERROR] %s ; errno = %d ; errno-msg = %s \n" , error_message, err, strerror(err)); // Execute exit system call and terminate this process. exit(1); } int main(int argc, char** argv) { // Create inotify instance, returning file descriptor int fd_inotify = ::inotify_init(); exit_on_failure(fd_inotify, "Cannot create Inotify instance"); using WatcheFileDescriptor = int; using DirectoryPath = std::string; auto watch_table = std::map<WatcheFileDescriptor, DirectoryPath>{}; if(argc < 2){ std::fprintf(stderr, " [ERROR] Requires one or more directories to be watched. \n"); return EXIT_FAILURE; } for(int i = 1; i < argc; i++) { const char* directory = argv[i]; // Create watch instance, returning a file descriptor for the watcher int fd_watch = inotify_add_watch(fd_inotify, directory, IN_ALL_EVENTS); // Error checking std::string error_msg = std::string("Cannot create watcher for this directory: ") + directory; exit_on_failure(fd_watch, error_msg.c_str()); watch_table[fd_watch] = directory; std::fprintf( stdout, " [INFO] Watching directory => wd = %d ; directory = %s \n" , fd_watch, directory); } // Maximum number of events constexpr size_t MAX_EVENTS = 1024; // Size of data structure inotify_event in bytes constexpr size_t EVENT_SIZE = sizeof(struct inotify_event); // Lenght of buffer for events storage in bytes constexpr size_t BUFFER_LENGTH = MAX_EVENTS * ( EVENT_SIZE + NAME_MAX + 1 ); std::uint8_t buffer[BUFFER_LENGTH]; // -------------- Inotify event loop ----------------// for(;;) { // std::fprintf(stderr, " [INFO] Waiting for Inotify event \n"); // nn_read => signed number of bytes read from the file descriptor // It is (-1) in the case of errrors. ssize_t nn_read = read(fd_inotify, buffer, BUFFER_LENGTH); assert(nn_read != -1 && "Placeholder for future error handling."); ssize_t offset = 0; // Iterate over all events while( offset < nn_read ) { auto event = reinterpret_cast<inotify_event*>(buffer + offset); auto directory = watch_table[event->wd]; // Create file if(event->mask & IN_CREATE && !(event->mask & IN_ISDIR) ) std::fprintf(stdout , " [INFO] CREATE file event => (file = %s ) ; (dir = %s) \n" , event->name, directory.c_str()); // Create directory event if(event->mask & IN_CREATE && event->mask & IN_ISDIR ) std::fprintf( stdout , " [INFO] CREATE directory event => (file = %s ) ; (dir = %s) \n" , event->name, directory.c_str()); // Delete file or directory event. if(event->mask & IN_DELETE) std::fprintf( stdout, " [INFO] DELETE file event => (file = %s ) ; (dir = %s) \n" , event->name, directory.c_str()); // Modify file if( event->mask & IN_MODIFY && !(event->mask & IN_ISDIR) ) std::fprintf( stdout , " [INFO] MODIFY file event => (file = %s ) ; (dir = %s) \n" , event->name, directory.c_str()); // Event happens when file system is unmounted. if(event->mask & IN_IGNORED) std::fprintf( stdout , " [INFO] IGNRED file monitor removed or file system unmounted => dir = %s \n" , directory.c_str()); offset = offset + EVENT_SIZE + event->len; } } // --------- End of Inotify event loop() --------------// //free(event); std::fprintf(stdout, " [INFO] Terminate application gracefully \n\n"); close(fd_inotify); return 0; }
Building:
$ git clone https://gist.github.com/6e3fc8a39a3a6089ad7002738e4d509a inotify-gist && cd inotify-gist $ g++ inotify-test.cpp -o inotify-test.bin -std=c++1z -Wall -Wextra -g $ file inotify-test.bin inotify-test.bin: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), .... ...
Running:
Run the following command from terminal emulator 1:
$ ./inotify-test.bin /tmp/testing ~/Downloads [INFO] Watching directory => wd = 1 ; directory = /tmp/testing [INFO] Watching directory => wd = 2 ; directory = /home/unixuser/Downloads [INFO] CREATE file event => (file = file1.txt ) ; (dir = /tmp/testing) [INFO] CREATE file event => (file = file2.txt ) ; (dir = /tmp/testing) [INFO] CREATE file event => (file = file3.txt ) ; (dir = /tmp/testing) [INFO] CREATE file event => (file = file4.txt ) ; (dir = /tmp/testing) [INFO] DELETE file event => (file = file1.txt ) ; (dir = /tmp/testing) [INFO] CREATE file event => (file = testing-files1.txt ) ; (dir = /home/unixuser/Downloads) [INFO] CREATE file event => (file = testing-files2.txt ) ; (dir = /home/unixuser/Downloads) [INFO] CREATE file event => (file = testing-files3.txt ) ; (dir = /home/unixuser/Downloads) [INFO] CREATE file event => (file = testing-files4.txt ) ; (dir = /home/unixuser/Downloads) [INFO] DELETE file event => (file = file2.txt ) ; (dir = /tmp/testing) [INFO] DELETE file event => (file = file3.txt ) ; (dir = /tmp/testing) [INFO] DELETE file event => (file = file4.txt ) ; (dir = /tmp/testing) [INFO] MODIFY file event => (file = testing-files1.txt ) ; (dir = /home/unixuser/Downloads) [INFO] MODIFY file event => (file = testing-files2.txt ) ; (dir = /home/unixuser/Downloads) [INFO] MODIFY file event => (file = testing-files2.txt ) ; (dir = /home/unixuser/Downloads)
Manipulate the watched directories from terminal emulator 2:
$ mkdir -p /tmp/testing & cd /tmp/testing $ >> touch file1.txt file2.txt file3.txt file4.txt $ >> rm -rf file1.txt $ >> touch ~/Downloads/testing-files{1, 2, 3, 4}.txt $ >> rm -rf -v *.txt removed 'file2.txt' removed 'file3.txt' removed 'file4.txt' $ >> echo "change file" >> ~/Downloads/testing-files1.txt $ >> echo "change file" >> ~/Downloads/testing-files2.txt $ >> echo "change file" >> ~/Downloads/testing-files2.txt
1.24 mmap - Memory Mapping
1.24.1 Overview
File mapping is an operating system mechanism which maps a disk file to a process virtual memory. This operating system feature allows accessing the file as it was in the process memory.
Benefits:
- Transparent access to file, allows accessing the file as it was an ordinary memory. (the file is accessible by pointer)
- Good for processing large files which does not fit in the machine RAM memory.
Use-cases:
- Read large files
- IPC - Inter-Process Communication
- Process complicated binary files
- Modify complex binary files. The file can be modified by just changing a memory, in other words, modifying the contents of a memory address.
- Implement JIT - Just-In-Time compiler and execute assembly code at runtime.
The mmap API is available in most Unix-like operating systems:
- Linux
- BSD-family: MacOSX, FreeBDS, NetBSD, OpenBSD
- Solaris
- QNX, AIX
Mmap API functions
File descriptors:
#include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> // Returns file descriptor of a disk-file int open(const char *pathname, int flags); // Close file descriptor (release resource) int close(int fd);
Mmap:
- Doc: $ man mmap
- Linux Manpage Description: "mmap() creates a new mapping in the virtual address space of the calling process. The starting address for the new mapping is specified in addr. The length argument speci‐ fies the length of the mapping (which must be greater than 0)"
- Argument: prot (Memory Protection)
- PROT_READ => Pages may be written (most used)
- PROT_WRITE => Pages may be written (allows changing the file by writing to the mapped memory)
- PROT_NONE => Pages may not be accessed
- PROT_EXEC => Pages may be executed => Allows executing machine code (assembly) at runtime. Use case: JIT - Just-In-Time compiler
- Argument: flags
- MAP_SHARED => Multiple processes can share the same file mapping. This flag is used for IPC - Inter Process Communication.
- MAP_PRIVATE => Only the current process can access the file mapping.
#include <sys/mman.h> void* mmap( void* addr // (often nullptr) Address that the file will be mapped , size_t length // Length of file mapping (often the file size) , int prot // Flags for memory protection , int flags // Flags for process access conttrol (private | shared) , int fd // File descriptor to be mapped into virtual memory , off_t offset // (often zero) Offset from the beginning of the file );
munmap:
- Doc: $ man munmap
- Linux Manpage doc: "The munmap() system call deletes the mappings for the specified address range, and causes further references to addresses within the range to generate invalid memory ref‐ erences. The region is also automatically unmapped when the process is terminated. On the other hand, closing the file descriptor does not unmap the region."
- Param addr: Address of memory mapping (value returned by mmap)
- Param length: length of file mapping, often it is the file size.
// For mmap and munmap #include <sys/mman.h> int munmap( void *addr, size_t length);
Hyperlinks to manpage documentation of related-functions:
- mmap (Linux manpage)
- remap_file_pages
- mremap (Linux manpage)
- msync
References and further reading
General:
- mmap - Wikipedia
- Apple - Mapping Files Into Memory
- Programmation SystèmeCours 5 — Memory Mapping (Web Archive)
- How does memory mapping a file have significant performance increases over the standard I/O system calls?
- Interprocess Communication: Memory mapped files and pipes
- Linux Memory Mapped System Call Performance
- Shared Memory with Memory-Mapped Files
- Use mmap With Care - Sublime HQ
- Writing a Memory Allocator for Fast Serialization
- Paper: Memory Mapped Files And Shared Memory For C++
- Overview of Serialization Technologies - CERN
- Efficient Memory Mapped File I/O for In-Memory File Systems
- Getting storage engines ready for fast storage devices
- Introduction to Memory Mapped Files (.NET Specific)
MMAP - File Memory Mapping for other programming languages
- https://docs.rs/flatdata/0.5.0/flatdata/ (Rust library for mmap)
- Sharing is Caring: Using Memory Mapped Files in .NET
- Inter-Process Communication with Memory-Mapped Files, Part 01
- Python SharedBuffer - library for mmap
- https://docs.julialang.org/en/v1/stdlib/Mmap/ - Julia Language library for mmap (memory-mapped files)
- Memory Mapping with util-mmap - mmap for Java.
1.24.2 Example (C) - mmap for reading Windows PE32 files
This code uses mmap API (memory mapped files) for reading metadata from PE (Portable Executable) - Windows native executable files or windows object-code.
GIST:
File: unix-mmap.c
#include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <assert.h> // --- Unix/Posix headers ---------// #include <unistd.h> #include <sys/mman.h> #include <sys/stat.h> #include <fcntl.h> #define IMAGE_SIZEOF_SHORT_NAME 8 #define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16 typedef int32_t LONG; typedef uint16_t WORD; typedef uint32_t DWORD; typedef uint8_t BYTE; typedef uint64_t ULONGLONG;; // Source: <winttt.h> => Original: typedef struct _IMAGE_DOS_HEADER typedef struct { WORD e_magic; WORD e_cblp; WORD e_cp; WORD e_crlc; WORD e_cparhdr; WORD e_minalloc; WORD e_maxalloc; WORD e_ss; WORD e_sp; WORD e_csum; WORD e_ip; WORD e_cs; WORD e_lfarlc; WORD e_ovno; WORD e_res[4]; WORD e_oemid; WORD e_oeminfo; WORD e_res2[10]; // Offset to the PE header from the beginning of the file. LONG e_lfanew; } IMAGE_DOS_HEADER; typedef struct { DWORD Signature; WORD Machine; WORD NumberOfSections; DWORD TimeDateStamp; DWORD PointerToSymbolTable; DWORD NumberOfSymbols; WORD SizeOfOptionalHeader; WORD Characteristics; } PE_HEADER; ssize_t get_file_size(int fd) { struct stat file_stat; if( fstat(fd, &file_stat) == -1 ) return -1; return file_stat.st_size; } int main(int argc, char** argv) { // ----------- Validate arguments ----------------------------// if(argc < 2) { fprintf(stderr, "Usage: /unix-mmap <FILE> \n"); return EXIT_FAILURE; } // ----------- Get file descriptor ----------------------------// // Get read-only file descriptor of file int fd = open(argv[1], O_RDONLY); if(fd == -1){ fprintf(stderr, " Error: unable to open file. check ERRNO variable \n"); return EXIT_FAILURE; } ssize_t size = get_file_size(fd); if(size == -1){ fprintf(stderr, " Error: unable to get file size \n"); return EXIT_FAILURE; } // ----------- Map file in to process' virtual memory ---------------------------// void* fmap = mmap( NULL /* Often set to zero, aka nullpointer */ , size /* Size of file mapping in bytes */ , PROT_READ /* Open in read-only mode */ , MAP_PRIVATE /* Only this process can access this memory-map */ , fd /* File descriptor of file to be memory mapped */ , 0x00 /* Offset from the beggining of the file */ ); if( fmap == MAP_FAILED) { fprintf(stderr, " Error: memory mapped failed. check ERRNO \n"); return EXIT_FAILURE; } // --------- Process file -----------------------------------------// unsigned char* bmap = (unsigned char*) fmap; // char file_signature [2] = { bmap[0], bmap[1], bmap[2], bmap[3], 0x00 }; printf(" File signature = %c %c 0x%X 0x%X \n", bmap[0], bmap[1], bmap[2], bmap[3]); IMAGE_DOS_HEADER* dos_header = (IMAGE_DOS_HEADER*) fmap; PE_HEADER* pe_header = fmap + dos_header->e_lfanew; assert( pe_header->Signature == 0x4550 ); printf("\n ========= [DOS HEADER] =================\n"); printf(" MAGIC NUMBER = 0x%X (hex) \n", dos_header->e_magic); printf(" MAGIC NUMBER = %c %c (str) \n", dos_header->e_magic & 0xFF, (dos_header->e_magic >> 8) & 0xFF); printf(" e_lfanew = 0x%X \n", dos_header->e_lfanew); printf("\n ======== [PE - Header] ================\n"); printf(" Note: if machine is 0x8664 => the processor is AMD-Intel x86-64 \n"); printf("\n"); printf(" Signature = 0x%X \n", pe_header->Signature); printf(" Machine = 0x%X \n", pe_header->Machine); printf(" Number of sections = %d \n", pe_header->NumberOfSections); // ---------- Release Resource ------------------------------------// // Unmap memory segment munmap(fmap, size); // Release file-descriptor resource close(fd); return 0; } // --- End of main() ----//
Build:
$ gcc unix-mmap.c -o unix-mmap-c.bin -Wall -Wextra -g
Running:
$ ./unix-mmap-c.bin notepad.exe File signature = M Z 0x90 0x0 ========= [DOS HEADER] ================= MAGIC NUMBER = 0x5A4D (hex) MAGIC NUMBER = M Z (str) e_lfanew = 0xF8 ======== [PE - Header] ================ Note: if machine is 0x8664 => the processor is AMD-Intel x86-64 Signature = 0x4550 Machine = 0x8664 Number of sections = 7
1.24.3 Example (C++) - mmap for reading Windows PE32 files
GIST:
File: unix-mmap.cpp
#include <iostream> #include <string> #include <fstream> #include <cstdint> #include <cassert> // --- Unix/Posix headers ---------// #include <unistd.h> #include <sys/mman.h> #include <sys/stat.h> #include <fcntl.h> constexpr size_t IMAGE_SIZEOF_SHORT_NAME = 8; constexpr size_t IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16; // using LONG = long; using LONG = std::int32_t; using WORD = std::uint16_t; // unsigned short; using DWORD = std::uint32_t; // unsigned long; using BYTE = std::uint8_t; //unsigned char; using ULONGLONG = std::uint64_t; // unsigned long long // Source: <winttt.h> => Original: typedef struct _IMAGE_DOS_HEADER struct IMAGE_DOS_HEADER { WORD e_magic; WORD e_cblp; WORD e_cp; WORD e_crlc; WORD e_cparhdr; WORD e_minalloc; WORD e_maxalloc; WORD e_ss; WORD e_sp; WORD e_csum; WORD e_ip; WORD e_cs; WORD e_lfarlc; WORD e_ovno; WORD e_res[4]; WORD e_oemid; WORD e_oeminfo; WORD e_res2[10]; // Offset to the PE header from the beginning of the file. LONG e_lfanew; }; struct PE_HEADER { DWORD Signature; WORD Machine; WORD NumberOfSections; DWORD TimeDateStamp; DWORD PointerToSymbolTable; DWORD NumberOfSymbols; WORD SizeOfOptionalHeader; WORD Characteristics; }; ssize_t get_file_size(int fd) { struct stat file_stat; if( fstat(fd, &file_stat) == -1 ) throw std::runtime_error("Error: unable to get file size"); return file_stat.st_size; } int main(int argc, char** argv) { // ----------- Validate arguments ----------------------------// if(argc < 2) { std::fprintf(stderr, "Usage: /unix-mmap <FILE> \n"); return EXIT_FAILURE; } // ----------- Get file descriptor ----------------------------// // Get read-only file descriptor of file int fd = open(argv[1], O_RDONLY); if(fd == -1){ std::fprintf(stderr, " Error: unable to open file. check ERRNO variable \n"); return EXIT_FAILURE; } ssize_t size = get_file_size(fd); // ----------- Map file in to process' virtual memory ---------------------------// void* fmap = mmap( nullptr /* Often set to zero, aka nullpointer */ , size /* Size of file mapping in bytes */ , PROT_READ /* Open in read-only mode */ , MAP_PRIVATE /* Only this process can access this memory-map */ , fd /* File descriptor of file to be memory mapped */ , 0x00 /* Offset from the beggining of the file */ ); if( fmap == MAP_FAILED) { std::fprintf(stderr, " Error: memory mapped failed. check ERRNO \n"); return EXIT_FAILURE; } // --------- Process file -----------------------------------------// const auto bmap = reinterpret_cast<unsigned char*>(fmap); // char file_signature [2] = { bmap[0], bmap[1], bmap[2], bmap[3], 0x00 }; std::printf(" File signature = %c %c 0x%X 0x%X \n", bmap[0], bmap[1], bmap[2], bmap[3]); IMAGE_DOS_HEADER* dos_header = reinterpret_cast<IMAGE_DOS_HEADER*>(fmap); PE_HEADER* pe_header = reinterpret_cast<PE_HEADER*>( (uintptr_t) fmap + dos_header->e_lfanew); assert( pe_header->Signature == 0x4550 ); std::printf("\n ========= [DOS HEADER] =================\n"); std::printf(" MAGIC NUMBER = 0x%X (hex) \n", dos_header->e_magic); std::printf(" MAGIC NUMBER = %c %c (str) \n", dos_header->e_magic & 0xFF, (dos_header->e_magic >> 8) & 0xFF); std::printf(" e_lfanew = 0x%X \n", dos_header->e_lfanew); std::printf("\n ======== [PE - Header] ================\n"); std::printf(" Note: if machine is 0x8664 => the processor is AMD-Intel x86-64 \n"); std::printf("\n"); std::printf(" Signature = 0x%X \n", pe_header->Signature); std::printf(" Machine = 0x%X \n", pe_header->Machine); std::printf(" Number of sections = %d \n", pe_header->NumberOfSections); // ---------- Release Resource ------------------------------------// // Unmap memory segment munmap(fmap, size); // Release file-descriptor resource close(fd); return 0; } // --- End of main() ----//
Building:
$ clang++ unix-mmap.cpp -o unix-mmap.bin -std=c++1z -Wall -Wextra -g
Running:
$ ./unix-mmap.bin ipconfig.exe File signature = M Z 0x90 0x0 ========= [DOS HEADER] ================= MAGIC NUMBER = 0x5A4D (hex) MAGIC NUMBER = M Z (str) e_lfanew = 0xE8 ======== [PE - Header] ================ Note: if machine is 0x8664 => the processor is AMD-Intel x86-64 Signature = 0x4550 Machine = 0x8664 Number of sections = 6 $ ./unix-mmap.bin notepad.exe File signature = M Z 0x90 0x0 ========= [DOS HEADER] ================= MAGIC NUMBER = 0x5A4D (hex) MAGIC NUMBER = M Z (str) e_lfanew = 0xF8 ======== [PE - Header] ================ Note: if machine is 0x8664 => the processor is AMD-Intel x86-64 Signature = 0x4550 Machine = 0x8664 Number of sections = 7
1.24.4 Example (C++) - mmap for reading Windows PE32 with class
This code uses a class FileMapping for encapsulating Unix memory-mapped files which simplifies the usage of the mmap feature and makes the code cleaner and safer.
GIST:
File: unix-mmap-class.cpp
#include <iostream> #include <string> #include <fstream> #include <cstdint> #include <cassert> // --- Unix/Posix headers ---------// #include <unistd.h> #include <sys/mman.h> #include <sys/stat.h> #include <fcntl.h> constexpr size_t IMAGE_SIZEOF_SHORT_NAME = 8; constexpr size_t IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16; // using LONG = long; using LONG = std::int32_t; using WORD = std::uint16_t; // unsigned short; using DWORD = std::uint32_t; // unsigned long; using BYTE = std::uint8_t; //unsigned char; using ULONGLONG = std::uint64_t; // unsigned long long // Source: <winttt.h> => Original: typedef struct _IMAGE_DOS_HEADER struct IMAGE_DOS_HEADER { WORD e_magic; WORD e_cblp; WORD e_cp; WORD e_crlc; WORD e_cparhdr; WORD e_minalloc; WORD e_maxalloc; WORD e_ss; WORD e_sp; WORD e_csum; WORD e_ip; WORD e_cs; WORD e_lfarlc; WORD e_ovno; WORD e_res[4]; WORD e_oemid; WORD e_oeminfo; WORD e_res2[10]; // Offset to the PE header from the beginning of the file. LONG e_lfanew; }; struct PE_HEADER { DWORD Signature; WORD Machine; WORD NumberOfSections; DWORD TimeDateStamp; DWORD PointerToSymbolTable; DWORD NumberOfSymbols; WORD SizeOfOptionalHeader; WORD Characteristics; }; /** Class for encapsulating memory mapped files*/ class FileMapping { int m_fd = -1; void* m_addr = nullptr; ssize_t m_size = -1; ssize_t get_file_size(int fd) { struct stat file_stat; if( fstat(fd, &file_stat) == -1 ) throw std::runtime_error("Error: unable to get file size"); return file_stat.st_size; } public: // Disable copy constructor FileMapping(FileMapping const&) = delete; // Disable copy assignment operator FileMapping& operator=(FileMapping const&) = delete; /** * @param file_path - File to be memory-mapped to current process. */ FileMapping(std::string file_path) { // Get read-only file descriptor of file m_fd = open(file_path.c_str(), O_RDONLY); if(m_fd == -1){ throw std::runtime_error("Unable to open file"); } m_size = get_file_size(m_fd); if(m_size == -1) throw std::runtime_error("Unable to get file size"); m_addr = ::mmap( nullptr , m_size , PROT_READ , MAP_PRIVATE , m_fd , 0x00 ); if( m_addr == MAP_FAILED) { throw std::runtime_error("Error: failed to map file to memory"); } } ~FileMapping() { // Unmap memory segment munmap(m_addr, m_size); // Release file-descriptor resource close(m_fd); m_fd = -1; m_addr = nullptr; m_size = -1; } /** @brief Returns pointer file mapping address. */ void* addr() const { return m_addr; } /** @brief Get casted pointer to an offset of the file mapping address. */ template<typename T> T addr_rel(std::ptrdiff_t offset) const { return reinterpret_cast<T>( reinterpret_cast<uintptr_t>(m_addr) + offset); } }; // --- End of class FileMapping ---- // int main(int argc, char** argv) { // ----------- Validate arguments ----------------------------// if(argc < 2) { std::fprintf(stderr, "Usage: /unix-mmap <FILE> \n"); return EXIT_FAILURE; } FileMapping fmap(argv[1]); // --------- Process file -----------------------------------------// const auto bmap = fmap.addr_rel<unsigned char*>(0x00); // char file_signature [2] = { bmap[0], bmap[1], bmap[2], bmap[3], 0x00 }; std::printf(" File signature = %c %c 0x%X 0x%X \n", bmap[0], bmap[1], bmap[2], bmap[3]); IMAGE_DOS_HEADER* dos_header = fmap.addr_rel<IMAGE_DOS_HEADER*>(0x00); PE_HEADER* pe_header = fmap.addr_rel<PE_HEADER*>( dos_header->e_lfanew); assert( pe_header->Signature == 0x4550 ); std::printf("\n ========= [DOS HEADER] =================\n"); std::printf(" MAGIC NUMBER = 0x%X (hex) \n", dos_header->e_magic); std::printf(" MAGIC NUMBER = %c %c (str) \n", dos_header->e_magic & 0xFF, (dos_header->e_magic >> 8) & 0xFF); std::printf(" e_lfanew = 0x%X \n", dos_header->e_lfanew); std::printf("\n ======== [PE - Header] ================\n"); std::printf(" Note: if machine is 0x8664 => the processor is AMD-Intel x86-64 \n"); std::printf("\n"); std::printf(" Signature = 0x%X \n", pe_header->Signature); std::printf(" Machine = 0x%X \n", pe_header->Machine); std::printf(" Number of sections = %d \n", pe_header->NumberOfSections); return 0; } // --- End of main() ----//
Building:
$ g++ unix-mmap-class.cpp -o unix-mmap-class.bin -std=c++1z -g -Wall -Wextra
Running:
$ ./unix-mmap-class.bin notepad.exe File signature = M Z 0x90 0x0 ========= [DOS HEADER] ================= MAGIC NUMBER = 0x5A4D (hex) MAGIC NUMBER = M Z (str) e_lfanew = 0xF8 ======== [PE - Header] ================ Note: if machine is 0x8664 => the processor is AMD-Intel x86-64 Signature = 0x4550 Machine = 0x8664 Number of sections = 7
1.24.5 Example - mmap - Running machine code at runtime
The mmap posix system call and the APIs mprotect can be used for allocating memory with executable permission which allows writing and executing machine code at runtime. This feature is widely used by JIT (Just-In-Time) compilers of interpreted languages such as Julia and Javascript Engine V8, from Chrome browser, for improving the runtime performance and reducing the interpretation overhead.
Additional notes related to: mmap, assembly and machine code.
- Note 1: The function mmap is also available on other Unix-like operating systems such as MacOSx, BSD-variants (FreeBSD, OpenBSD, NetBSD, DragonFly BSD) and so on.
- Note 2: The the assembly x64 ISA (x86-64 AMD or Intel) is the same on all operating system that supports this ISA (Instruction Set Architecture). However, an object-code (machine code) from one operating system will not be compatible with another different operating system due the difference between the object-code binary format (Linux uses ELF; MacOSX uses MachO; Windows uses PE32); system calls; calling conventions and system shared libraries.
- Note 3: On Windows NT kernels the equivalent functions are: VirtualAlloc (equivalent to mmap) and VirtualProtect (mprotect).
- Note 4: Most Unix system uses the System V calling convention for x86-64 bits while Windows NT 64 bits uses a different callng convention.
Sample Code
The following sample application mmap-loader.cpp loads machine code (compiled assembly) for two functions, at runtime. The machine codes were extract from compiled C application with objdump GNU binutils tool.
GIST (Sources files):
File: mmap-loader.cpp
// =======>>> Load and execute machine code at runtime with mmap <<<====== #include <iostream> #include <string> #include <fstream> #include <sstream> #include <cstring> #include <cstdlib> #include <vector> #include <sys/mman.h> using byte = uint8_t; /** Load machine code into process virtual memory */ void* load_machine_code(byte* buffer, std::size_t size); void* load_machine_code(std::vector<byte> const& data); std::vector<byte> hexstr_to_bytes(std::string const& hexcode); template<typename T> inline T load_mcode(std::vector<byte> const& data) { return reinterpret_cast<T>( load_machine_code(data) ); } int main(int argc, char** argv) { std::puts("\n ====== Load add_numbers from machine code ================"); // 'extern' means that the variable will be resolved by the Linker extern std::string add_numbers_str; // Type alias for function pointer using add_numbers_t = int (*) (int, int); auto add_numbers_code = hexstr_to_bytes(add_numbers_str); // Load machine code for function add_numbers() at runtime. auto add_numbers = load_mcode<add_numbers_t>(add_numbers_code); std::cout << " [RESULT] add_numbers(20, 100) = " << add_numbers(20, 100) << '\n'; std::cout << " [RESULT] add_numbers(206, 782) = " << add_numbers(206, 782) << '\n'; std::puts("\n ======== Load factorial() from machine code ============="); extern std::vector<byte> factorial_code; // Function pointer alias using factorial_t = int (*) (int); auto factorial_func = load_mcode<factorial_t>(factorial_code); std::cout << " [RESULT] factorial(5) = " << factorial_func(5) << '\n'; std::cout << " [RESULT] factorial(6) = " << factorial_func(6) << '\n'; std::cout << " [RESULT] factorial(7) = " << factorial_func(7) << '\n'; std::fprintf(stdout, " [TRACE] Process terminated gracefully Ok. \n"); return 0; } //===========================================================// // I M P L E M E N T A T I O N S // //===========================================================// void* load_machine_code(const byte* buffer, std::size_t size) { // Create anonymous mapping (not backed by file), a memory that will // be later marked as executable. void* pmap = ::mmap( nullptr , size , PROT_READ | PROT_WRITE , MAP_PRIVATE | MAP_ANONYMOUS , -1, 0 ); if(pmap == MAP_FAILED){ throw std::runtime_error("Failed create memory map"); } // Copy machine code to memory map std::memcpy( pmap, buffer, size); // Mark memory as executable, but not writable. if( mprotect(pmap, size, PROT_READ | PROT_EXEC ) == -1 ) throw std::runtime_error("Faile to change memory protection flags"); return pmap; } std::vector<byte> hexstr_to_bytes(std::string const& hexcode) { std::vector<byte> bytes; bytes.reserve(hexcode.size()); char* token = strtok((char*) hexcode.data(), "\\x"); while( token != nullptr ){ char byte = (char) strtol(token, nullptr, 16); // ss << byte; bytes.push_back(byte); token = strtok(nullptr, "\\x"); } return bytes; } void* load_machine_code(std::vector<byte> const& data) { return load_machine_code(&data[0], data.size()); } // int add_numners(int x, int y) //--------------------------------------------------- // 0000000000401152 <add_numbers>: // 401152: 55 push %rbp // 401153: 48 89 e5 mov %rsp,%rbp // 401156: 48 89 f8 mov %rdi,%rax // 401159: 48 01 f0 add %rsi,%rax // 40115c: 5d pop %rbp // 40115d: c3 retq // // Machine encoded as ASCII string std::string add_numbers_str = "\\x55\\x48\\x89\\xe5\\x48\\x89\\xf8\\x48\\x01\\xf0\\x5d\\xc3"; std::vector<byte> factorial_code = { 0x55 // push rbp , 0x48, 0x89, 0xe5 // mov rbp,rsp , 0x89, 0x7d, 0xec // mov DWORD PTR [rbp-0x14],edi , 0xc7, 0x45, 0xfc, 0x01, 0x00, 0x00, 0x00 // mov DWORD PTR [rbp-0x4],0x1 , 0xc7, 0x45, 0xf8, 0x01, 0x00, 0x00, 0x00 // mov DWORD PTR [rbp-0x8],0x1 , 0xeb, 0x0e // jmp 40115b <factorial+0x25> , 0x8b, 0x45, 0xfc // mov eax,DWORD PTR [rbp-0x4] , 0x0f, 0xaf, 0x45, 0xf8 // imul eax,DWORD PTR [rbp-0x8] , 0x89, 0x45, 0xfc // mov DWORD PTR [rbp-0x4],eax , 0x83, 0x45, 0xf8, 0x01 // add DWORD PTR [rbp-0x8],0x1 , 0x8b, 0x45, 0xf8 // mov eax,DWORD PTR [rbp-0x8] , 0x3b, 0x45, 0xec // cmp eax,DWORD PTR [rbp-0x14] , 0x7c, 0xea // jl 40114d <factorial+0x17> , 0x8b, 0x45, 0xfc // mov eax,DWORD PTR [rbp-0x4] , 0x5d // pop rbp , 0xc3 // ret };
Building and running on Linux
Building:
$ >> g++ mmap-loader.cpp -o mmap-loader.bin -std=c++1z -Wall -Wextra -g
Running:
$ >> ./mmap-loader.bin ====== Load add_numbers from machine code ================ [RESULT] add_numbers(20, 100) = 120 [RESULT] add_numbers(206, 782) = 988 ======== Load factorial() from machine code ============= [RESULT] factorial(5) = 24 [RESULT] factorial(6) = 120 [RESULT] factorial(7) = 720 [TRACE] Process terminated gracefully Ok.
Building Running on MacOSX
Building:
$ clang++ mmap-loader.cpp -o mmap-loader.bin -std=c++1z -Wall -Wextra -g $ file mmap-loader.bin mmap-loader.bin: Mach-O 64-bit executable x86_64 $ otool -L mmap-loader.bin mmap-loader.bin: /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 902.1.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1281.100.1)
Running:
- Note: It works on MacOSX because the Linux, MacOSX and other Unix-based sytems use the System V x86-64 calling convention and the machine code does not use any C library function.
$ ./mmap-loader.bin ====== Load add_numbers from machine code ================ [RESULT] add_numbers(20, 100) = 120 [RESULT] add_numbers(206, 782) = 988 ======== Load factorial() from machine code ============= [RESULT] factorial(5) = 24 [RESULT] factorial(6) = 120 [RESULT] factorial(7) = 720 [TRACE] Process terminated gracefully Ok.
Running the application in GDB debugger on Linux
Load application in debugger:
$ >> gdb ./mmap-loader.bin --silent Reading symbols from ./mmap-loader.bin...
Set breaking point:
# Set breaking point (gdb) b main Breakpoint 1 at 0x4023f6: file mmap-loader.cpp, line 30.
Start running:
(gdb) r Starting program: /home/user/asm-projects/asm/mmap-loader.bin Missing separate debuginfos, use: dnf debuginfo-install glibc-2.31-2.fc32.x86_64 Breakpoint 1, main (argc=1, argv=0x7fffffffd348) at mmap-loader.cpp:30 30 std::puts("\n ====== Load add_numbers from machine code ================"); Missing separate debuginfos, use: dnf debuginfo-install libgcc-10.2.1-1.fc32.x86_64 libstdc++-10.2.1-1.fc32.x86_64
Execute multiple steps:
(gdb) n ====== Load add_numbers from machine code ================ 37 auto add_numbers_code = hexstr_to_bytes(add_numbers_str); (gdb) 40 auto add_numbers = load_mcode<add_numbers_t>(add_numbers_code); (gdb) n 42 std::cout << " [RESULT] add_numbers(20, 100) = " << add_numbers(20, 100) << '\n'; (gdb) (gdb) p add_numbers_code $1 = std::vector of length 12, capacity 48 = {85 'U', 72 'H', 137 '\211', 229 '\345', 72 'H', 137 '\211', 248 '\370', 72 'H', 1 '\001', 240 '\360', 93 ']', 195 '\303'} (gdb) p add_numbers.size() Attempt to extract a component of a value that is not a struct or union (gdb) p add_numbers_code.size() $2 = 12 (gdb) ptype add_numbers type = int (*)(int, int) (gdb) ptype add_numbers_code type = std::vector<unsigned char>
Call the function pointer add_numbers()
(gdb) call add_numbers(20, 100) $3 = 120 (gdb) call add_numbers(222, -100) $4 = 122 (gdb) call add_numbers(222, -1000) $5 = -778
Show memory contents as machine code at function pointer memory location:
# Bytes at the function pointer memory location # (memory allocated with mmap) (gdb) x/12b add_numbers 0x7ffff7ffb000: 0x55 0x48 0x89 0xe5 0x48 0x89 0xf8 0x48 0x7ffff7ffb008: 0x01 0xf0 0x5d 0xc3 # Show assembly x86-64 (64 bits) in AT&T syntax (gdb) x/6i add_numbers 0x7ffff7ffb000: push %rbp 0x7ffff7ffb001: mov %rsp,%rbp 0x7ffff7ffb004: mov %rdi,%rax 0x7ffff7ffb007: add %rsi,%rax 0x7ffff7ffb00a: pop %rbp 0x7ffff7ffb00b: retq (gdb) (gdb) set disassembly-flavor intel # Show assembly x86-64 (64 bits) in Intel syntax (gdb) x/6i add_numbers 0x7ffff7ffb000: push rbp 0x7ffff7ffb001: mov rbp,rsp 0x7ffff7ffb004: mov rax,rdi 0x7ffff7ffb007: add rax,rsi 0x7ffff7ffb00a: pop rbp 0x7ffff7ffb00b: ret (gdb)
Continue execution until process termination:
(gdb) n [RESULT] add_numbers(20, 100) = 120 43 std::cout << " [RESULT] add_numbers(206, 782) = " << add_numbers(206, 782) << '\n'; (gdb) c Continuing. [RESULT] add_numbers(206, 782) = 988 ======== Load factorial() from machine code ============= [RESULT] factorial(5) = 24 [RESULT] factorial(6) = 120 [RESULT] factorial(7) = 720 [TRACE] Process terminated gracefully Ok. [Inferior 1 (process 933348) exited normally] (gdb)
Procedure for Extracting the sample machine code with Objdump
This procedure presents how the sample machine code was extracted with objdump tool.
File: functions.c
#include <stdio.h> int factorial(int n) { int prod = 1; for(int i = 1; i < n; i++){ prod = prod * i; } return prod; } int main() { printf(" factorial(5) = %d \n", factorial(5)); printf(" factorial(6) = %d \n", factorial(6)); return 0; }
Build:
$ >> gcc functions.c -o functions.bin -Wall -Wextra -O0 ~/t/asm $ >> ./functions.bin factorial(5) = 24 factorial(6) = 120
Extract assembly and machine code (encoded as hexadecimal bytes):
$ objdump functions.bin -M intel -d functions.bin: file format elf64-x86-64 Disassembly of section .init: 0000000000401000 <_init>: 401000: f3 0f 1e fa endbr64 401004: 48 83 ec 08 sub rsp,0x8 401008: 48 8b 05 e9 2f 00 00 mov rax,QWORD PTR [rip+0x2fe9] # 403ff8 <__gmon_start__> ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... 0000000000401126 <factorial>: 401126: 55 push rbp 401127: 48 89 e5 mov rbp,rsp 40112a: 89 7d ec mov DWORD PTR [rbp-0x14],edi 40112d: c7 45 fc 01 00 00 00 mov DWORD PTR [rbp-0x4],0x1 401134: c7 45 f8 01 00 00 00 mov DWORD PTR [rbp-0x8],0x1 40113b: eb 0e jmp 40114b <factorial+0x25> 40113d: 8b 45 fc mov eax,DWORD PTR [rbp-0x4] 401140: 0f af 45 f8 imul eax,DWORD PTR [rbp-0x8] 401144: 89 45 fc mov DWORD PTR [rbp-0x4],eax 401147: 83 45 f8 01 add DWORD PTR [rbp-0x8],0x1 40114b: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8] 40114e: 3b 45 ec cmp eax,DWORD PTR [rbp-0x14] 401151: 7c ea jl 40113d <factorial+0x17> 401153: 8b 45 fc mov eax,DWORD PTR [rbp-0x4] 401156: 5d pop rbp 401157: c3 ret .... ... .... ... .... ... .... ... .... ... .... ... .... ... .... ... .... ... .... ... .... ... .... ...
1.25 POSIX Shared Memory
1.25.1 Overview
POSIX (Portable Operarting System Interface) shared memory is a IPC inter-process communication API which allows multiple processes to share the same physical memory segment and transfer data without any copying overhead, unlike pipes, message queues, TCP/IP sockets, UDP/IP sockets or unix domain sockets.
Unix Shared Memory APIs
Unix-based operating systems, including Linux, BSD variants and MacOSX have the following APIs for shared memory.
- MMAP - Memory Mapped Files (mmap)
- Functions: mmap(), ftruncate(), munmap()
- System V shared memory API
- Functions: shmget(), shmat(), ftok()
- Posix Shared Memory shm (similar to mmap)
- Functions: shm_open(), ftruncate(), mmap().
On Linux, the POSIX shared memory is implemented as /dev/shm/<SHARED-MEMORY-NAME> memory mapped file which is an ordinary file created in the /dev/shm pseudo file system, that only exists in the volatile RAM memory. Regardless of the shared memory API, they are prone to race-condition problems if two processes attempt to write to the memory simultaneously which can lead to data corruption and unpredictable behavior. As a result, locks, semaphores and other mutual-exclusion concurrency primitives are necessary to synchronize the access to the shared memory in order to avoid race conditions and data corruption.
Limitations and potential problems
The following limitation for objects allocation are noteworthy:
- Objects allocated in the shared memory should be contiguous and have any internal indirection or any internal pointer, in other words, they should be POD types, plain-old-data.
- It is not possible to allocate objects containing STL containers without special allocators.
- It is also not possible to allocate polymorphic objects, objects implemeting some C++ interface class with virtual member functions.
Headers
- #include <sys/mman.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <unistd.h>
Functions
// Open or create a new shared memory segment int shm_open(const char* name, int oflag, mode_t mode); // Remove shared memory segment int shm_unlink(const char* name); // Truncate or resize file to specific lenght int ftruncate(int fd, off_t length); // Map a file to current process address space void *mmap( void* addr // Process virtual address where the file will be mapped , size_t length // Length in bytes of the file mapping , int prot // Protection flags , int flags // Read-Write type of access , int fd // File descriptor to be mapped , off_t offset // Offset from beginning of file to be mapped. ); // Unmap file mapping from current int munmap(void* addr, size_t length);
Documentation and Further Reading
1.25.2 Sample Code
This sample code aims to experiment POSIX shared memory subsystem. This sample program has two functionalities, a server functionality, which allocates an object in the shared memory and a client functionality which loads and modifies the object allocated in the shared memory segment.
Sources available at:
File: shmem1.cpp
#include <iostream> #include <string> #include <cassert> #include <cstring> // ----- Unix and Posix headers ---// #include <unistd.h> #include <sys/types.h> #include <sys/mman.h> #include <sys/stat.h> #include <fcntl.h> // Implementation of object allocated in Shared memory struct Object { int x = 0; double y = 26.2124214; char dataset[400] = "Hello world"; Object(){ std::cout << " [TRACE] Object created Ok \n" << '\n'; } void set_x(int x) { this->x = x; } void set_y(double y) { this->y = y; } void set_str(const char* value) { mempcpy(dataset, value, 200); } void show() const { std::cout << " [TRACE] Shared memory object =>> " << " ; x = " << x << " ; y = " << y << " ; str = " << dataset << '\n'; } }; constexpr int FAILURE = -1; constexpr size_t STORAGE_SIZE = sizeof(Object); constexpr const char* shared_memory_name = "/shared-data"; // Provides shared memory object. void server_side(const char* shm_name); // Consumes shared memory object. void client_side(const char* shm_name); int main(int argc, char** argv) { std::cout << " [INFO] Process Unique Identifier PID = " << ::getpid() << '\n'; std::cout << " [INFO] Storage size in bytes = " << STORAGE_SIZE << '\n'; if(argc < 2){ std::cerr << " [ERROR] Expected command argument. " << '\n'; return EXIT_FAILURE; } auto command = std::string{argv[1]}; if(command == "server" ){ server_side(shared_memory_name); } if(command == "client" ){ client_side(shared_memory_name); } return 0; } // ========= I M P L E M E N T A T I O N S =================// void server_side(const char* shm_name) { // Remove shared memory segment if it already exists. ::shm_unlink(shm_name); // Attempt to create shared memory segment // On Linux this segment corresponds to the file: '/dev/shm/<SHM-NAME>' int fd = shm_open( shm_name , O_CREAT | O_EXCL | O_RDWR , S_IRUSR | S_IWUSR ); // assert => Placeholder for future error handling assert( fd != FAILURE && "Failed to create shared memory segment" ); // Resize shared memory segment assert( ::ftruncate(fd, STORAGE_SIZE) != FAILURE ); // Map shared memory segment into process address space void* pmap = ::mmap( nullptr // Most of the time set to nullptr , STORAGE_SIZE // Size of memory mapping , PROT_READ | PROT_WRITE // Allows reading and writing operations , MAP_SHARED // This flag makes this segment visible by other processes. , fd // File descriptor , 0x00 // Offset from beggining of file ); if(pmap == MAP_FAILED){ perror("mmap"); exit(EXIT_FAILURE); } // Allocate object in shared memory segment // using placement 'new operator' Object* pObj = new (pmap) Object; assert( pObj != nullptr ); std::cout << " [BEFORE] Before typing RETURN " << '\n'; pObj->show(); std::cout << "\n\n Type return to exit" << '\n'; std::getchar(); std::cout << " [BEFORE] After typing RETURN " << '\n'; pObj->show(); // Mmap cleanup procedure assert( ::munmap(pmap, STORAGE_SIZE) != FAILURE ); // Remove shared memory segment ::shm_unlink(shm_name); } void client_side(const char* shm_name) { // Attempt to create shared memory segment int fd = ::shm_open( shm_name, O_RDWR, 0); // assert => Placeholder for future error handling assert( fd != - 1 && "Failed to open shared memory segment" ); // Map shared memory segment into process address space void* pmap = ::mmap( nullptr // Most of the time to NULL or nullptr , sizeof STORAGE_SIZE // Size of memory mapping , PROT_READ | PROT_WRITE // Allows reading and writing operations , MAP_SHARED // This flag makes this segment visible by other processes. , fd // File descriptor , 0x00 // Offset from beggining of file ); if(pmap == MAP_FAILED){ perror("mmap"); exit(EXIT_FAILURE); } /* Load object from shared memory knowing only its interface * (pointer to interface class), without knowing its exact * implementation or type. */ Object* pObj = reinterpret_cast<Object*>(pmap); std::cout << " Previous object value " << '\n'; pObj->show(); int x; double y; std::string str; std::cout << "Enter x, y and str: "; std::cin >> x >> y >> str; pObj->set_x(x); pObj->set_y(y); pObj->set_str(str.c_str()); pObj->show(); // assert( ::munmap(pmap, STORAGE_SIZE) != FAILURE ); }
Building:
$ >> g++ shmem1.cpp -o shmem1.bin -std=c++1z -g -O0 -Wall -Wextra -lrt
Run as a server in terminal 1:
$ >> ./shmem1.bin server
[INFO] Process Unique Identifier PID = 81749
[INFO] Storage size in bytes = 416
[TRACE] Object created Ok
[BEFORE] Before typing RETURN
[TRACE] Shared memory object =>> ; x = 0 ; y = 26.2124 ; str = Hello world
Type return to exit
[BEFORE] After typing RETURN
[TRACE] Shared memory object =>> ; x = 26234 ; y = 3.15156 ; str = cpp-shared-memory-allocation-shared-object
Run as a client in terminal 2:
$ >> ./shmem1.bin client [INFO] Process Unique Identifier PID = 81777 [INFO] Storage size in bytes = 416 Previous object value [TRACE] Shared memory object =>> ; x = 0 ; y = 26.2124 ; str = Hello world Enter x, y and str: 20 -90.25122 hello-world-cpp1z [TRACE] Shared memory object =>> ; x = 20 ; y = -90.2512 ; str = hello-world-cpp1z $ >> ./shmem1.bin client [INFO] Process Unique Identifier PID = 81811 [INFO] Storage size in bytes = 416 Previous object value [TRACE] Shared memory object =>> ; x = 20 ; y = -90.2512 ; str = hello-world-cpp1z Enter x, y and str: 26234 3.15156324 cpp-shared-memory-allocation-shared-object [TRACE] Shared memory object =>> ; x = 26234 ; y = 3.15156 ; str = cpp-shared-memory-allocation-shared-object $ >> ./shmem1.bin client [INFO] Process Unique Identifier PID = 81837 [INFO] Storage size in bytes = 416 Previous object value [TRACE] Shared memory object =>> ; x = 26234 ; y = 3.15156 ; str = cpp-shared-memory-allocation-shared-object Enter x, y and str: ^C⏎
While the server is running, the following command outputs can be observed:
$ >> ls /dev/shm/shared-data /dev/shm/shared-data $ >> file /dev/shm/shared-data /dev/shm/shared-data: data $ >> cat /dev/shm/shared-data zf=���f6 @cpp-shared-memory-allocation-shared-object1�y-allocation-s��⏎ # ---- Process mappings -------------------------// $ >> pmap 81749 81749: ./shmem1.bin server 0000000000400000 8K r---- shmem1.bin 0000000000402000 4K r-x-- shmem1.bin 0000000000403000 4K r---- shmem1.bin 0000000000404000 4K r---- shmem1.bin .... ... .... ... .... ... .... ... .... ... .... ... .... ... .... ... .... ... 00007faddd82f000 32K r---- ld-2.31.so 00007faddd837000 4K rw-s- shared-data 00007faddd838000 4K r---- ld-2.31.so 00007faddd839000 4K rw--- ld-2.31.so 00007faddd83a000 4K rw--- [ anon ] 00007ffe9af95000 136K rw--- [ stack ] 00007ffe9afc0000 16K r---- [ anon ] 00007ffe9afc4000 8K r-x-- [ anon ] ffffffffff600000 4K r-x-- [ anon ] total 5944K
Attempt to run application in client mode after the server process is terminated.
$ >> ./shmem1.bin client [INFO] Process Unique Identifier PID = 82176 [INFO] Storage size in bytes = 416 shmem1.bin: shmem1.cpp:128: void client_side(const char*): Assertion `fd != - 1 && "Failed to open shared memory segment"' failed. fish: “./shmem1.bin client” terminated by signal SIGABRT (Abort)
1.26 memfd_create syscall - Loading from memory
This sample code illustrates how the Linux system call memfd_create can be used for loading shared libraries and executable directly from memory without writing them to disk.
Documentation:
Download incbin.h for embedding files in the executable memfd_test.bin
$ curl -O -L "https://raw.githubusercontent.com/graphitemaster/incbin/8cefe46d5380bf5ae4b4d87832d811f6692aae44/incbin.h"
Files
File: memfd_test.cpp
#include <iostream> #include <string> #include <cstring> #include <cassert> #include <fcntl.h> #include <stdlib.h> #include <unistd.h> #include <linux/memfd.h> #include <sys/mman.h> #include <sys/syscall.h> #include <dlfcn.h> // Incbin will embed the file named 'binary' in this executable. #define INCBIN_STYLE INCBIN_STYLE_SNAKE #define INCBIN_PREFIX g_ #include "incbin.h" INCBIN(asset, "executable.bin"); INCBIN(shlib, "shlib.so"); // #include <sys/memfd.h> // Execute ELF64 binary from memory void execv_mem(const char* file_name, const std::byte* buffer, size_t buffer_size) { int fd = memfd_create(file_name, MFD_ALLOW_SEALING); assert( fd != - 1); // Write from memory to anonymous file write(fd, buffer, buffer_size ); char path[100]; memset(path, 0x00, 100); sprintf(path, "/proc/self/fd/%d", fd); // close(fd); // - Process name can be anything ----------- const char* process_name = "ghost"; const char* args[] = { process_name, nullptr }; execvp(path, (char* const*) args); } // Load shared library from memory without writing to disk!! void* dlopen_mem(const char* file_name, const std::byte* buffer, size_t buffer_size) { int fd = memfd_create(file_name, MFD_ALLOW_SEALING); assert( fd != - 1); // Write from memory to anonymous file write(fd, buffer, buffer_size ); char path[100]; memset(path, 0x00, 100); sprintf(path, "/proc/self/fd/%d", fd); void* handle = dlopen(path, RTLD_LAZY | RTLD_GLOBAL); return handle; } using tfunc = int (*) (const char* param); int main(int argc, char** argv) { // --------- Load library from memory ---------------// // void* hnd = dlopen_mem("dummyname", (std::byte*) g_shlib_data, g_shlib_size ); assert(hnd != nullptr); tfunc exported_func = (tfunc) dlsym(hnd, "exported_function"); assert(exported_func != nullptr); exported_func("Passed to memory loaded library Ok"); // --------- Run executable from memory ------------------// // fprintf(stderr, " [TRACE] Executing ELF64 file from memory. Ok \n"); execv_mem("anon-file", (std::byte*) g_asset_data, g_asset_size); // execvp(path, (char* const*) args); }
File: app.cpp (Executable to be run from memory)
#include <iostream> int main() { std::cout << " [*] Hello world / Hallo Welt! (Deustche) " << std::endl; return 0; }
File: shlib.cpp (Shared library to be loaded from memory)
#include <iostream> #include <cstring> #define EXTERN_C extern "C" EXTERN_C int exported_function(const char* param) { fprintf(stderr, " [TRACE] (Library) User parameter = %s \n", param); return 1002 + strlen(param); }
Building
Build the shared library:
$ g++ shlib.cpp -o shlib.so -fPIC -shared -ldl -Wall -g
Build the executable:
$ g++ app.cpp -o executable.bin
Build the loader:
$ g++ memfd_test.cpp -o memfd_test.bin -ldl -std=c++1z -Wall -Wextra -g
Running
After the loader is built, delete shlib.so and executable.bin binary artifacts:
$ rm shlib.so executable.bin
Finally, run the loader:
$ ./memfd_test.bin [TRACE] (Library) User parameter = Passed to memory loaded library Ok [TRACE] Executing ELF64 file from memory. Ok [*] Hello world / Hallo Welt! (Deustche)
1.27 Syslog - System Message Logging
1.27.1 Overview
Several Unix-like operating systems provide a system message logging, syslog system which allows applications, such as servers and daemons (a.k.a system services) to send logging messages reporting events, auditing and debugging information to a central location, a syslog server which can be in the same machine as the application or in a remote host.
The syslog system has a client-server architecture, comprised of two main parts, the client which is any application that sends the logging message and the server which receives and consolidates client messages via Unix Domain Sockets on local machine; UDP/IP or TCP/IP if the server is located in a remote machine.
Some motivations for using syslog are:
- Logging consolidation in a central location - which improves discoverability and makes easier to debug, filter and audit applications that runs without any graphical interface.
- Logging persistence
- Standard logging API, implemented by many Unix-like operating systems such as GNU/Linux distributions; GNU/Linux with systemd; FreeBSD; OpenBSD and MacOSX.
- Operating system agnostic. The syslog server or daemon only relies on TCP/UDP, TCP/IP or Unix-Domain sockets, which makes possible to implement a syslog system, even on non-Unix-like operating system.
- Familiarity of system administrators.
- Integration to system-d on GNU/Linux distributions.
- Network infrastructure equipment => Many common network equipment provide facilities for sending auditing information to syslog servers.
- Multiple ways of visualization. => Unlike printf-like statements, logging can be viewed and inspected from any terminal emulators, graphical applications and also from remote machines. Even for desktop applications, does not run in terminals, logging via syslog is a more convenient way for diagnosing and debugging the application control flow.
Potential use-cases:
- Logging for auditing and debugging in daemon (services) applications that runs in background and do not have any graphical user interface. Some of those applications are http servers, ftp servers, ssh servers, authentication systems and so on.
Syslog C API
API Documentation:
Syslog Protocol Technical Standard:
Headers:
- #include <syslog.h>
- #include <stdarg.h>
Functions:
- openlog() => Set logging configuration and attributes that affects
calls to the logging functions.
- ident => Unique tag name identifier which allows identifying and filtering the application that logging messages belong.
- logopt => Logging options bitwise-or flags that speficy the logging behavior.
- facility => Parameter the assigns thae default category that all log messages will be related to. Some possible values are: LOG_USER (default) - generic user-level messages ;LOG_AUTH - log messages are related to login and authentication via ssh, terminal and so on; LOG_CRON - Cron clock messages; LOG_FTP - ftp daemon; LOG_KERN for Kernel messages.
void openlog( const char* ident // Application unique identifierr , int logopt , int facility // Gr );
- closelog() => Disable logging messages.
void closelog(void);
- syslog() => Send logging messages to the syslog daemon.
- priority => Flag that indicates the logging severity. It can be: LOG_INFO for informational messages; LOG_DEBUG for debugging messages; LOG_WARNING for warnings; LOG_ERR for errros and so on. See full documentation: syslog
- message => Logging message.
void syslog(int priority, const char* message, ...); void vsyslog(int priority, const char* message, va_list args);
Further Reading
- Syslog - Wikipedia
- RFC 3164 - The BSD Syslog Protocol
- Common Log Format - Wikipedia
- Linux Logging Basics - The Ultimate Guide To Logging
- Running Syslog Within a Docker Container — Project Atomic
- How to collect Windows Event Logs with syslog-ng without installing an agent
- The original Windows Syslog Server - WinSyslog
- Configuring Syslog on Your Apple Mac OS X - TechLibrary - Juniper Networks
- How to View the System Log on a Mac
- Using OS X as a Syslog Server · GitHub
- syslog - The Go Programming Language
- syslog with golang | Medium
- FreeBSD - syslogd(8) - Syslog Daemon
- FreeBSD - logger(1) - Command line tool for sending logging messages to syslog server from terminal.
- FreeBSD - 11.7. Configuring System Logging
- FreeBSD - Remote Host Logging with syslogd
- Mac OS X Manual Page For syslog(3)
- syslog-ng Open Source Edition 3.16 - Administration Guide
1.27.2 Usage example
File: syslog-test.cpp
#include <iostream> #include <cmath> // ---- Unix-specific headers ---------// #include <syslog.h> #include <unistd.h> // sleep() int main(int argc, char** argv) { // Unique identifier const char* ident = "MYSERVER"; int logopt = // Include PID (process unique identifier number) // in the log messages LOG_PID | LOG_ODELAY // Print log messages to console (terminal) stderr | LOG_PERROR ; // Configure logger openlog(ident, logopt, LOG_USER); syslog(LOG_INFO, " Logging has started Ok."); syslog(LOG_DEBUG, "Starting server main loop"); for(int i = 0; i < 10; i++) { syslog(LOG_INFO, "Loop counter value = %d - sqrt(%d) = %f" , i, i, std::sqrt(i)); // Sleep for 1 second sleep(1); } syslog(LOG_WARNING, "Main loop terminated."); // Shutdown logging closelog(); return 0; }
Building:
$ g++ syslog-test.cpp -o syslog-test -Wall -Wextra -g -std=c++1z
Running: (Terminal 1)
$ >> build/test-syslog MYSERVER[1668452]: Logging has started Ok. MYSERVER[1668452]: Starting server main loop MYSERVER[1668452]: Loop counter value = 0 - sqrt(0) = 0.000000 MYSERVER[1668452]: Loop counter value = 1 - sqrt(1) = 1.000000 MYSERVER[1668452]: Loop counter value = 2 - sqrt(2) = 1.414214 MYSERVER[1668452]: Loop counter value = 3 - sqrt(3) = 1.732051 MYSERVER[1668452]: Loop counter value = 4 - sqrt(4) = 2.000000 MYSERVER[1668452]: Loop counter value = 5 - sqrt(5) = 2.236068 MYSERVER[1668452]: Loop counter value = 6 - sqrt(6) = 2.449490 MYSERVER[1668452]: Loop counter value = 7 - sqrt(7) = 2.645751 MYSERVER[1668452]: Loop counter value = 8 - sqrt(8) = 2.828427 MYSERVER[1668452]: Loop counter value = 9 - sqrt(9) = 3.000000 MYSERVER[1668452]: Main loop terminated.
Monitoring via journalctl: (Terminal 2)
In a second terminal run the command '$ journalctl -f' that displays the newest logging entries.
$ >> journalctl -f -- Logs begin at Mon 2020-05-11 22:09:13 -03. -- Nov 27 14:20:39 localhost.localdomain dnf[1666375]: Fedora 32 - x86_64 21 kB/s | 3.3 kB 00:00 Nov 27 14:20:40 localhost.localdomain dnf[1666375]: RPM Fusion for Fedora 32 - Nonfree - NVIDIA Dri 23 kB/s | 11 kB 00:00 ... ... ... ... ... ... ... ... ... ... ... ... Nov 27 14:50:44 localhost.localdomain MYSERVER[1668452]: Logging has started Ok. Nov 27 14:50:44 localhost.localdomain MYSERVER[1668452]: Starting server main loop Nov 27 14:50:44 localhost.localdomain MYSERVER[1668452]: Loop counter value = 0 - sqrt(0) = 0.000000 Nov 27 14:50:45 localhost.localdomain MYSERVER[1668452]: Loop counter value = 1 - sqrt(1) = 1.000000 Nov 27 14:50:46 localhost.localdomain MYSERVER[1668452]: Loop counter value = 2 - sqrt(2) = 1.414214 Nov 27 14:50:47 localhost.localdomain MYSERVER[1668452]: Loop counter value = 3 - sqrt(3) = 1.732051 Nov 27 14:50:48 localhost.localdomain MYSERVER[1668452]: Loop counter value = 4 - sqrt(4) = 2.000000 Nov 27 14:50:49 localhost.localdomain MYSERVER[1668452]: Loop counter value = 5 - sqrt(5) = 2.236068 Nov 27 14:50:50 localhost.localdomain MYSERVER[1668452]: Loop counter value = 6 - sqrt(6) = 2.449490 Nov 27 14:50:51 localhost.localdomain MYSERVER[1668452]: Loop counter value = 7 - sqrt(7) = 2.645751 Nov 27 14:50:52 localhost.localdomain MYSERVER[1668452]: Loop counter value = 8 - sqrt(8) = 2.828427 Nov 27 14:50:53 localhost.localdomain MYSERVER[1668452]: Loop counter value = 9 - sqrt(9) = 3.000000 Nov 27 14:50:54 localhost.localdomain MYSERVER[1668452]: Main loop terminated.
View only logging entries related to tag MYSERVER: (Approach 1)
$ >> journalctl -f -t MYSERVER -- Logs begin at Mon 2020-05-11 22:09:13 -03. -- Nov 27 14:50:45 localhost.localdomain MYSERVER[1668452]: Loop counter value = 1 - sqrt(1) = 1.000000 Nov 27 14:50:46 localhost.localdomain MYSERVER[1668452]: Loop counter value = 2 - sqrt(2) = 1.414214 Nov 27 14:50:47 localhost.localdomain MYSERVER[1668452]: Loop counter value = 3 - sqrt(3) = 1.732051 Nov 27 14:50:48 localhost.localdomain MYSERVER[1668452]: Loop counter value = 4 - sqrt(4) = 2.000000 Nov 27 14:50:49 localhost.localdomain MYSERVER[1668452]: Loop counter value = 5 - sqrt(5) = 2.236068 Nov 27 14:50:50 localhost.localdomain MYSERVER[1668452]: Loop counter value = 6 - sqrt(6) = 2.449490 Nov 27 14:50:51 localhost.localdomain MYSERVER[1668452]: Loop counter value = 7 - sqrt(7) = 2.645751 Nov 27 14:50:52 localhost.localdomain MYSERVER[1668452]: Loop counter value = 8 - sqrt(8) = 2.828427 Nov 27 14:50:53 localhost.localdomain MYSERVER[1668452]: Loop counter value = 9 - sqrt(9) = 3.000000 Nov 27 14:50:54 localhost.localdomain MYSERVER[1668452]: Main loop terminated.
View only log entries related to tag MYSERVER: (Approach 2)
$ >> journalctl SYSLOG_IDENTIFIER=MYSERVER
-- Logs begin at Mon 2020-05-11 22:09:13 -03, end at Fri 2020-11-27 14:52:01 -03. --
Nov 27 14:50:44 localhost.localdomain MYSERVER[1668452]: Logging has started Ok.
Nov 27 14:50:44 localhost.localdomain MYSERVER[1668452]: Starting server main loop
Nov 27 14:50:44 localhost.localdomain MYSERVER[1668452]: Loop counter value = 0 - sqrt(0) = 0.000000
Nov 27 14:50:45 localhost.localdomain MYSERVER[1668452]: Loop counter value = 1 - sqrt(1) = 1.000000
Nov 27 14:50:46 localhost.localdomain MYSERVER[1668452]: Loop counter value = 2 - sqrt(2) = 1.414214
Nov 27 14:50:47 localhost.localdomain MYSERVER[1668452]: Loop counter value = 3 - sqrt(3) = 1.732051
Nov 27 14:50:48 localhost.localdomain MYSERVER[1668452]: Loop counter value = 4 - sqrt(4) = 2.000000
Nov 27 14:50:49 localhost.localdomain MYSERVER[1668452]: Loop counter value = 5 - sqrt(5) = 2.236068
Nov 27 14:50:50 localhost.localdomain MYSERVER[1668452]: Loop counter value = 6 - sqrt(6) = 2.449490
Nov 27 14:50:51 localhost.localdomain MYSERVER[1668452]: Loop counter value = 7 - sqrt(7) = 2.645751
Nov 27 14:50:52 localhost.localdomain MYSERVER[1668452]: Loop counter value = 8 - sqrt(8) = 2.828427
Nov 27 14:50:53 localhost.localdomain MYSERVER[1668452]: Loop counter value = 9 - sqrt(9) = 3.000000
Nov 27 14:50:54 localhost.localdomain MYSERVER[1668452]: Main loop terminated.
View log messages in JSON format (machine readable):
$ >> journalctl SYSLOG_IDENTIFIER=MYSERVER -o json-pretty { "_MACHINE_ID" : "a1ac43b933e24659bba5edf2b9cec1e1", "_CMDLINE" : "build/test-syslog", "_UID" : "1000", "_HOSTNAME" : "localhost.localdomain", "_SYSTEMD_SLICE" : "user-1000.slice", "PRIORITY" : "6", "_COMM" : "test-syslog", "_EXE" : "/home/myunixuser/temp-projects/quickjs-project/build/test-syslog", "SYSLOG_FACILITY" : "1", "MESSAGE" : " Logging has started Ok.", "_SOURCE_REALTIME_TIMESTAMP" : "1606499444168590", "_GID" : "1000", "_SYSTEMD_CGROUP" : "/user.slice/user-1000.slice/session-6.scope", "_SYSTEMD_USER_SLICE" : "-.slice", "_AUDIT_LOGINUID" : "1000", "_SYSTEMD_SESSION" : "6", "__CURSOR" : "s=c5bc71b70e1e4196ad228c9854261d9a;i=a55c3;b=367eba6384324bf9a1b17ed43352d82e;m=218d3e06963;t=5b51a4cb397e2;x=ce1952db56e33f8b", "__REALTIME_TIMESTAMP" : "1606499444168674", "_PID" : "1668452", "_BOOT_ID" : "367eba6384324bf9a1b17ed43352d82e", "_TRANSPORT" : "syslog", "SYSLOG_IDENTIFIER" : "MYSERVER", "_AUDIT_SESSION" : "6", "__MONOTONIC_TIMESTAMP" : "2305657170275", "_SYSTEMD_UNIT" : "session-6.scope", "_CAP_EFFECTIVE" : "0", "SYSLOG_PID" : "1668452", "_SYSTEMD_OWNER_UID" : "1000", "SYSLOG_TIMESTAMP" : "Nov 27 14:50:44 ", "_SYSTEMD_INVOCATION_ID" : "b25ee3d518c54e5c805521823f22d60d" } { "_SYSTEMD_OWNER_UID" : "1000", "SYSLOG_IDENTIFIER" : "MYSERVER", "_SOURCE_REALTIME_TIMESTAMP" : "1606499444168718", "_PID" : "1668452", "_UID" : "1000", "_CMDLINE" : "build/test-syslog", "_GID" : "1000", "_AUDIT_SESSION" : "6", "MESSAGE" : "Starting server main loop", "__MONOTONIC_TIMESTAMP" : "2305657481811", "PRIORITY" : "7", "__CURSOR" : "s=c5bc71b70e1e4196ad228c9854261d9a;i=a55c4;b=367eba6384324bf9a1b17ed43352d82e;m=218d3e52a53;t=5b51a4cb858d3;x=f796bcc2cb08f71e", "_HOSTNAME" : "localhost.localdomain", "_SYSTEMD_INVOCATION_ID" : "b25ee3d518c54e5c805521823f22d60d", "_MACHINE_ID" : "a1ac43b933e24659bba5edf2b9cec1e1", "_BOOT_ID" : "367eba6384324bf9a1b17ed43352d82e", "_SYSTEMD_CGROUP" : "/user.slice/user-1000.slice/session-6.scope", "SYSLOG_FACILITY" : "1", "_SYSTEMD_USER_SLICE" : "-.slice", "_SYSTEMD_UNIT" : "session-6.scope", "_SYSTEMD_SLICE" : "user-1000.slice", "__REALTIME_TIMESTAMP" : "1606499444480211", "_TRANSPORT" : "syslog", "_EXE" : "/home/myunixuser/temp-projects/quickjs-project/build/test-syslog", "_COMM" : "test-syslog", "SYSLOG_PID" : "1668452", "_CAP_EFFECTIVE" : "0", "SYSLOG_TIMESTAMP" : "Nov 27 14:50:44 ", "_AUDIT_LOGINUID" : "1000", "_SYSTEMD_SESSION" : "6" } ... ... ... ... ... ... ...
See also:
- debian jessie - systemd's journalctl: how to filter by message? - Server Fault
- c - Pulling log messages for a particular log in systemd journal? - Unix & Linux Stack Exchange
- How to analyze Linux systemd logs using journalctl advanced filtering options
- Logging w/ journald: Why use it & how it performs vs syslog - Sematext
- How To Use Journalctl to View and Manipulate Systemd Logs | DigitalOcean
1.28 Sockets - TCP/IP and Unix Domain Sockets
1.28.1 Overview
Sockets are inter process communication mechanism, intruduced by Unix BSD 4.1 circa 1982, which allows multiple processes running at different machines to communicate across TCP/IP network.
Main Implementations
- BSD Sockets => Pervarsive on Unix-like operating systems: BSD, Linux, MacOSX, ….,
- Winsocks => Microsft Windows only (Windows NT and Windows CE families)
Domain and Connection Type
A socket endpoint is defined by its domain and connection type.
Most common socket domains:
- AF_INET => IPv4 - Internet Protocol [Most used]
- AF_INET6 => IPv6 - Intenert Protocol (IPv6)
- AF_UNIX => Unix Domain socket on local machine [Most used]
Most common connection Types:
- SOCK_STREAM => TCP - Transmission Control Protocol
- SOCK_DGRAM => UDP - User Datagram Protocol
- SOCK_RAW => Raw network protocol (Requires root permission)
Socket server or socket client:
- socket client
- => Connects to socket server and always starts the connection. Example: curl; web browser (http client); netcat
- socket server
- => Listen some port and waits client connection. It can handle one or more clients. Example: Http server, nginx, ssh server.
TCP/IP connection tuple:
- Hostname or IP address (IPv4 or IPv6)
- 0.0.0.0 / address used by a socket server for listening all hosts.
- 127.0.0.1 / Localhost - refers to the local (current) machine
- 'localhost' / Localhost - refers to the local (current) machine, same as (127.0.0.1)
- Port number: 16 bits number
Common TCP/IP Ports:
- 20, 21 - FTP server
- 22 - SSH server
- 23 - Telnet (SSH predecessor)
- 80 - HTTP server
- 443 - HTTPS (HTTP + SSL/TSL) - Http with SSL encryption
- 143 - IMAP - Mail
- 25 - SMTP - Simple Mail Transfer Protocol
- More at: List of TCP and UDP port numbers
Further Reading
1.28.2 BSD Socket API
The BSD socket API is the reference-implementation for network sockets inter-process communication mechanism, which powers the internet. The BSD socket API was initially implemented on BSD Unix and nowadays is used by the most Unix-based operating systems, namely, Linux, BSD-variants (FreeBSD, NetBSD, OpenBSD) and MacOSX. The Windows sockets (Winsocks) implementation is also based on BSD sockets API, although one of the main differences is that on the Winsocks API, sockets are not file descriptors and Unix read(),/write() calls or windows equivalent APIs cannot be used with sockets.
Headers:
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <netdb.h>
Functions for creating and disposing sockets:
- socket()
- Linux Manpage: "socket() creates an endpoint for communication and returns a file descriptor that refers to that endpoint. The file descriptor returned by a successful call will be the lowest-numbered file descriptor not currently open for the process."
- Note: returns -1 when an error happens and sets the errno thread-local global variable.
int socket(int domain, int type, int protocol); // --------- Example ----------------------------// // Usage for TCP/IPv4 int sockfd = socket(AF_INET, SOCK_STREAM, 0); // Placeholder for future error handling assert( sockfd != -1 && "Unable to create socket." ); // Error handling: if(sockfd == -1){ perror("Error: failed to create a socket connect. Check errno"); exit(EXIT_FAILURE); }
Usages for instantiating sockets:
/************ TCP / IP ******************************/ //--------------------------------------------------// // Usage for TCP/IPv4 int sockfd = socket(AF_INET, SOCK_STREAM, 0); // Usage for TCP/IPv6 int sockfd = socket(AF_INET6, SOCK_STREAM, 0); /************* UDP / IP ******************************/ //--------------------------------------------------// // Usage for UDP/IPv4 int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // Usage for UDP/IPv6 int sockfd = socket(AF_INET6, SOCK_DGRAM, 0); /*********** SCTP / IP *****************************/ //--------------------------------------------------// // Usage for SCTP / IPv4 - (SCTP - Stream Control Protocol) int fd = socket(PF_INET, SOCK_STREAM, IPPROTO_SCTP); /***** Unix Domain Sockets (Only for local host) *****/ //--------------------------------------------------// // Usage for Unix socket with TCP int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); // Usage for Unix socket with UDP int sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
- close() => Close file descriptor or socket file descriptor.
int close(int fd);
Functions for setting a socket as client-socket:
- connect() => Connect a client socket to a given address. On error, (-1) is returned. On success, 0 is returned.
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
Functions for setting up a socket as a server socket (aka passive socket, listening socket)
- bind() [SOCKET SERVER] => Bind a given port for listening income connections. (used for socket servers)
int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen); struct sockaddr { sa_family_t sa_family; char sa_data[14]; };
- listen() [SOCKET SERVER]
- Linux Manpage: "listen() marks the socket referred to by sockfd as a passive socket, that is, as a socket that will be used to accept incoming connection requests using accept(2)."
- Linux Manpage: "The backlog argument defines the maximum length to which the queue of pending connections for sockfd may grow. If a connection request arrives when the queue is full, the client may receive an error with an indication of ECONNRE‐ FUSED or, if the underlying protocol supports retransmission, the request may be ignored so that a later reattempt at connection succeeds."
int listen(int sockfd, int backlog);
- setsockopt() => Manipulate socket options.
- Doc: $ man setsockopt
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
Functions for sending and receiving messages
Functions send, and recev works on all implementations of sockets BSD Sockets, used by Unix-based operating systems, and Windows sockets.
- send() => Send a buffer to a socket.
- Doc: $ man send
// Returns number of bytes received ssize_t send( int sockfd // Socket file descriptor , const void* buf // Pointer to buffer (any POD - Plain Old Data) , size_t len // Buffer length (or size) in bytes , int flags // Flags (bitwise OR flags) );
- recv() => Receive a message from a socket. Read 'len' bytes from
socket and store data in a buffer. Note: the buffer argument is a
pointer to any POD (Plain-Old Data) type, contiguos allocated data
without any internal pointer.
- Doc: $ man recv
- This function returns the number of bytes read which the maximum
value is the buffer length.
- The function returns (-1) on error and sets the errno global variable for indicating the failure.
- The return value (0) indicates that the connection was closed either by the current calling code with close() function applied on the socket file descriptor or by the counterpart socket located in the remote host.
- If a recev() call is made, and the counterpart socket did not send any data with send() or write(), the call to recev() blocks the current thread if the socket file descriptor is set in blocking mode (default).
- A call to recev() may not receive all bytes available, even though the peer socket has send a message larger than the buffer size. This situation is called partial-read that must be handled by the calling code.
ssize_t recv( int sockfd // Socket file descriptor , void* buffer // Pointer to buffer (POD type) , size_t len // Buffer length or size in bytes , int flags // Flags (bitwise OR flags );
Functions read(), write() also works with sockets file descriptors. However this feature is not portable to windows sockets implementation.
- read() => Similar as recev(), but it is only available with BSD-style. The usage of read() is not portable to Windows sockets.
ssize_t read( int fd // File descriptor or socket file descriptor , void* buffer // Pointer to buffer (any POD type) , size_t size // Buffer size in bytes );
- write() => Similar to write(), but not portable to Windows sockets. It writes data from buffer to any type of file descriptor and returns the number of bytes sent to the peer socket or returns (-1) on failure setting errno global variable.
ssize_t write( int fd // File descriptor or socket file descriptor , const void* buffer // Pointer to buffer (any POD type) , size_t size // Buffer size in bytes );
Endianess conversion for binary data exchange of numeric types
One of the pitfalls of binary data exchange is the endianess, the order which bytes of numeric types are placed in the memory. A code assuming that the server and client side have the same endianess may eventually fail and iterpret a numeric type in a incorrect way if the server or client runs in a machine (computer architecture) with a different endianess. In order to both socket communication endpoints interpret the data in a correct way, both have to agree about the data representation (_external representation_) which is the big-endian or network-byte-order. See also: (IBM endian-independent code)
The BSD socket API provides the following functions for converting between machine (host) and network-order endianess of numeric types. Note: those functions are not portable and are neither part of C standard nor part of POSIX standard.
Header:
- #include <arpa/inet.h>
Functions that converts from host to network endianess:
- htonl() => Conversion from host (local machine) to
network-byte-order endianess. It takes a number in local machine
byte-order and returns a number with network-byte-order (big
endian) endianess. This function should be used whenever the
calling code needs to write to the network via write() or send()
calls.
- Mneumonic: h-to-n-l (Host to network long)
uint32_t htonl(uint32_t hostlong); // --------- Usage example ------------// // // // ==> Number in host (local machine) endianess. uint32_t message_size = 500; // ==> Number in network-byte-order (big-endian) uint32_t data = htonl(message_size); if( send(sockfd, &data, sizeof(uint32_t), 0) == -1 ) { /* Handler error */ } // OR: if( write(sockfd, &data, sizeof(uint32_t)) == -1 ) { /* Handler error */ }
- htons() => Conversion from host to network-byte-order.
- Mneumonic: h-to-n-s => (Host to network short)
uint16_t htons(uint16_t hostshort); // --------- Usage Example -------------// // // ==> Number in host (local machine) endianess. uint16_t message_size = 500; // ==> Number in network-byte-order (big-endian) uint16_t data = htonl(message_size); if( send(sockfd, &data, sizeof(uint16_t), 0) == -1 ) { /* Handler error */ }
Functions that converts from network to host endianess:
- ntohl() => Takes a number in network-endianess as argument and
returns a number in local-machine endiness. This funtion should be
used whenever the calling code is reading data from the network
via read() or recev() calls.
- Mneumonic: n-to-h-l (Network to Host Long)
uint32_t ntohl(uint32_t netlong); // ------ Usage example / Reading data from network ----------// uint32_t buffer; // Buffer variable will receive the data network-byte-order // or big-endian. if( recv(sockfd, &buffer, sizeof(uint32_t), 0 ) == -1 ) { /** Error handling ... here */ } // OR: if( read(sockfd, &buffer, sizeof(uint32_t) ) == -1 ) { /** Error handling ... here */ } // Number in host endiness (byte order) uint32_t message_size = nothl(buffer); std::fprintf(" Message size in bytes: %u \n", message_size);
- ntohs() => Similar to ntohl, but uses with short type, or 16 bit
integers.
- Mneumonic: n-to-h-s (Network to Host Short)
uint16_t ntohs(uint16_t netshort); // ----------- Usage -----------------// uint16_t number; if( recv(sockfd, &number, sizeof(uint32_t), 0 ) == -1 ) { /** Error handling ... here */ } number = ntohs(number);
Functions for resolving hostnames (DNS Query)
- gethostbyname()
- Translate hostname or IPv4 into IPv4 address. This function performs a DNS query.
struct hostent* gethostbyname(const char* name); struct hostent { char *h_name; /* official name of host */ char **h_aliases; /* alias list */ int h_addrtype; /* host address type */ int h_length; /* length of address */ char **h_addr_list; /* list of addresses */ } #define h_addr h_addr_list[0] /* for backward compatibility */
1.28.3 Network Programming Pitfalls [DRAFT]
There are many pitfalls and edge-cases that a network-related can fall in to. This section aims to outline those issues that affect network code.
- Lack of error checking
- => Most BSD-socket APIs and Unix APIs return (-1) when an error happens and sets the errno global variable. If error is not properly checked, the calling code may be subject to silent and subtle failure without any warning. During the development assertions, assert(), statements can be used as temporary placeholders for error checking.
- TCP message boundary
- => The TCP protocol does not have message boundary, instead TCP is a byte stream oritend protocol. Threfore, there is no way to determine where a message begins or ends. For instance, if a sender sends three messages of 100 bytes via send() or write() functions and the peer socket attempts to read using three calls to read() or recev() using 1000 bytes long buffer, only a single a message will be received and the second call read() or recev() may block the current thread.
- Solutions:
- => Use a fixed size message (buffer with constant lenght) on both communication endpoints.
- => Use custom delimiters for distinguishing the beginning and end of messages.
- => Send messages prefixed with number of bytes to be read.
- => Replace TCP with SCTP (Stream Control Transmission Protocol) which combines the best features of TCP and UDP. It provides both the TCP realibility and UDP message-boundary feature.
- Partial-Reads
- => Socket functions such recev() or read() may return the number of bytes read smaller than the buffer size, even though there is still bytes avaiable and data sent has a fixed size agreed by both socket endpoints. This edge-case scenario is called partial-read, a robust network code should be keeping reading the file descriptor until the buffer is filled or the connection is closed.
- Partial-Writes
- => Functions write() or send() may also be affected by similar edge-case to partial-reads and the number of bytes written returned by write() or send() may be smaller than the buffer size. In this case, the calling code, should be keeping sending the remaining parts of the buffer until all data sent over the network.
- Endianess (Byte order for numeric types)
- => In order to proper exchange a numeric type, both ends of a connection must agree about data endianess, or byte-order representation, since different processor architectures may have different byte order data representation of numeric types. In order to avoid a data being read in a wrong way, both sides of the connection must use the same endianess, which is the network-byte-order (big endian). So, a numeric value in machine endianess (default) should be converted to network-byte-order (big-endian) before being sent to the over the network, or written to the file descriptor. And a numeric value received from the network (socket file descriptor) must be converted from network-byte-order to the current machine endianess.
- Fix-width integers
- The fundamental integer types in C and C++, int, long, short, long long are not guaranteed to have the same size in all machine. For an instance, on Windows NT 64 bits the type 'long' has 32 bits, while on Linux, the type 'long' has 64 bits. If a socket server running on Linux attempts to send a value with long type to a client socket running on a Windows machine, the value will be interpreted in a incorrect way in the Windows machine. Data misinterpretation such as the previous mentioned one can be avoided by using fixed width integers instead of fundamental integer types as fix-width integers such as uint8_t, uint16_t, uint32_t, uint64_t, int8_t and so on, are guaranteed to have the same size in all machines
- Summary: User fix-width integers (headers: <cstdint> for C++ or <stdint.h> for C) instead of fundamental types for avoiding data interpretation disagreement.
- Sending and receiving structured data_
- Structs or C++ classes should not be sent or received directly via read(), recev(), write() or send() functions due to many issues related to ABI, machine endianess, type-sizes and message-boundary. The first shortcoming is that the buffer argument of the mentioned functions can only be a pointer to POD type, which is a contiguous allocated type without any internal pointer. Other shortcoming are the machine endianess and compilers ABIs (Application Binary Interfaces) which may lead to the data to corruption and and data misinterpretation.
- Solution: Convert and serialize each member of struct or class to an external data representation agreed by both connection endpoints.
Further Reading
1.28.4 Command Line Tools for Network Debugging
Show Routing Table
Linux:
$ route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 10.0.0.1 0.0.0.0 UG 600 0 0 wlp2s0 10.0.0.0 0.0.0.0 255.255.255.0 U 600 0 0 wlp2s0 169.254.0.0 0.0.0.0 255.255.0.0 U 1000 0 0 wlp2s0
Linux:
$ netstat -rn Kernel IP routing table Destination Gateway Genmask Flags MSS Window irtt Iface 0.0.0.0 10.0.0.1 0.0.0.0 UG 0 0 0 wlp2s0 10.0.0.0 0.0.0.0 255.255.255.0 U 0 0 0 wlp2s0 169.254.0.0 0.0.0.0 255.255.0.0 U 0 0 0 wlp2s0
Mac OSX:
$ netstat -rn Routing tables Internet: Destination Gateway Flags Netif Expire default 10.0.2.2 UGSc en0 10.0.2/24 link#8 UCS en0 ! 10.0.2.2/32 link#8 UCS en0 ! 10.0.2.2 52:55:a:0:2:2 UHLWIir en0 1161 10.0.2.3 link#8 UHLWIi en0 ! 10.0.2.15/32 link#8 UCS en0 ! 10.0.2.15 52:54:0:9:49:17 UHLWI lo0 127 127.0.0.1 UCS lo0 127.0.0.1 127.0.0.1 UH lo0 169.254 link#8 UCS en0 ! 224.0.0/4 link#8 UmCS en0 ! 224.0.0.251 1:0:5e:0:0:fb UHmLWI en0 255.255.255.255/32 link#8 UCS en0 ! Internet6: Destination Gateway Flags Netif Expire default fe80::2%en0 UGc en0 default fe80::%utun0 UGcI utun0 default fe80::%utun1 UGcI utun1 ::1 ::1 UHL lo0 fe80::%lo0/64 fe80::1%lo0 UcI lo0 fe80::1%lo0 link#1 UHLI lo0 fe80::%en0/64 link#8 UCI en0 fe80::2%en0 52:56:0:0:0:2 UHLWIir en0 fe80::cb9:425:579:f7ca%en0 52:54:0:9:49:17 UHLI lo0 fe80::%utun0/64 fe80::69e6:edfe:e4dc:ccb3%utun0 UcI utun0 fe80::69e6:edfe:e4dc:ccb3%utun0 link#9 UHLI lo0 fe80::%utun1/64 fe80::1b23:740d:d7d0:b07d%utun1 UcI utun1 fe80::1b23:740d:d7d0:b07d%utun1 link#10 UHLI lo0 fec0::/64 link#8 UC en0 fec0::2 52:56:0:0:0:2 UHLWI en0 fec0::cee:fd13:3be4:8411 52:54:0:9:49:17 UHL lo0 fec0::2d6d:1074:dc19:b28a 52:54:0:9:49:17 UHL lo0 ff01::%lo0/32 ::1 UmCI lo0 ff01::%en0/32 link#8 UmCI en0 ff01::%utun0/32 fe80::69e6:edfe:e4dc:ccb3%utun0 UmCI utun0 ff01::%utun1/32 fe80::1b23:740d:d7d0:b07d%utun1 UmCI utun1 ff02::%lo0/32 ::1 UmCI lo0 ff02::%en0/32 link#8 UmCI en0 ff02::%utun0/32 fe80::69e6:edfe:e4dc:ccb3%utun0 UmCI utun0 ff02::%utun1/32 fe80::1b23:740d:d7d0:b07d%utun1 UmCI utun1
Network interfaces
For newer Linux distributions:
- The following command shows that the current machine has three network interfaces. lo is a virtual network interface that corresponds to localhost (127.0.0.1); enps10 => corresponds to a ethernet network card, wbhich is not active; and finally wlp2s0 which is a wifi network card, which the IPv4 address on the local network is 10.0.0.175 and IPv6 address is fe81::88a:6b9c:fcef:4e32.
$ ip addr 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: enp1s0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc fq_codel state DOWN group default qlen 1000 link/ether 9a:8b:8c:f2:b3:d4 brd ff:ff:ff:ff:ff:ff 3: wlp2s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 9a:8c:8a:ef:ff:0c brd ff:ff:ff:ff:ff:ff inet 10.0.0.175/24 brd 10.0.0.255 scope global dynamic noprefixroute wlp2s0 valid_lft 84718sec preferred_lft 84718sec inet6 fe81::88a:6b9c:fcef:4e32/64 scope link noprefixroute valid_lft forever preferred_lft forever
For BSD, MacOSX and older Linux Distributions.
- Note: The ifconfig cli application is deprecated on most Linux distributions and was replaced by the ip command..
# On MacOSX VM $ ifconfig lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384 options=1203<RXCSUM,TXCSUM,TXSTATUS,SW_TIMESTAMP> inet 127.0.0.1 netmask 0xff000000 inet6 ::1 prefixlen 128 inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1 nd6 options=201<PERFORMNUD,DAD> gif0: flags=8010<POINTOPOINT,MULTICAST> mtu 1280 stf0: flags=0<> mtu 1280 UHC29: flags=0<> mtu 0 UHC93: flags=0<> mtu 0 EHC253: flags=0<> mtu 0 UHC61: flags=0<> mtu 0 en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500 options=424<VLAN_MTU,TSO4,CHANNEL_IO> ether 51:53:2a:f8:49:27 inet6 fe20::cc9:4a5:549:f7ca%en0 prefixlen 64 secured scopeid 0x8 inet6 fec0::cab:f513:8be4:9251 prefixlen 64 autoconf secured inet6 fec0::2c6a:1276:dc19:b29a prefixlen 64 autoconf temporary inet 10.0.2.20 netmask 0xffffff00 broadcast 10.0.2.255 nd6 options=201<PERFORMNUD,DAD> media: autoselect (1000baseT <full-duplex>) status: active utun0: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 1380 inet6 fe80::69f6:edfe:84dc:acb3%utun0 prefixlen 64 scopeid 0x9 nd6 options=201<PERFORMNUD,DAD> utun1: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 2000 inet6 fe80::1b2c:7424:d7d0:ca7d%utun1 prefixlen 64 scopeid 0xa nd6 options=201<PERFORMNUD,DAD>
Display all listening TCP sockets and open ports (socket servers)
$ >> netstat --listening -tnp (Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:9000 0.0.0.0:* LISTEN 142308/python tcp 0 0 192.168.100.1:53 0.0.0.0:* LISTEN - tcp 0 0 192.168.122.1:53 0.0.0.0:* LISTEN - tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN - tcp 0 0 0.0.0.0:9056 0.0.0.0:* LISTEN 434583/./poll-serve tcp6 0 0 :::9070 :::* LISTEN 131190/java tcp6 0 0 :::8080 :::* LISTEN 100425/java tcp6 0 0 ::1:631 :::* LISTEN - tcp6 0 0 :::9080 :::* LISTEN 100272/java
List all UDP connections
$ netstat --udp Active Internet connections (w/o servers) Proto Recv-Q Send-Q Local Address Foreign Address State udp 0 0 localhost.locald:bootpc _gateway:bootps ESTABLISHED
Display all Listening TCP/IP, UDP/IP and Unix Domain Sockets
$ >> netstat --listening -p (Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:cslistener 0.0.0.0:* LISTEN 142308/python tcp 0 0 localhost.locald:domain 0.0.0.0:* LISTEN - tcp 0 0 localhost.locald:domain 0.0.0.0:* LISTEN - ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... unix 2 [ ACC ] STREAM LISTENING 45857 1720/systemd /run/user/1000/systemd/private unix 2 [ ACC ] STREAM LISTENING 116650 - @/tmp/.X11-unix/X0 unix 2 [ ACC ] STREAM LISTENING 125379 7281/Xorg @/tmp/.X11-unix/X1 unix 2 [ ACC ] STREAM LISTENING 780071 7994/emacs /run/user/1000/emacs/server unix 2 [ ACC ] STREAM LISTENING 45863 1720/systemd /run/user/1000/bus unix 2 [ ACC ] STREAM LISTENING 45866 1720/systemd /run/user/1000/pipewire-0 unix 2 [ ACC ] STREAM LISTENING 45868 1720/systemd /run/user/1000/pulse/native unix 2 [ ACC ] STREAM LISTENING 2443571 388596/code --no-sa /run/user/1000/vscode-1d972a5c-1.45.1-main.sock unix 2 [ ACC ] STREAM LISTENING 77108 3932/tmux /tmp//tmux-1000/default unix 2 [ ACC ] STREAM LISTENING 3152439 456731/code /run/user/1000/vscode-git-be7a37b432.sock unix 2 [ ACC ] STREAM LISTENING 36623 - @/tmp/dbus-00sao3pj
Show ARP table
$ arp Address HWtype HWaddress Flags Mask Iface _gateway ether 21:fe:1d:78:d4:28 C wl_1s22f0u4 10.0.1.113 ether 7b:3f:24:57:2b:9c C wlp1s22f0u4 10.0.1.156 ether 80:92:26:87:ab:8f C
1.28.5 Simple TCP/IP client socket
Description:
- This sample code contains a simple TCP/IP client socket application that connects to a netcat running as server.
GIST Containing source:
File: client-sock1.cpp
#include <iostream> #include <cassert> // Use: assert() for marking placeholders for future error handling. #include <string> // std::string #include <cstring> // memcpy, memset and so on // ----- Unix/Linux headers -----// #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <limits.h> constexpr int OPERATION_FAILURE = -1; // Read buffer from file descriptor handling partial reads. // Note: It should only be used if the counterpart socket, sends // a buffer of equal to the size of this 'size' parameter. void read_buffer(int fd, void* buffer, size_t size); // Write buffer to file descriptor handling partial writes; void write_buffer(int fd, const void* buffer, size_t size); // Send text with '\n' UNIX (EOL) end line character. void write_line(int fd, std::string const& text); int main(int argc, char** argv) { // ======= Server port and hostname configuration ===========// // The port should be a 16 bits number // (Ports less than 1024 requires administrative/root privilege.) std::uint16_t port = 8095; // '127.0.0.1' for local host (LO Network interface) const char* hostname = "127.0.0.1"; // ======= Create socket file descriptor =====================// int sockfd = socket ( AF_INET // domain: IPv4 , SOCK_STREAM // type: TCP , 0 // protocol: (value 0) ); // Returns (-1) on failure assert( sockfd != OPERATION_FAILURE); // =========== Query Address (DNS query) =======================// // Note: the keyword 'struct' is not necessary here (redundant). struct hostent* h = gethostbyname(hostname); assert(h != nullptr); struct sockaddr_in sa; // memset(&sa, 0x00, sizeof(sa)); // set address std::memcpy(&sa.sin_addr.s_addr, h->h_addr, h->h_length); //bcopy( h->h_addr, &sa.sin_addr.s_addr, h->h_length ); sa.sin_family = AF_INET; // The function htons convert a number to big-endian format. sa.sin_port = htons(port); // ======== Attempt connection to server ========================// int conn_result = connect( // Socket file descriptor sockfd // Pointer sockaddr => Structure containing host address , reinterpret_cast<sockaddr*>(&sa) // Size of sockaddr buffer in bytes , sizeof(sockaddr_in)); if( conn_result == OPERATION_FAILURE ) { std::fprintf(stderr, " [ERROR] Unable to connect to server. \n"); exit(EXIT_FAILURE); } //============= Session Loop ===================================// // --- Send a buffer (untyped memory location) ----------// char start_message[500]; std::memset(start_message, '\0', 500); std::strcpy(start_message, " [CLIENT] <MESSAGE 1> Hi there, server, I am the client socket \n"); write_buffer( sockfd, start_message, strlen(start_message) ); // --- Send text --------------------------------------// write_line(sockfd, " [CLIENT] <MESSAGE 2> Hi there, server, I was sent by the client-side \n"); constexpr size_t BUFFER_SIZE = 500; char buffer[BUFFER_SIZE]; // ---------- Session Loop ------------------------------// for(;;) { // Clean buffer before each iteratiion std::memset(buffer, 0x00, BUFFER_SIZE); std::fprintf(stdout, " [TRACE] Waiting for server input. " " =>> Type something in the server netcat terminal and hit RETURN. \n"); ssize_t r = read(sockfd, buffer, BUFFER_SIZE); if(r == 0){ std::fprintf(stdout, " [TRACE] Server has closed the connection. " "User types CTRL + D in the server-side terminal. \n"); break; } if( r == -1){ std::fprintf(stdout, " [TRACE] An error has happend Check ERRNO. "); break; } std::fprintf(stdout, " [SERVER DATA] Server has sent: %s \n", buffer); // Send data back to server write_buffer(sockfd, buffer, BUFFER_SIZE); } // --- End of session loop ---------------------------// std::fprintf(stderr, " [INFO] Session shutdown OK \n"); close(sockfd); return 0; } // ------------------------------------------------------// // I M P L E M E N T A T I O N S // //-------------------------------------------------------// void read_buffer(int fd, void* buffer, size_t size) { // Pre-condition assert( size <= SSIZE_MAX ); size_t len = size; ssize_t nr = -1; // Note: std::byte (C++17) is a better replacement for char* or std::uint8_t // as std::byte is more effective for conveying the intent. auto pbuf = reinterpret_cast<std::byte*>(buffer); std::fprintf(stderr, " [TRACE] Waiting input \n" ); while( len != 0 && ( nr = read(fd, pbuf, len) ) != 0 ) { if(nr == -1 && errno == EINTR){ continue; } if(nr == -1) { throw std::runtime_error("An error has happened, check ERRNO "); break; } len = len - nr; pbuf = pbuf + nr; std::cerr << " [TRACE] len = " << len << " ; nr = " << nr << '\n'; } std::fprintf(stderr, " [TRACE] Loop finished. \n" ); } void write_buffer(int fd, const void* buffer, size_t size) { // Pre-condition assert( size <= SSIZE_MAX ); size_t len = size; ssize_t nr = -1; auto pbuf = reinterpret_cast<const std::uint8_t*>(buffer); while( len != 0 && ( nr = write(fd, pbuf, len) ) != 0 ) { if(nr == -1 && errno == EINTR){ continue; } if(nr == -1) { throw std::runtime_error("An error has happened, check ERRNO "); break; } len = len - nr; pbuf = pbuf + nr; } } void write_line(int fd, std::string const& text) { write_buffer(fd, text.data(), text.size()); char ch = '\n'; write(fd, &ch, 1); }
Building:
$ g++ sock-client1.cpp -std=c++1z -Wall -Wextra -g -O0
Run netcat as server in terminal 1 (server-side) listening port 8095:
$ >> nc -v -l 8095 Ncat: Version 7.80 ( https://nmap.org/ncat ) Ncat: Listening on :::8095 Ncat: Listening on 0.0.0.0:8095 Ncat: Connection from 127.0.0.1. Ncat: Connection from 127.0.0.1:33180. [CLIENT] <MESSAGE 1> Hi there, server, I am the client socket [CLIENT] <MESSAGE 2> Hi there, server, I was sent by the client-side Message to be sent to client-side Message to be sent to client-side Hello world client side Hello world client side I am the server - I provide network services such as tensor calculations I am the server - I provide network services such as tensor calculations I will type CTRL + D and shutdown the connection after this message I will type CTRL + D and shutdown the connection after this message
Run sock-client1.bin in terminal 2 connecting to port 8095 from local host:
$ ./sock-client1.bin [TRACE] Waiting for server input. =>> Type something in the server netcat terminal and hit RETURN. [SERVER DATA] Server has sent: Message to be sent to client-side [TRACE] Waiting for server input. =>> Type something in the server netcat terminal and hit RETURN. [SERVER DATA] Server has sent: Hello world client side [TRACE] Waiting for server input. =>> Type something in the server netcat terminal and hit RETURN. [SERVER DATA] Server has sent: I am the server - I provide network services such as tensor calculations [TRACE] Waiting for server input. =>> Type something in the server netcat terminal and hit RETURN. [SERVER DATA] Server has sent: I will type CTRL + D and shutdown the connection after this message [TRACE] Waiting for server input. =>> Type something in the server netcat terminal and hit RETURN. [TRACE] Server has closed the connection. User types CTRL + D in the server-side terminal. [INFO] Session shutdown OK
1.28.6 Simple TCP/IP socket echo server
Description:
- Sample code for a single-thread TCP/IP socket server listening at port 8095.
GIST:
File: socket-echo-server.cpp
// File: socket-echo-server.cpp // Desc: Simple socket server with a single thread. #include <iostream> #include <string> #include <vector> #include <cassert> #include <cstring> // memcpy // ----- Unix/Linux headers -----// #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> // ------------- MAIN() ------------------------------// int main(int argc, char** argv) { std::puts(" [INFO] Program started. "); // The port should be a 16 bits number (Note: binding to some low port numbers // requires administrative/root privilege.) std::uint16_t port = 8095; // The address can be: // '0.0.0.0' for listening all hosts // '127.0.0.1' for local host const char* hostname = "0.0.0.0"; // --- Create socket file descriptor -----------------------// int sockfd = socket ( AF_INET // domain: IPv4 , SOCK_STREAM // type: TCP , 0 // protocol: (value 0) ); // Returns (-1) on failure assert( sockfd != - 1); // ---------- query address ------------------------------// // Note: the keyword 'struct' is not necessary here (redundant). struct hostent* h = gethostbyname(hostname); assert(h != nullptr); struct sockaddr_in sa; // memset(&sa, 0x00, sizeof(sa)); // set address memcpy(&sa.sin_addr.s_addr, h->h_addr, h->h_length); //bcopy( h->h_addr, &sa.sin_addr.s_addr, h->h_length ); sa.sin_family = AF_INET; // The function htons convert a number to big-endian format. sa.sin_port = htons(port); // ----- Bind to Port and wait for client connections ---------// if( ::bind(sockfd, (sockaddr *) &sa, sizeof(sa)) == -1) { fprintf(stderr, "Error: unable to bind socket \n"); return EXIT_FAILURE; } // Enables binding to the same address int enable_reuseaddr = 1; if ( setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &enable_reuseaddr, sizeof(int)) < 0 ) { fprintf(stderr, "Error: unable to set socket option. \n"); return EXIT_FAILURE; } fprintf(stderr, " [TRACE] Listening client connection \n"); int backlog = 5; assert( listen(sockfd, backlog) != -1 ); // --------------- Server Main Loop --------------------------// // Infinite loop where client connections are handled while(true) { /* [[ ============== BEGIN client loop =========== ]] */ struct sockaddr_in client_sa; socklen_t addrlen = sizeof(sockaddr_in); fprintf(stderr, " [TRACE] Waiting for client connection. \n"); int sock_client = accept(sockfd, (sockaddr *) &client_sa, &addrlen ); if(sock_client == -1) { fprintf(stderr, " Error: failure to handle client socket. Check errno \n"); close(sock_client); continue; } const char* welcome_msg = "\n => [INFO] Hello world client side!! \n"; // Send buffer content to client socket send(sock_client, welcome_msg, strlen(welcome_msg), 0); char buffer[300]; while(true){ // Read message from client socket ssize_t n = recv(sock_client, &buffer, 300, 0); if(n == 0){ break; } std::fprintf(stdout, " [INFO] Received: "); ::write(STDOUT_FILENO, buffer, n); // Send content back send(sock_client, buffer, n, 0); // Compare 4 first bytes of buffer and 'quit' if( strncmp(buffer, "quit", 4) == 0) { fprintf(stderr, " [TRACE] Shutdown connection. Ok. \n"); // Close client connection shutdown(sock_client, SHUT_RDWR); close(sock_client); break; } } } /* [[ ============== END client loop =========== ]] */ // Release resource => RAII (Resource-Acquisition-Is-Initialization) fits here. close(sockfd); return 0; }
Building:
$ g++ socket-echo-server.cpp -o socket-echo-server.elf -std=c++1z -Wall -Wextra
Running server:
$ ./socket-echo-server.elf [INFO] Program started. [TRACE] Listening client connection [TRACE] Waiting for client connection. c++1z ada spark high performance c++2z c++20 C++17 c++14 C89 c90 ancient unix quit [TRACE] Shutdown connection. Ok. [TRACE] Waiting for client connection.
Running client: (netcat tool)
$ nc localhost 8095 => [INFO] Hello world client side!! c++1z ada spark high performance c++1z ada spark high performance c++2z c++20 C++17 c++14 C89 c90 ancient unix c++2z c++20 C++17 c++14 C89 c90 ancient unix quit quit ^C
1.28.7 Simple Unix-domain socket echo server
Unix domain-socket (not confuse with BSD socket) is an inter-process mechanism that allows usage of socket communication on local machine with less overhead than TCP/IP socket. A significat difference between IP sockets and Unix sockets is that unlike IP-based sockets, Unix domain sockets use file path or abstract namespace not bound to file system as address instead of
The most important usages of Unix-domain sockets are:
- Implementing network transparency.
- IPC - Inter Process Network Communication,
- Privilege separation (Security)
Known usage
- DBUS - Linux DBUS
- X11 - X Windows Systems uses Unix Domain Sockets locally for enabling network transparency. A code using Unix Domain socket needs does not need many changes for using IP-based sockets and operate over the network.
- Docker Container Engine - Docker uses Unix Domain Sockets for privilege separation and network transparency. The docker damenon, running with root (administrative) privilege and the docker client, running without administrative privilege, communicate via Unix domain sockets on local machine and or via TCP/IP sockets over the ĩnternt.
See also
- unix(7) - Linux manual page
- Unix domain socket - Wikipedia
- networking - What is the difference between Unix sockets and TCP/IP sockets? - Server Fault
Sample Code
GIST:
File: unix-domain-echo-server.cpp
// File: socket-echo-server.cpp // Desc: Simple socket server with a single thread. #include <iostream> #include <string> #include <vector> #include <cassert> #include <cstring> // memcpy // ----- Unix/Linux headers -----// #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> // ------------- MAIN() ------------------------------// int main(int argc, char** argv) { std::puts(" [INFO] Program started. "); const char* unix_sock_file = "/tmp/unix-socket-file"; // --- Create socket file descriptor -----------------------// int sockfd = socket ( AF_UNIX, SOCK_STREAM, 0); assert( sockfd != - 1); struct sockaddr_un sa; sa.sun_family = AF_UNIX; strcpy( sa.sun_path, unix_sock_file); // Remove file if it exists. unlink(unix_sock_file); // ----- Bind to unix domain socket and wait for client connections ---------// if( ::bind(sockfd, (sockaddr *) &sa, sizeof(sa)) == -1) { int err = errno; fprintf(stderr, "Error: unable to bind socket \n"); fprintf(stderr, " [ERROR] => %s", strerror(err)); return EXIT_FAILURE; } // Enables binding to the same address int enable_reuseaddr = 1; if ( setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &enable_reuseaddr, sizeof(int)) < 0 ) { fprintf(stderr, "Error: unable to set socket option. \n"); return EXIT_FAILURE; } fprintf(stderr, " [TRACE] Listening client connection \n"); int backlog = 5; assert( listen(sockfd, backlog) != -1 ); // --------------- Server Main Loop --------------------------// // Infinite loop where client connections are handled while(true) { /* [[ ============== BEGIN client loop =========== ]] */ struct sockaddr_un client_sa; socklen_t addrlen = sizeof(sockaddr_un); fprintf(stderr, " [TRACE] Waiting for client connection. \n"); int sock_client = accept(sockfd, (sockaddr *) &client_sa, &addrlen ); if(sock_client == -1) { fprintf(stderr, " Error: failure to handle client socket. Check errno \n"); close(sock_client); continue; } const char* welcome_msg = "\n => [INFO] Hello world client side!! \n"; // Send buffer content to client socket send(sock_client, welcome_msg, strlen(welcome_msg), 0); char buffer[300]; while(true){ // Read message from client socket ssize_t n = recv(sock_client, &buffer, 300, 0); if(n == 0){ break; } std::fprintf(stdout, " [INFO] Received: "); ::write(STDOUT_FILENO, buffer, n); // Send content back send(sock_client, buffer, n, 0); // Compare 4 first bytes of buffer and 'quit' if( strncmp(buffer, "quit", 4) == 0) { fprintf(stderr, " [TRACE] Shutdown connection. Ok. \n"); // Close client connection shutdown(sock_client, SHUT_RDWR); close(sock_client); break; } } } /* [[ ============== END client loop =========== ]] */ // Release resource => RAII (Resource-Acquisition-Is-Initialization) fits here. close(sockfd); return 0; }
Build:
$ g++ unix-domain-echo-server.cpp -o unix-domain-echo-server.bin -std=c++1z -Wall -Wextra -g
Run server:
$ ./unix-domain-echo-server.bin [INFO] Program started. [TRACE] Listening client connection [TRACE] Waiting for client connection. unix irix posix linux macosx kernel c++1z c++14 c++20 ADA spark ADA rust C++ Lisp quit [TRACE] Shutdown connection. Ok. [TRACE] Waiting for client connection.
Run client (netcat) :
$ nc -v -U /tmp/unix-socket-file Ncat: Version 7.80 ( https://nmap.org/ncat ) Ncat: Connected to /tmp/unix-socket-file. => [INFO] Hello world client side!! unix irix posix linux macosx kernel unix irix posix linux macosx kernel c++1z c++14 c++20 ADA spark ADA rust C++ Lisp c++1z c++14 c++20 ADA spark ADA rust C++ Lisp quit quit ^C
Check unix-socket file:
$ file /tmp/unix-socket-file
/tmp/unix-socket-file: socket
$ ls -l /tmp/unix-socket-file
srwxrwxr-x 1 mxpkf8 mxpkf8 0 Jul 2 01:22 /tmp/unix-socket-file=
1.28.8 Telnet-like application
The following code is a sample telent-like application, which provide remote access to interactive shells (REPLs read-eval-print-loop) from local machine, where the application is running. The application can work in server or client mode. When running in client mode, it attempts to connect to remote netcat server. When running in server mode, the application accepts connection from any netcat server. Interactive shells are applications such as: irb (Ruby shell); ash shell; TCL shell and so on.
File: rsh-server.cpp
// Desc: Simple telnet-like server. #include <iostream> #include <string> #include <vector> #include <cassert> #include <cstring> // memcpy // ----- Unix/Linux headers -----// #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <sys/types.h> #include <sys/wait.h> constexpr int UNIX_FAILURE = -1; // Create server socket and return its file descriptor. int create_server (const char* hostname, std::uint16_t port, int backlog); // Spawn a process and redirect its standard streams // to socket (stderr, stdin, stdout) void spawn_redirect (int sockfd, const char* executable); // Send text message to socket void send_msg (int sockfd, const char* message); // Connect to remote server and return client socket file descriptor. int socket_connect(const char* hostname, std::uint16_t port); // Handle session between server and client. void session_handler(int sockfd); // ------------- MAIN() ------------------------------// int main(int argc, char** argv) { // Placeholder for future error handling. assert( argc == 4 && " Requires at least three arguments. " ); int sockfd = -1; auto command = std::string{ argv[1] }; auto hostname = std::string{ argv[2] }; int port = std::stoi(argv[3]); // Run as client mode => Forwarding the shell to the server. if(command == "client") for(;;) { sockfd = socket_connect(hostname.c_str(), port); if( sockfd > 0 ) std::fprintf(stderr, " [OK ] Connected to server. \n"); else std::fprintf(stderr, " [ERROR] Conncection failure. Retry. \n"); session_handler(sockfd); sleep(2); } // ---- End of client command ---- // // Run in server mode => Liste connection. if(command == "server") { sockfd = create_server(hostname.c_str(), port, 5); assert( sockfd != -1 ); // ============= Server Event Loop =============// for(;;){ struct sockaddr_in client_sa; socklen_t addrlen = sizeof(sockaddr_in); fprintf(stderr, " [TRACE] Waiting for client connection. \n"); int sock_client = accept(sockfd, (sockaddr *) &client_sa, &addrlen ); if(sock_client == -1) { fprintf(stderr, " Error: failure to handle client socket. Check errno \n"); close(sock_client); continue; } session_handler(sock_client); } // --- End of for(;;) server loop ------// } // --- End of server command ---// // Release resource => RAII (Resource-Acquisition-Is-Initialization) fits here. close(sockfd); return 0; } // =============================================// // D E F I N I T I O N S // //==============================================// int create_server(const char* hostname, std::uint16_t port, int backlog) { // --- Create socket file descriptor -----------------------// int sockfd = socket ( AF_INET, SOCK_STREAM, 0); // Future error handling. // Returns (-1) on failure assert( sockfd != - 1); // ---------- query address ------------------------------// // Note: the keyword 'struct' is not necessary here (redundant). struct hostent* h = gethostbyname(hostname); assert(h != nullptr); struct sockaddr_in sa; memcpy(&sa.sin_addr.s_addr, h->h_addr, h->h_length); sa.sin_family = AF_INET; // The function htons convert a number to big-endian format. sa.sin_port = htons(port); // ----- Bind to Port and wait for client connections ---------// if( ::bind(sockfd, (sockaddr *) &sa, sizeof(sa)) == -1) { fprintf(stderr, "Error: unable to bind socket \n"); return EXIT_FAILURE; } // Enables binding to the same address int enable_reuseaddr = 1; if ( setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &enable_reuseaddr, sizeof(int)) < 0 ) { fprintf(stderr, "Error: unable to set socket option. \n"); return EXIT_FAILURE; } fprintf(stderr, " [TRACE] Listening client connection \n"); assert( listen(sockfd, backlog) != -1 ); return sockfd; } int socket_connect(const char* hostname, std::uint16_t port) { int sockfd = socket ( AF_INET, SOCK_STREAM, 0); // assert() => Placeholder for future error handling. assert( sockfd != UNIX_FAILURE ); // Query DNS struct hostent* h = gethostbyname(hostname); assert( h != nullptr ); struct sockaddr_in sa; std::memcpy(&sa.sin_addr.s_addr, h->h_addr, h->h_length); sa.sin_family = AF_INET; sa.sin_port = htons(port); int res = connect( sockfd , reinterpret_cast<sockaddr*>(&sa) , sizeof(sockaddr_in)); if( res == -1){ return -1; } return sockfd; } void send_msg(int sockfd, const char* message) { send(sockfd, message, strlen(message), 0); } void spawn_redirect(int sockfd, const char* executable) { pid_t pid = fork(); switch (pid) { case -1: std::fprintf(stderr, "Error: unable to fork process. "); exit(EXIT_FAILURE); break; // ----------- Child Process ------------// case 0: { std::fprintf( stderr, " [TRACE] Spawning process (PID = %d ) shell: %s \n" , ::getpid(), executable); // Redirect standard streams to socket file descriptor dup2(sockfd, STDOUT_FILENO); dup2(sockfd, STDIN_FILENO ); dup2(sockfd, STDERR_FILENO); const char* args [] = { "rshell" // Process name (arbitrary) , nullptr // Sentinel value - (always NULL pointer) }; // Spawn a new process. int status = execvp( executable, const_cast<char* const*>(args) ); if( status == UNIX_FAILURE ){ int err = errno; send_msg(sockfd, " [SERVER ERROR] Unable to locate REPL \n"); send_msg(sockfd, strerror(err)); close(sockfd); exit(EXIT_FAILURE); } } break; //----------- Parent Process ------------// default: // Wait for child process termination. ::waitpid(pid, nullptr, 0); break; } } void session_handler(int sockfd) { constexpr size_t SIZE = 500; char buffer[500]; // Always initialize buffer for avoiding undefined behavior. std::memset(buffer, 0x00, SIZE); send_msg(sockfd, "Enter the shell to be spawned: "); if( ::recv(sockfd, buffer, SIZE, 0) <= 0) { std::fprintf(stderr, "Connection error or peer closed connection \n"); return; } std::fprintf(stderr, " [TRACE] Waiting for child process termination. \n"); // Remove '\n' end line character if it is found. if( char* ptr_char = (char*) ::memchr(buffer, '\n', SIZE) ) { *ptr_char = '\0'; } // Fork a new process (run executable indicated by the buffer). spawn_redirect(sockfd, buffer); std::fprintf(stderr, " [TRACE] Child process terminated Ok. \n"); }
Running on MacOSX
Building on MacOSX Catalina:
$ g++ rsh-server.cpp -o rsh-server.osx -std=c++1z -Wall -Wextra -g $ file rsh-server.osx rsh-server.osx: Mach-O 64-bit executable x86_64 $ otool -L rsh-server.osx rsh-server.osx: /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 902.1.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1281.100.1)
Run terminal A => (Remote Linux Machine, netcat as server):
$ >> rlwrap netcat -l 9070 Enter the shell to be spawned: sh # User typed: $ whoami whoami unix # User typed: $ uname -a uname -a Darwin ghosts-iMac-Pro.local 19.5.0 Darwin Kernel Version 19.5.0: Tue May 26 20:41:44 PDT 2020; root:xnu-6153.121.2~2/RELEASE_X86_64 x86_64 # User typed: $ ls / ls / Applications Library System Users Volumes bin cores dev etc home opt private sbin tmp usr var # User typed: $ ls /Applications ls /Applications Safari.app Utilities iTerm.app # User typed : '$ osalang' for listing all supported # OSA (Open Scripting Architecture) languages. osalang AppleScript JavaScript Generic Scripting System # User typeed: # osascript -e .... ... osascript -e 'display dialog "My Dialog in OSA AppleScripting Automation!" ' Unable to create basic Accelerated OpenGL renderer. Unable to create basic Accelerated OpenGL renderer. Core Image is now using the software OpenGL renderer. This will be slow. button returned:OK # User typed: $ osascript -e .... osascript -e 'tell app "Finder" to make new Finder window' Finder window id 117 # User typed: $ osascript -e .... osascript -e 'display notification "Temperature 31 celsius." with title "Wheather report"' # Type Ctrl + C to shutdown server
Run terminal B => (MacOSX, run rsh-server in client mode):
./rsh-server.osx client 10.0.2.2 9070 [OK ] Connected to server. [TRACE] Waiting for child process termination. [TRACE] Spawning process (PID = 1569 ) shell: sh [TRACE] Child process terminated Ok. [ERROR] Conncection failure. Retry. Connection error or peer closed connection [ERROR] Conncection failure. Retry. Connection error or peer closed connection [ERROR] Conncection failure. Retry. Connection error or peer closed connection [OK ] Connected to server.
1.28.9 Telnet-like application with pseudo-terminal devices
The previous telnet-like application is not able to display colors or handle interactive applications which expect to be running in a terminal device, such as sudo, python and so on.
This rewrite of the previous code solves those shortcomings by using UNIX pseudo-terminal device pair, comprised of master and slave pseudo terminal devices. In this new code version, the slave pseudo terminal device is connected to the remote application's standard streams stdin, stdout and stederr. The master pseudo terminal device is connected to the socket. Any message received by the socket is forwarded to the master pseudo-terminal device, then the slave pseudo-terminal devices forwards this data to the process input. Any process output, stderr or stdout, are relayed to the slave pseudo terminal device, then the master pseudo terminal device forwards to the socket that sends the data over the network.
The pseudo terminal devices application programming interface from Unix-like systems is one of the most complex ones due to its long history and sparse documentation. This API for instance, has RS232 UART baud rate parameters that dates back to the mainframe era where terminals was a single unit comprised of a screen and a keyboard attached to a mainframe via a serial cable.
Concepts involved in the Terminal API
- UNIX signals
- TTY
- PTY - Pseudo terminal device
- Shell
- Interactive intepreter REPL (read-eval-print-loop), such as bash or python.
- Controlling terminal
- Terminal emulator (Example: Xterm, Gnome terminal)
- Child process
- Parent process
- Session leader (often the shell)
- Session - set of or one or more process groups.
- Process group (also called 'job')
- Foreground process
- Background process
- Pseudo-terminal modes:
- Canonical mode (default) => allso called non raw mode. Reads line, by line. Provides default command line history, echoing and allows moving the cursor forward.
- Non-canonical => also called raw mode => This mode does not provide, history, character echoing, cursor backward movement and so on. Used by Emacs and VI and other interactive terminal applications.
- Applications that cannot work without pseudo terminals:
- ssh
- telnet => Predecessor of ssh
- script => Records shell session to a file
- rlogin
- GNU screen
- Tmux => Terminal multiplexer
- sudo => For elevating user privilege.
File: rshell.cpp
// Desc: Simple telnet-like server using pseudo terminal devices. #include <iostream> #include <string> #include <vector> #include <cassert> #include <cstring> // memcpy // ----- Unix/Linux headers -----// #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <sys/types.h> #include <sys/wait.h> #include <poll.h> // ------ UNIX Pseudo Terminal Device API headers ---------// // #include <termios.h> #if defined(__linux__) // On Linux #include <pty.h> #else // On BSDs =>> MacOSX, FreeBSD, OpenBSD #include <util.h> #include <sys/ioctl.h> #endif constexpr int UNIX_FAILURE = -1; // Create server socket and return its file descriptor. int create_server (const char* hostname, std::uint16_t port, int backlog); // Spawn a process and redirect its standard streams // to socket (stderr, stdin, stdout) void spawn_redirect (int sockfd, const char* executable); // Send text message to socket void send_msg (int sockfd, const char* message); // Connect to remote server and return client socket file descriptor. int socket_connect(const char* hostname, std::uint16_t port); // Handle session between server and client. void session_handler(int sockfd, const char* executable); void set_raw_terminal(int pty_fd); // ------------- MAIN() ------------------------------// int main(int argc, char** argv) { if(argc != 4) { fprintf(stderr, " Usage: $ %s client <HOSTNAME> <PORT> \n", argv[0]); fprintf(stderr, " Usage: $ %s server <HOSTNAME> <PORT> \n", argv[0]); return EXIT_FAILURE; } int sockfd = -1; auto command = std::string{ argv[1] }; auto hostname = std::string{ argv[2] }; int port = std::stoi(argv[3]); const char* shell_executable = "bash"; // Run as client mode => Forwarding the shell to the server. if(command == "client") for(;;) { sockfd = socket_connect(hostname.c_str(), port); if( sockfd > 0 ) std::fprintf(stderr, " [OK ] Connected to server. \n"); else std::fprintf(stderr, " [ERROR] Conncection failure. Retry. \n"); session_handler(sockfd, shell_executable); sleep(2); } // ---- End of client command ---- // // Run in server mode => Liste connection. if(command == "server") { sockfd = create_server(hostname.c_str(), port, 5); assert( sockfd != -1 ); // ============= Server Event Loop =============// for(;;){ struct sockaddr_in client_sa; socklen_t addrlen = sizeof(sockaddr_in); fprintf(stderr, " [TRACE] Waiting for client connection. \n"); int sock_client = accept(sockfd, (sockaddr *) &client_sa, &addrlen ); if(sock_client == -1) { fprintf(stderr, " Error: failure to handle client socket. Check errno \n"); close(sock_client); continue; } session_handler(sock_client, shell_executable); } // --- End of for(;;) server loop ------// } // --- End of server command ---// // Release resource => RAII (Resource-Acquisition-Is-Initialization) fits here. close(sockfd); return 0; } // --- End of main() ----- // // =============================================// // D E F I N I T I O N S // //==============================================// int create_server(const char* hostname, std::uint16_t port, int backlog) { // --- Create socket file descriptor -----------------------// int sockfd = socket ( AF_INET, SOCK_STREAM, 0); // Future error handling. // Returns (-1) on failure assert( sockfd != - 1); // ---------- query address ------------------------------// // Note: the keyword 'struct' is not necessary here (redundant). struct hostent* h = gethostbyname(hostname); assert(h != nullptr); struct sockaddr_in sa; memcpy(&sa.sin_addr.s_addr, h->h_addr, h->h_length); sa.sin_family = AF_INET; // The function htons convert a number to big-endian format. sa.sin_port = htons(port); // ----- Bind to Port and wait for client connections ---------// if( ::bind(sockfd, (sockaddr *) &sa, sizeof(sa)) == -1) { fprintf(stderr, "Error: unable to bind socket \n"); return EXIT_FAILURE; } // Enables binding to the same address int enable_reuseaddr = 1; if ( setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &enable_reuseaddr, sizeof(int)) < 0 ) { fprintf(stderr, "Error: unable to set socket option. \n"); return EXIT_FAILURE; } fprintf(stderr, " [TRACE] Listening client connection \n"); assert( listen(sockfd, backlog) != -1 ); return sockfd; } int socket_connect(const char* hostname, std::uint16_t port) { int sockfd = socket ( AF_INET, SOCK_STREAM, 0); // assert() => Placeholder for future error handling. assert( sockfd != UNIX_FAILURE ); // Query DNS struct hostent* h = gethostbyname(hostname); assert( h != nullptr ); struct sockaddr_in sa; std::memcpy(&sa.sin_addr.s_addr, h->h_addr, h->h_length); sa.sin_family = AF_INET; sa.sin_port = htons(port); int res = connect( sockfd , reinterpret_cast<sockaddr*>(&sa) , sizeof(sockaddr_in)); if( res == -1){ return -1; } return sockfd; } void send_msg(int sockfd, const char* message) { send(sockfd, message, strlen(message), 0); } void session_handler(int sockfd, const char* executable) { char name[500]; memset(name, 0x00, 500); // File descriptor of master pseudo terminal (PTY) pair. // The master pseudo terminal is connected to the parent process. int pty_master = -1; // File descriptor of slave pseudo terminal (PTY) pair. // The slave pseudo terminal is connected to the child process (shell). // int pty_slave = -1; assert( openpty(&pty_master, &pty_slave, &name[0], nullptr, nullptr) != -1 ); fprintf(stderr, " [TRACE] Slave PTY fd = %d ; file = %s \n", pty_slave, ttyname(pty_slave)); fprintf(stderr, " [TRACE] Master PTY fd = %d ; file = %s \n", pty_master, ttyname(pty_master)); int flag = 1; // Note: ioctl() returns 0 on success. assert( ioctl(pty_master, TIOCPKT, &flag ) != -1); // assert( ioctl(pty_master, FIONBIO, &flag ) != -1); set_raw_terminal(pty_slave); struct winsize win; assert( ioctl(pty_master, TIOCGWINSZ, &win) != -1); fprintf(stderr, " [TRACE] Window size of cols = %d ; rows = %d \n", win.ws_col, win.ws_row); // Fork a child process pid_t pid = fork(); switch (pid) { case -1: std::fprintf(stderr, "Error: unable to fork process. "); exit(EXIT_FAILURE); break; // ----------- Child Process ------------// case 0: { std::fprintf( stderr, " [TRACE] Spawning process (PID = %d ) pty = %s ; shell: %s \n" , ::getpid(), name, executable); // Turn the current terminal into the session leader of the child process. assert( setsid() > 0); // Redirect process (stdin, stdout, stderr) to the pseudo terminal slave device (PTY). assert( dup2(pty_slave, STDOUT_FILENO) != -1); assert( dup2(pty_slave, STDIN_FILENO ) != -1); assert( dup2(pty_slave, STDERR_FILENO) != -1); const char* args [] = { executable // Process name (arbitrary) , nullptr // Sentinel value - (always NULL pointer) }; // Spawn a new process. int status = execvp( executable, const_cast<char* const*>(args) ); if( status == UNIX_FAILURE ){ int err = errno; send_msg(sockfd, " [SERVER ERROR] Unable to locate REPL \n"); send_msg(sockfd, strerror(err)); close(sockfd); exit(EXIT_FAILURE); } } break; //----------- Parent Process ------------// default: // Buffer size in bytes. constexpr size_t BUFFER_SIZE = 1024; char buffer[BUFFER_SIZE]; constexpr size_t N = 2; struct pollfd pollv[N]; // Register socket and pseudo-terminal master side. pollv[0].fd = sockfd; pollv[0].events = POLLIN; pollv[0].revents = 0; pollv[1].fd = pty_master; pollv[1].events = POLLIN; pollv[1].revents = 0; // --------- Connection session loop -----------------------------// // for(;;) { // Block current thread until any file descriptor becomes // ready for read operation. int nfds = ::poll( pollv, N, -1); if(nfds < 0){ fprintf(stderr, " [TRACE] An error has happened. Abort. "); std::terminate(); } // Event that happens when socket becomes ready (counterparty sends a message) if( pollv[0].revents & POLLIN ) { size_t n = read(sockfd, buffer, BUFFER_SIZE); if(n == 0){ fprintf(stderr, " [TRACE] Peer closed connection \n"); close(pty_master); break; } // Echoe command output to the server terminal write(STDOUT_FILENO, buffer, n); // Write socket remote input (stdin) to the master pseudo-terminal device (PTY) assert( write(pty_master, buffer, n) > 0); } // Event that happens when the process connected to the slave-end of pseudo // terminal (PTY) sends a message. if( pollv[1].revents & POLLIN ) { size_t nn = read(pty_master, buffer, BUFFER_SIZE); assert( nn > 0 ); // Relay process output from master pseudo terminal to the socket file descriptor. assert( write(sockfd, buffer, nn) > 0); } } // --- End of session loop ------// break; } } // Set pseudo terminal device in raw mode (not cooked mode, default) // The algorithm was extracted from the following Python code: // =>> https://github.com/python/cpython/blob/711381dfb09fbd434cc3b404656f7fd306161a64/Lib/tty.py#L18 void set_raw_terminal(int pty_fd) { // Saved terminal settings struct termios tty_old; // Current terminal settings struct termios tty_new; // Save the default parameters of the slave side of the PTY int rc = tcgetattr(pty_fd, &tty_old); assert( rc != -1 ); tty_new = tty_old; cfmakeraw (&tty_new); tty_new.c_iflag = tty_new.c_iflag & ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON) ; tty_new.c_oflag = tty_new.c_oflag & ~(OPOST); tty_new.c_cflag = tty_new.c_cflag & ~(CSIZE | PARENB); tty_new.c_cflag = tty_new.c_cflag | CS8; tty_new.c_lflag = tty_new.c_lflag & ~(ECHO | ICANON | IEXTEN | ISIG); tty_new.c_cc[VMIN] = 1; tty_new.c_cc[VTIME] = 0; tcsetattr (pty_fd, TCSANOW, &tty_new); // tcsetattr (pty_slave, TCSANOW, &tty_new); }
Building
This application is able to be build on Linux, BSD-variants or MacOSX. In this case it was compiled on a MacOSX VM:
$ clang++ rshell.cpp -o rshell-osx.bin -std=c++1z -Wall -Wextra $ file rshell-osx.bin rshell.bin: Mach-O 64-bit executable x86_64 $ otool -L rshell-osx.bin rshell.bin: /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 902.1.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1281.100.1)
Running
Terminal 1: Run netcat as server in a Linux machine:
$ nc -l 9056 The default interactive shell is now zsh. To update your account to use zsh, please run `chsh -s /bin/zsh`. For more details, please visit https://support.apple.com/kb/HT208050. bash-3.2$ bash-3.2$ uname -a Darwin ghosts-iMac-Pro.local 19.5.0 Darwin Kernel Version 19.5.0: ... ... bash-3.2$ pwd /Volumes/data/macosx bash-3.2$ ls / Applications Volumes etc sbin Library bin home tmp System cores opt usr Users dev private var bash-3.2$ bash-3.2$ osascript -l JavaScript -i >> ObjC.import("Foundation") ObjC.import("Foundation") => undefined >> ObjC.import("Cocoa") ObjC.import("Cocoa") => undefined >> >> console.log("Hello world - Hola mundo - Ola mundo") console.log("Hello world - Hola mundo - Ola mundo") Hello world - Hola mundo - Ola mundo => undefined >> >> $.NSLog("Mac OSX Objective C is awesome => GNUSTep") $.NSLog("Mac OSX Objective C is awesome => GNUSTep") 2020-12-22 14:19:01.001 osascript[8194:730902] Mac OSX Objective C is awesome => GNUSTep >> z = $.NSNumber.numberWithFloat(10.2323) z = $.NSNumber.numberWithFloat(10.2323) => $(10.2322998046875) >> $.NSLog(" z = %@", z) $.NSLog(" z = %@", z) 2020-12-22 14:20:47.164 osascript[8194:730902] z = 10.2323 >> alert = $.NSAlert.alloc.init alert = $.NSAlert.alloc.init => [id NSAlert] >> alert.messageText = "Hello world Alert button" alert.messageText = "Hello world Alert button" => "Hello world Alert button" >> alert.runModal(); $.NSLog(" Finished") alert.runModal(); $.NSLog(" Finished")
Terminal 2: Run rshell as client in a MacOSX VM:
$ ./rshell-osx.bin client 10.0.2.2 9056 [OK ] Connected to server. [TRACE] Slave PTY fd = 5 ; file = /dev/ttys021 [TRACE] Master PTY fd = 4 ; file = (null) [TRACE] Window size of cols = 0 ; rows = 0 [TRACE] Spawning process (PID = 8191 ) pty = /dev/ttys021 ; shell: bash ... .. ... .. ... .. ... .. ... .. ... .. ... .. ... .. ... .. ... .. ... .. ... .. ... ..
1.29 Sockets with SSL/TSL - Secure Communication
1.29.1 Overview
Many widely network protocols, such as HTTP, FTP, Telnet or SMTP, were designed without any security considerations, without any type of encryption which makes the data transmitted by those protocols vulnerable to eavesdropping; tampering, the data can be changed by third party during the transmission; spoofing (impersonation); MIT (Man-In-The Middle attack) a third-party actor can impersonate both sides and modify the data sent over the connection; and integrity violation.
The TLS (Transport Layer Security) and its predecessor SSL (Secure Socket Layer) are the bed rocks of internet security, they are network protocol stacks designed for encapsulating the TCP protocol and solve its major security shortcomings by providing confidentiality through encryption, a third party will only be able to view obfuscated non-readable binary data (blobs); non-repudiation and authentication through certificates and public key infrastructure (PKI); data integrity as property of encryption, both sides of communication channel are able to detect any data tampering.
The TSL protcol stack is comprised of basically two parts, the TLS _handshake protocol, that takes care of authentication, certificate verification and shared key generation and exchange; and the TLS record protocol, that takes care of encapsulating the TCP protocol and providing encrypted communication. When the connection is opened, the TSL hanshaking takes place and the following sequence of events happen: both sides negotiate the TLS version; decides the ciphers that will be used; the client checks server certificate by using a trusted certificate authority; the server may also check the client certificate, but this feature is semdom used; generate a shared encryption key (symmetric encryption) for exchanging encryoted data. After the TLS handshake, the TLS record protocol is used for encapsulating TCP-based protocols, such as HTTP, FTP or SMTP by providing data encryption by using the generated shared (symmetric) key, which is unique for every connection.
Remarks
- TLS and SSL are essential for internet security. Without it any third party agent, including ISP (Internet Service Providers), would be able to intercept and view the communication between the web browser and web servers.
- Despite being widely used for providing encryption for the http
protocol (
https:/www.site.com
URLs, instead ofhttp://www.site.com
), the TLS protocol can also encapsulate any other TCP-based network protocol. - APIs based on OpenSSL or other implementation of the TLS protocol stack provide encapsulation and transparent encryption and decryption for TCP-based protocols where the calling code does not need to care about encryption details.
- The private key should never be disclosed, otherwise any third party agent, that obtained the key, will be able to intercept and decrypt the communication.
- Always keep openSSL library updated with the latest security fixes. This library is a fundamental piece of internet security. Any OpenSSLL vulnerability will make anything that depends on this library vulnerable.
- Avoiding creating custom or proprietary encryption as cryptography is business critical and requires deeper knowledge of number theory, random numbers, discrete mathematics and abstract algebra.
Some network protocols that relies on TLS/SSL
- HTTPS (HTTP + SSL/TLS), port 443 => Http protocol encapsulated with TLS/SSL. Web sites with https URL, for instance https://www.httpbin.org, are using TLS.
- FTPS (FTP + SSL/TLS) => FTP encapsulated with TLS/SSL
- OpenVPN
- SSTP (Secure Socket Tunneling Protocol)
- DoH (DNS over TLS)
Some Implementation of TLS and SSL Protocol Stack
- OpenSSL
- Reference-implementation and first implementation of the SSL and TLS network protocol stack. In addition to the shared library provided by OpenSSL suite, it also provide command line tools for creating and validating certificates; creating self-signed certificates and check connection certificate; creating digital signature and so on.
- Note: OpenSSL is cross-platform and can be used on all platforms, including Windows.
- Note: In many cases, web applications use OpenSSL indirectly through Nginx reverse proxy where Nginx is used for providing TLS security.
- GNU TLS
- License: LGPL
- GNU Implementation of TLS protocol stack.
- LibreSSL
- Fork of OpenSSL created by OpenBSD developers.
- BoringSSL
- Google's implementation of TLS/SSL.
- Mbed TLS (Embedded Systems)
- Lightweight implementation derived from Mbed operating system for embedded systems.
- Repository: GitHub - ARMmbed/mbedtls
- wolfSSL (Embedded Systems)
- License: Dual license, GPL3 and commercial for closed source applications.
- Highly secure implementation, designed for embedded systems. Unlike other libraries, WolfSSL has certification for meeting regulatory requirements.
- Repository: https://github.com/wolfSSL/wolfssl
- Botan
- License: BSD
- Repository: https://github.com/randombit/botan
- "Botan's goal is to be the best option for cryptography in C++ by offering the tools necessary to implement a range of practical systems, such as TLS protocol, X.509 certificates, modern AEAD ciphers, PKCS#11 and TPM hardware support, password hashing, and post quantum crypto schemes. A Python binding is included, and several other language bindings are available. It is used in many open source and commercial products. The library is accompanied by a featureful command line interface."
- Rust TLS
- TLS protocol stack implemented in Rust that conforms to OpenSSL API.
- BearSSL - Main
- "BearSSL is an implementation of the SSL/TLS protocol (RFC 5246) written in C. It aims at offering the following features: Be correct and secure. In particular, insecure protocol versions and choices of algorithms are not supported, by design; cryptographic algorithm implementations are constant-time by default. Be small, both in RAM and code footprint. For instance, a minimal server implementation may fit in about 20 kilobytes of compiled code and 25 kilobytes of RAM. Be highly portable. BearSSL targets not only “big” operating systems like Linux and Windows, but also small embedded systems and even special contexts like bootstrap code. Be feature-rich and extensible. SSL/TLS has many defined cipher suites and extensions; BearSSL should implement most of them, and allow extra algorithm implementations to be added afterwards, possibly from third parties."
- Schannel [WINDOWS-ONLY]
- Microsft Windows-only implementation of SSL and TSL protocol stack.
Useful Terminology and Concepts
- IETF - Internet Engineering Task Force
- RFC - Request For Comment
- SSL - Socket Secure Layer (predecessor of TLS, first used by Netscape Web Browser)
- TLS - Transport Layer Security (over TCP) - provides security for TCP based protocols, such as HTTP, FTP, SMTP and so on.
- DTLS - Datagram Transport Layer Security (over UDP) - provides security for UDP based protocols.
- PKI - Public Key Infrastructure
- Non-Repudiation (Used for digital signature and data integrity)
- If the message recipient has the data received from the sender, the data hash created by the sender's private key, the sender's public key and the receiver is really sure about the public key ownership validated by a CA - Certificate Authority. The sender, owner of the private key, cannot deny that he or she really sent the message and cannot deny anything about the message content.
- Shared-Key Cryptography (a.k.a Symmetric Key Cryptography)
- The same key is used for data encryption and decryption. The usage of shared key cryptography is unsafe for communication over an unsafe channels as the encryption keys need to be send over an unsafe channel before the encrypted communication takes place.
- Applications and use-cases: bulk data encryption for hiding and obfuscating data from unauthorized access; password file encryption; file system encryption; disk encryption; encrypted backup and private key encryption. Shared key cryptography can be used for encrypted communication. However, before the encrypted communication takes place, the key may be transmitted in an unencrypted channel which makes the key vulnerable to interception.
- Public-Key Cryptography (a.k.a Asymmetric key cryptography)
- Unlike the shared key cryptography, where the same key is used for data encryption and decryption. The public-key cryptography uses the public key for encryption and private key for decryption. The publick-key cryptography allows communication between two agents without them exchanging their encryption keys (private keys), they only need the public key from each other. Given two agents Alice and Bob, if Alice wants to send a encryted message to Bob, she encrypts the message with Bob's public key and he will be able to decrypt the encrypted data using his private key, which only he owns and knows. If Bob wants to send an encryted message, he encrypts the message with Alice's public key and Bob will be able to decrypt the message using his private key. Although, the public-key cyrptography is safer than shared-key cryptogaphy, public-key algorithms are more computationally expensive than shared key algorithms. Public key cryptography is not enough for secure communication. A third party agent can still impersonate Bob and Alice by sending a bogus Alice's public key to Bob and a bogus Bob's public key to Alice. Now the agent can intercept and decrypt the communication between both sides. This situation is called MITM (Man-In-The-Middle) attack. The MITM could be avoided if Bob was sure that Alice's public really belongs to her and Alice was sure that Bob's public key really belongs to him. What gives the assurance about the public key ownership and identity bound to the key is the PKI public-key infrastructure or the CAs (Certificate Authorities).
- Applications and use-cases: secure communication over unsafe channels without disclosing private keys; digital signature for legal documents and contracts; data integrity detection, any data modification can be detected by both sides; authentication using the private key, if the server has the client's public key; blockchain is based on public-key cryptography; cryptocurrencies such as Bitcon or Litecoin are also based on public key cryptography; TLS and SSL uses public-key cryptography for exchanging the shared key without disclosing it.
- X.509 Certificates - The certificate encapsulates is standardized format by RFC 5280 that encapsulates the identification metadata, server or client public key and digital signatures from certificate authorities for validating the identification linked to the certificate. In order to a TLS communication to be established, at least one side needs a TLS certificate, which is in most of the cases only the server. However, client-isde certificate can also be used for client authentication.
- CHF - Crytography Hash Function => Cryptography hash are
deterministic, non-reversible and one-way functions that uniquely
map any amount of data, taken as input, to a fixed-width output,
named hash value, message-digest or checksum. Some of those
algorithms are: MD5, SHA-1, SHA-2, SHA-3, SHA-256, Argon-2,
PBKDF2, Bcrypt and so on. Some applications of hash functions are:
- Uniquely identifying a file: The computed hash value is unique for each file, as a result it uniquely identifies a file. For instance, when comparing two files at different machines, it is faster to compare their hash values rather than downloading the counterpart file and checking the equality of all bytes.
- File integrity checking: given a file and its previously computed hash, an user can verify if the file is not corrupted or was not tampered by comparing the previously computed hash value and the current file hash value. If the computed value and the already known hash matches, then the file was not currupted and not tampered.
- Data Checksum: if a data is transmistted with its checksum (hash value), it is possible to detect any data change or corruption by computing the received data hash value (checksum) and comparing with the received hash value.
- Secure password storage for web applications. If only the hash value of user passwords are storead and the database is leaked, whoever accesses the hashes will not be able to reuse it on other websites.
- Digital Signature
- The digital signature of a file or data is produced by generating the hash of this file or data through some hash algorithm and encryting the hash with the sender's private key. Then, the data and its digital signature are sent to the recipient which can check the message data integrity and that the messsage was relly sent by whom the recipient claims to be. The recipient can verify the message by peforming the following process: first decrypt the digital signature with the sender's public key obtaining the message hash; finally the it is compared with the message hash computed by the recipient. If the expected hash and computed hash values match, then the message was really sent by the who the sender claims to be.
- Most cryptography applications, including PGP, GPUPG, TLS software suits already check the public key automatically.
- While digital signature ensures data integrity, the non-repudiation assurance which is only possible if the recipient is really sure that public key really belongs to the sender.
- The recipient can sure about the public key ownership if the following conditions are met: he or she has the sender's public key; the public key is signed by a certificate authority that is trusted by both sides; and the recipient has the public key of the certifcate authority and he or she is really sure about the authenticity of the certificate authority public key.
- CA - Certificate Authority (PKI - Public Key Infrastructure)
- Provides non-repudiation, it allows the receiver of the message, for instance a Web browser, to be sure about sender, which could be a the http server, identity and the sender is really the intended communication counterparty. The CA chain also solves the MITM problem. Web browsers use a built-in set of certificate authorities trusted by the browser vendor, which are used for validating certificates from web sites (http servers).
- SRP - Secure Remote Password
- CRL - Certificate Revocation List
- RSA - Rivest–Shamir–Adleman public-key algorithm.
- ECC - Elliptic Curve Cryptography
- ECDSA - Elliptic Curve Digital Signature Algorithm
- MAC - Message Authentication Code
- HMAC - Keyed-Hashing for Message Authentication
- DES - Data Encryption Standard
- BER - Basic Enconding Rules (Related to X509 Certificates)
- DER - Distinguished Encoding Rules (Related to X509 Certificates)
- PEM - Privacy Enhanced Mail
- AES - Advanced Encryption Standard
- CBC - Cipher Block Chaining Mode
- OFB - Output FeedBack Mode
- ECB - Electronic Code Book Mode
Public Key Cryptography Worflow for secure communication
Message from Bob to Alice Bob side Alice side ................................. .......................................... . . Unencrypted +-------------+ . . +--------------+ Unencrypted (plain text) | Alice's | . Encrypted data . | Alice's | data (plain text) -------->>>-------->+ Public +--------->>>--------->>>-----.---->>+ Private key +--------->>> | Key | . (channel) . | | +-------------+ . . +--------------+ | . . | Bob encrypts the . . Alice decrypts the message to Alice . . message using her using her public key. . . private key Message from Alice to Bob Bob side Alice side ................................. .......................................... . . Unencrypted +-------------+ . . +--------------+ Unencrypted (plain text) | Bob's | . Encrypted data . | Bob's | data (plain text) ---<<----------<<---+ private +---------<<---------<<<-----.--<<--+ public key +--------<<<---- | Key | . . | | +-------------+ . . +--------------+ | . . | Bob decrypts the . . Alice encrypts the message using his . . message using Bob's private key. . . public key.
SSL/TLS Workflow for secure communication
Client-Side Server Side | | | (1) Intiates the communication | +-------------->>>>-------------------------------+ | | | (2) Check server certificate and its | | digital signatures signed by | | certificate authorities. | | Performs negotiation (TLS handshake) | +-----<<<-----------------<<<---------------------+ | | ` | | | (3) Generate session key (shared key) | | and sends it to server | | | | +------------+ Encrypted +------------+ Decrypted | | Sever's | session key (shared key) | Sever's | Session key +---> PUB. KEY +-------->>>--------------->+ PRIV. KEY +-------->> | | | | | | +------------+ +------------+ | | | | | Message from client to server | | is encrypted with the | | session key | +--------------->>>>---------------------------->>+ | | | Message from server to client | | is encrypted with the | | session key. | +<<<----------<<<-------------<<------------------+ | | | | | |
Digital Signature Workflow
Use-cases:
- Ensuring that message (plain text, data or file) sent was not tampered (data integrity) and that it really was authored by the holder of the private key corresponding to the public key on the recipient-side.
- Digital signature of legal documents for providing non-repudiation assurance and guarantee their authenticity.
- Trusted execution or app notarization: Only applications with digital signature matching public keys (built-in) trusted by the system vendor, are allowed to run. Examples: MacOSX app notarization; Android notarization; digital signature of Windows executable; UEFI firmware and so etc.
- Password-less login. Assuming that the server-side has the public key of the client-side. The server sends a challenge to the client. The challenge can be any random data. Then the client sends the data digital signature to the server, which checks whether it matches the data and the public key. If that is the case, then the user is authenticated and is allowed to sign in. If the digital signature fails, the user authentication fails.
- Signature of TLS/SSL certificates by CA - certificate authorities.
Bobs sends a message to its digital signature to Alice. Data, plain text or file authored by Bob. | +------<<--------+----->>--------+ │ │ │ │ ▼ │ │ │ ┌──────▼───────┐ │ │ Hash │ │ │ Function │ │ │ │ │ └──────┬───────┘ │ │ │ │ │ ▼ Data hash │ │ │ │ │ ┌──────▼───────┐ │ │ Bob's private│ │ │ key │ │ │ │ │ └─────┬────────┘ │ (message) │ digital signature │ Unencrypted │ │ data │ Encrypted │ │ data hash │ │ ▼ │ │ ▼ Bob side (sender) │ --------------│---------------------------------│--------- │ Insecure channel │ --------------│---------------------------------│---------- │ │ │ Alice side │ │ (Recipient) │ ┌─────▼─────────┐ ┌─────▼──────────┐ │ Bob's public │ │ │ │ key │ │ Hash Function │ │ │ │ │ │ │ │ │ └─────┬─────────┘ └─────┬──────────┘ │ │ │Data hash (A) │ Data hash │ │ computed by Alice (B) │ │ │ │ │ │ └────>>>>────[ = ]───<<<──────────┘ | | Compare hashes extracted from | digital signature (A) | and the hash computed by Alice (B). If both hashes are equal, then Alice can be sure that the data was really authored by Bob and it was not tamperd.
Passwordless Challenge-Response Authentication with Public-Key Cryptography
- Authentication refers to checking the user identity and only allowing access if the identity provided matches the user account. Although the implementation of password authentication method is simple, this authentication method has several shortcomings. Shorter passwords might be easy to guess and longer password are easy to forget. Adversaries could attempt to impersonate users by taking advantage of leaked credentials from web sites that fail properly store them as the same password may be used on many other web sites. Another drawback is that users can be tricked into disclosing them. A safer alternative to this authentication method is the challenge-response authentication with public-key. In this authentication method, the server has the user account's public key and when an authentication attempt happens, the server sends a challenge, that may be random generated data to the client-side which must send a valid digital signature of the data to the server. If the signature matches the data and the public key, access is granted, otherwise access is denied and the authentication attempt fails. The benefits of this approach over the password-based authentication are that: the user does not need to create and remember another unique password for every new subscription or account; no private data is sent to the server-side; there is no way to trick users into providing the password as no shared secret is ever sent over the network; replay attacks are not possible since the digital signature is unique on every authentication attempt. The disadvantage of this technique is that user may lose access if the private key is lost or someone else obtains the private key. The security of this technique can be improved even further if the private key is stored in a external hardware capable of generating digital signature of input data. The private key can also be encrypted using a shared key cryptography with passphrase or pin as the shared key.
- Note: The difference between password and passphrase or pin is that passphrase and pin are never sent to the server-side.
- Another variant of challenge-response authentication process, which is used by ssh, is the following workflow: 1 - the server has the account public key; 2 - the server generates a random data, encrypts it with the public key and sends the encrypted data to the client-side; 3 - in order to authenticate, the client-side must decrypt the data and send it back; 4 - the server grants access if the decrypted data sent by the client-side is identical to the generated data.
- Examples:
- TLS/SSL client-side certificate authentication (also called mTLS or mutual TLS).
- SSH authentication with public key.
- FIDO alliance authentication standard that proposes usage of hardware with NFC or USB connectivity password-less authentication. The hardware generates digital signature of input data without ever leaking any private key.
- WebAuthn (W3C consortium) standard for web authentication using public key.
- See:
Workflow:
Client-side Server-side (has private key) (has public key or certificate) | | | (1) - Attempts to autenticate (login) | | provide username or account name | +----------------->>>------------------------->>>+ | | | (2) Server sends challenge (random number) | | unique for every auth. attempt | |----<<-----------<<<----------------------------+ | | \ / \ / | (3) Signs the data and sends and | | sends the digital signature to server | +---------->>>-------------------->>----------->>+ | | | (4) If the digital signature matches | | the public key and plain-text data, | | access is granted. | +---<<-------------------------------------------+ | | \ / \ /
1.29.2 Further Reading
Technical standards and specifications:
- RFC 2246 - The TLS Protocol Version 1.0
- "This document specifies Version 1.0 of the Transport Layer Security (TLS) protocol. The TLS protocol provides communications privacy over the Internet. The protocol allows client/server applications to communicate in a way that is designed to prevent eavesdropping, tampering, or message forgery."
- RFC 4346 - The Transport Layer Security (TLS) Protocol Version 1.1
- RFC 8446 - The Transport Layer Security (TLS) Protocol Version 1.3
- RFC 4680 - TLS Handshake Message for Supplemental Data
- RFC 4681 - TLS User Mapping Extension
- RFC 5746 - Transport Layer Security (TLS) Renegotiation Indication Extension
- RFC 6476 - Using Message Authentication Code (MAC) Encryption in the Cryptographic Message Syntax (CMS)
- RFC 5280 - Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile
- RFC 8484 - DNS Queries over HTTPS (DoH)
SSL/TSL Concepts:
- INFOSEC - Information security
- X.509 Certificate
- Online Certificate Status Protocol
- OpenSSL - Reference implementation of TSL and SSL protocols.
- TLS 1.3 is finally published by the IETF as RFC 8446 - Hashed Out
- Comparison of TLS implementations
- What is SSL (Secure Sockets Layer)? | Cloudflare
- What is SSL? - SSL.com
- cryptography - MAC vs Encryption - Information Security Stack Exchange
- cryptography - Lessons learned and misconceptions regarding encryption and cryptology
- What is message authentication code (MAC)? - Definition from WhatIs.com
- What Happens in a TLS Handshake? | SSL Handshake | Cloudflare
Cryptography Concepts:
- Public-key cryptography
- Symmetric-key algorithm
- Web of trust
- PKI - Public key infrastructure
- TLS Security 3: SSL/TLS Terminology and Basics | Acunetix
- Elliptic Curve Digital Signature Algorithm - Wikipedia
- Elliptic Curve Digital Signature Algorithm - Bitcoin Wiki
- ECDSA: The digital signature algorithm of a better internet
- A (Relatively Easy To Understand) Primer on Elliptic Curve Cryptography
Digital Signature:
- Crytography - Hash Functions & Digital Signatures
- Opensource.com - How to use OpenSLL: Hahses, digital signature and more.
- IBM - Digital Signatures.
- Digital Signature - Why you should sign everything
- GNUPG - Digital Signature
Challenge-Reponse authentication with public key:
- Is there a difference between asking for a signature and asking for …
- A Challenge-Response Protocol with Digital Signatures
- Industrial Security Part 6: THe Challenge-Response-Concept
- Understanding How FIDO Makes Passwordless Authentication Possible
- 10 Things You’ve Been Wondering About FIDO2, WebAuthn, and a Passwordless World
- An In-depth Guide to FIDO Protocols: U2F, UAF, and WebAuthn (FIDO2)
- FIDO U2F JavaScript API - FIDO Alliance Proposed Standard 11 April 2017
- Why does SSH authentication protocol send a challenge message instead of using digital signature to verify client
- Client-side authentication (TLS/SSL client certificate auth)
- Client Certificate vs Server Certificate – the Ultimate Difference (TLS/SSL client certificate auth)
- Introducing mutual TLS authentication for Amazon API Gateway
- (W3C Consortium) Web Authentication: An API for accessing Public Key Credentials -Level 2
- Introduction to Web Authentication: The New W3C Spec
- https://webauthn.guide/ => Provides strong and compelling motivation for the Webauthn standard for web autentication.
- https://webauthn.io/ => Demove of WebAuthn specification.
- Apple WWDC conference - Move beyond passwords
- "Despite their prevalence, passwords inherently come with challenges that make them poorly suited to securing someone's online accounts. Learn more about the challenges passwords pose to modern security and how to move beyond them. Explore the next frontier in account security with secure-by-design, public-key-based credentials that use the Web Authentication standard. Discover in this technology preview how Apple is approaching this standard in iOS 15 and macOS Monterey."
OpenSSL API
- SSL/TLS Client - OpenSSLWiki
- Simple TLS Server - OpenSSLWiki
- ssl(3) API Manpage
- Ubuntu Manpage: ssl - OpenSSL SSL/TLS library
- USENIX - APISan: Sanitizing API Usages through Semantic Cross-checking
- Securing Sockets with OpenSSL (Part 1) | Differentiating the Authorities | InformIT
- Securing Sockets with OpenSSL (Part 2) | Writing an HTTPS Server | InformIT
- /docs/man1.0.2/man3/SSL_CTX_load_verify_locations.html
- GeekCabinet – Using SSL Connections Over ncat
DoH - DNS over HTTPS (DNS over TLS)
1.29.3 Open SSL commands
View certificate of some sever, given its hostname
$ >> openssl s_client -showcerts -connect www.httpbin.org:443 < /dev/null CONNECTED(00000003) depth=2 C = US, O = Amazon, CN = Amazon Root CA 1 verify return:1 depth=1 C = US, O = Amazon, OU = Server CA 1B, CN = Amazon verify return:1 depth=0 CN = httpbin.org verify return:1 --- Certificate chain 0 s:CN = httpbin.org i:C = US, O = Amazon, OU = Server CA 1B, CN = Amazon -----BEGIN CERTIFICATE----- MIIFbjCCBFagAwIBAgIQC6tW9S/J9yHIw1v8WOnMPDANBgkqhkiG9w0BAQsFADBG MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRUwEwYDVQQLEwxTZXJ2ZXIg Q0EgMUIxDzANBgNVBAMTBkFtYXpvbjAeFw0yMDAxMTgwMDAwMDBaFw0yMTAyMTgx MjAwMDBaMBYxFDASBgNVBAMTC2h0dHBiaW4ub3JnMIIBIjANBgkqhkiG9w0BAQEF .... ... ... ........ qPGwissH0W8HgYWhKVISkN2ui55RgbHXQHYDX0uYgGK6iRMxHHlOR1vOqRgEvVGF 5g8CcK3EzritKaHkD6bf0pnSE/E7cjKXzgB4l+58dsNcIVo9YgID4xYGS6paetso XHE= -----END CERTIFICATE----- ... ... ... ... ... . ... ... ... ... ... ... --- Server certificate subject=CN = httpbin.org issuer=C = US, O = Amazon, OU = Server CA 1B, CN = Amazon --- No client certificate CA names sent Peer signing digest: SHA512 Peer signature type: RSA Server Temp Key: ECDH, P-256, 256 bits --- SSL handshake has read 5509 bytes and written 443 bytes Verification: OK --- New, TLSv1.2, Cipher is ECDHE-RSA-AES128-GCM-SHA256 Server public key is 2048 bit Secure Renegotiation IS supported Compression: NONE Expansion: NONE No ALPN negotiated SSL-Session: Protocol : TLSv1.2 Cipher : ECDHE-RSA-AES128-GCM-SHA256 Session-ID: F63AEC9287D557EE6FE770BE0DB46CC0B26991704DEE81E9071377849356F0D6 Session-ID-ctx: Master-Key: 9FA0D73281EBBD07DEF167E5517207BDB80CCFC22D8C53DE9E0354D50A01B5279C51C3C55BE972497329E3E70BE73C43 PSK identity: None PSK identity hint: None SRP username: None TLS session ticket lifetime hint: 43200 (seconds) TLS session ticket: 0000 - d0 62 5a 94 f7 f7 a8 e1-c9 40 e9 ff bc ff e1 d2 .bZ......@...... 0010 - 02 08 f4 aa 80 60 d7 e3-1f 85 1e d1 ba e3 b8 75 .....`.........u 0020 - 20 8f 7f 21 62 b9 72 a1-23 f5 05 8d 61 db b2 2d ..!b.r.#...a..- 0030 - 20 71 38 0d 70 30 30 3d-98 ed 64 b1 66 85 75 eb q8.p00=..d.f.u. 0040 - a6 e2 fe 13 9d 86 aa f4-43 42 be b3 86 2c a2 30 ........CB...,.0 0050 - 7c 90 f1 1b 84 b8 58 31-72 3e c2 92 83 d2 4a 33 |.....X1r>....J3 0060 - a1 76 f7 f4 c1 f1 6a a9-bc ce f7 c4 b6 81 28 2f .v....j.......(/ 0070 - 76 10 c1 ca a7 5f cf 16-0e 40 ee 09 ab b9 2b d1 v...._...@....+. 0080 - 05 85 ed 48 30 2b 84 2c-f4 1d b6 61 08 b5 66 ff ...H0+.,...a..f. 0090 - c6 3c d3 ac e4 bb 8a de-70 73 af d9 d0 e4 f4 65 .<......ps.....e 00a0 - 1e 42 74 0d 8b 71 a7 a1-58 84 a3 d1 3b 4e 8c 24 .Bt..q..X...;N.$ 00b0 - 64 46 2f ce 5a a9 a3 a0-64 23 46 36 73 92 50 2f dF/.Z...d#F6s.P/ Start Time: 1607966129 Timeout : 7200 (sec) Verify return code: 0 (ok) Extended master secret: no --- DONE
Generate certificate
- Generates the private key mykey.pem, which should never be disclosed, and the certificate mycert.pem.
$ openssl req -x509 -nodes -days 5000 -newkey rsa:1024 -keyout mykey.pem -out mycert.pem
View Certificate
$ openssl x509 -text -in mycert.pem
More commands at:
1.29.4 TCP/IP Client Socket with TLS (OpenSSL)
This sample code contains a TCP/IP client socket using TLS encapsulation for secure communication.
OpenSSL Documentation:
All codes and files available at:
File: CMakeLists.txt
cmake_minimum_required(VERSION 3.9) project(Sample_SLL) #========== Global Configurations =============# #----------------------------------------------# set(CMAKE_CXX_STANDARD 17) set(CMAKE_VERBOSE_MAKEFILE ON ) #----------- Set targets -------------------------------# find_package( OpenSSL REQUIRED ) add_executable( ssl-test ssl-test.cpp ) target_link_libraries( ssl-test ${OPENSSL_LIBRARIES} )
File: ssl-test.cpp
#include <iostream> #include <string> #include <cstring> // Equivalent to #include <string.h> in C #include <cassert> // assert() macro. // ----- Unix/Linux headers -----// #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <limits.h> #include <tuple> // ----- OpenSSL headers -------// #include <openssl/ssl.h> #include <openssl/bio.h> #include <openssl/err.h> constexpr int FAILURE = -1; constexpr int SSL_FAILURE = -1; using SSL_Tuple = std::tuple<SSL_CTX*, SSL*>; // Create and return a client socket and connecting to a hostname. int socket_connect(const char* hostname, std::uint16_t port); // Instantiate SSL data structures. SSL_Tuple setup_ssl(); // Shows certificate information. void display_certificate(SSL* ssl); int main(int argc, char** argv) { if(argc < 3) { std::fprintf(stderr, " Usage: %s <HOSTNAME> <PORT> \n", argv[0]); return EXIT_FAILURE; } uint16_t port = std::stod(argv[2]); const char* hostname = argv[1]; const char* trust_cert_path = X509_get_default_cert_dir(); std::fprintf( stderr, " [TRACE] X509 Trust Certificate Directory: %s \n" , trust_cert_path); // =========== SSL/TSL Initialization ===============// //---------------------------------------------------// // Requires C++ 17 auto [ctx, ssl] = setup_ssl(); //============ Connect to server =====================// //----------------------------------------------------// int sockfd = socket_connect(hostname, port); // Encapsulate TCP protcol SSL_set_fd(ssl, sockfd); if( SSL_connect(ssl) == SSL_FAILURE ) { std::fprintf(stderr, " [ERROR] Failure to setup SSL/TSL connection"); ERR_print_errors_fp(stderr); std::abort(); } std::fprintf(stdout, " [INFO] Connected to the server Ok. "); // From OpenSSL library display_certificate(ssl); // Check certifcate if( SSL_get_verify_result(ssl) == X509_V_OK ) { std::fprintf(stdout, " [INFO] Certificate validated. Ok. \n"); } else { std::fprintf(stdout, " [ERROR] Certificate validation. => May be self signed. \n"); ERR_print_errors_fp(stderr); } // ========= Session Loop =============================// //-----------------------------------------------------// constexpr size_t BUFFER_SIZE = 500; // Null character '\0' initialized buffer char buffer[BUFFER_SIZE] = {0}; for(;;) { ssize_t size = SSL_read(ssl, buffer, BUFFER_SIZE); // Return 0 bytes when peer (server) closes connection. if(size == 0) { std::fprintf(stderr, " [INFO] Peer closed the connection. \n"); break; } if(size == -1){ std::fprintf(stderr, " [ERROR] Failed to read socket. \n"); } std::string msg = " [SERVER] " + std::string(buffer, buffer + size); std::cout << msg << '\n'; // Write back message to server. SSL_write(ssl, msg.data(), msg.size() ); } // ========= Dispose Resources ========================// //-----------------------------------------------------// // Dispose resource (file descriptor) close(sockfd); // Release SSL context SSL_CTX_free(ctx); } //==================================================// // I M P L E M E N T A T I O N S // //==================================================// int socket_connect(const char* hostname, std::uint16_t port) { int sockfd = socket ( AF_INET, SOCK_STREAM, 0); // assert() => Placeholder for future error handling. assert( sockfd != FAILURE ); // Query DNS struct hostent* h = gethostbyname(hostname); assert( h != nullptr ); struct sockaddr_in sa; std::memcpy(&sa.sin_addr.s_addr, h->h_addr, h->h_length); sa.sin_family = AF_INET; sa.sin_port = htons(port); int res = connect( sockfd , reinterpret_cast<sockaddr*>(&sa) , sizeof(sockaddr_in)); assert( res != FAILURE && "Failure to connect => Check errno. " ); return sockfd; } SSL_Tuple setup_ssl() { SSL_load_error_strings(); ERR_load_BIO_strings(); OpenSSL_add_all_algorithms(); SSL_library_init(); // Instantiate SSL method const SSL_METHOD* method = SSLv23_method(); assert( method != nullptr && "Failure to load SSL method."); // Create SSL context (ctx) SSL_CTX* ctx = SSL_CTX_new( method ); if( ctx == nullptr ) { std::fprintf(stderr, " [ERROR] Failed to create SSL context \n"); ERR_print_errors_fp(stderr); std::abort(); } if( !SSL_CTX_set_default_verify_paths(ctx) ) { std::fprintf(stderr, " [ERROR] Failure to set default trusted CA paths"); ERR_print_errors_fp(stderr); exit(EXIT_FAILURE); } SSL* ssl = SSL_new(ctx); assert( ssl != nullptr ); return SSL_Tuple{ ctx, ssl }; } void display_certificate(SSL* ssl) { X509* cert = SSL_get_peer_certificate(ssl); if( cert == nullptr ) { std::fprintf(stderr, " [ERROR] Failed to get certificate \n"); ERR_print_errors_fp(stderr); return; } char* out = nullptr; long version = X509_get_version(cert); std::fprintf(stdout, " [CERTIFICATE] version = %d \n", version); out = X509_NAME_oneline( X509_get_subject_name(cert), 0, 0); std::fprintf(stdout, " [CERTIFICATE] name = %s \n", out); free(out); out = X509_NAME_oneline( X509_get_issuer_name(cert), 0, 0); std::fprintf(stdout, " [CERTIFICATE] issuer = %s \n", out); free(out); X509_free(cert); }
Install OpenSSL Dependency
Debian and Ubuntu Linux:
$ sudo apt-get install libssl-dev
Fedora Linux:
$ sudo dnf install -y openssl-devel.x86_64
Building
Building manually:
$ >> git clone https://gist.github.com/bb884fd36b8930cb7358133c2afe9b56 proj && cd proj $ >> g++ ssl-test.cpp -o ssl-test -std=c++1z -Wall -Wextra -lssl -lcrypto
Building with CMake:
$ >> git clone https://gist.github.com/bb884fd36b8930cb7358133c2afe9b56 proj && cd proj $ >> ls cert.pem CMakeLists.txt key.pem ssl-test.cpp $ >> cmake -H. -B_build -DCMAKE_BUILD_TYPE=Debug $ >> cmake --build _build --target all $ >> file _build/ssl-test _build/ssl-test: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so. 2, BuildID[sha1]=ff0d3e3765841a53a13dab7b700726fcbd92fe56, for GNU/Linux 3.2.0, with debug_info, not stripped
Generate a sample certificate for testing purposes
Create a certificate (cert.pem) and private key (key.pem) and answer the prompt.
$ openssl req -x509 -nodes -days 5000 -newkey rsa:1024 -keyout key.pem -out cert.pem
View certificate:
$ >> openssl x509 -text -in cert.pem Certificate: Data: Version: 3 (0x2) Serial Number: 57:5b:cc:50:94:fe:5f:e8:cb:7e:44:43:80:50:76:27:47:61:ba:7e Signature Algorithm: sha256WithRSAEncryption Issuer: C = UK, ST = Bristol, L = Yorkshire, O = some company, OU = dummy department, CN = www.myserver.com, emailAddress = company@mail.co.de Validity Not Before: Dec 9 22:25:09 2020 GMT Not After : Aug 18 22:25:09 2034 GMT Subject: C = UK, ST = Bristol, L = Yorkshire, O = some company, OU = dummy department, CN = www.myserver.com, emailAddress = company@mail.co.de Subject Public Key Info: Public Key Algorithm: rsaEncryption RSA Public-Key: (1024 bit) Modulus: 00:f3:73:c4:8c:86:6e:d5:06:a7:86:5d:1d:2f:f3: e7:7e:cd:f5:30:b5:c7:21:47:c2:8b:3f:f4:f3:09: 30:27:7e:5f:9d:a9:44:b9:5d:e4:50:4b:fe:d7:41: d0:17:da:72:a6:99:f7:25:c5:e1:f1:1f:ee:47:69: d0:c2:47:63:f7:f1:4a:ff:aa:3c:66:57:29:35:a4: c8:9d:b2:cf:44:e4:c4:15:21:8b:4d:25:61:c3:ee: c4:47:50:88:b6:19:d7:77:06:39:d8:6f:34:f0:51: 9f:86:78:2e:5c:d9:a5:e6:33:c4:29:a4:f8:8e:01: c0:c7:27:fe:73:bf:ea:3d:2f Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Subject Key Identifier: 52:48:8A:A3:78:49:BD:65:A5:E2:C9:EC:86:FF:05:93:D1:7F:A4:C4 X509v3 Authority Key Identifier: keyid:52:48:8A:A3:78:49:BD:65:A5:E2:C9:EC:86:FF:05:93:D1:7F:A4:C4 X509v3 Basic Constraints: critical CA:TRUE Signature Algorithm: sha256WithRSAEncryption b8:ee:be:e6:17:5f:9f:be:61:9f:05:c8:d2:58:f7:86:70:84: 5f:4b:0c:2a:e8:e5:19:03:20:62:6f:93:34:0c:65:af:68:ac: 95:ac:33:84:7e:3e:04:54:69:26:a3:f6:60:61:25:38:f2:0c: 24:94:fc:68:5a:f4:02:e1:61:8c:a7:bd:63:1d:39:e0:b9:f7: d7:77:ea:1e:5b:10:13:29:85:26:bf:a0:dc:e6:7d:4f:ff:73: 26:e4:d7:92:f3:be:6c:5b:55:c9:6f:e3:4e:1a:a6:b0:a8:ce: 59:e8:4c:21:b3:b7:03:7c:26:71:a4:61:8d:24:9b:cf:d8:d4: 8a:4b -----BEGIN CERTIFICATE----- MIIDJDCCAo2gAwIBAgIUV1vMUJT+X+jLfkRDgFB2J0dhun4wDQYJKoZIhvcNAQEL BQAwgaMxCzAJBgNVBAYTAlVLMRAwDgYDVQQIDAdCcmlzdG9sMRIwEAYDVQQHDAlZ b3Jrc2hpcmUxFTATBgNVBAoMDHNvbWUgY29tcGFueTEZMBcGA1UECwwQZHVtbXkg ZGVwYXJ0bWVudDEZMBcGA1UEAwwQd3d3Lm15c2VydmVyLmNvbTEhMB8GCSqGSIb3 DQEJARYSY29tcGFueUBtYWlsLmNvLmRlMB4XDTIwMTIwOTIyMjUwOVoXDTM0MDgx ODIyMjUwOVowgaMxCzAJBgNVBAYTAlVLMRAwDgYDVQQIDAdCcmlzdG9sMRIwEAYD VQQHDAlZb3Jrc2hpcmUxFTATBgNVBAoMDHNvbWUgY29tcGFueTEZMBcGA1UECwwQ ZHVtbXkgZGVwYXJ0bWVudDEZMBcGA1UEAwwQd3d3Lm15c2VydmVyLmNvbTEhMB8G CSqGSIb3DQEJARYSY29tcGFueUBtYWlsLmNvLmRlMIGfMA0GCSqGSIb3DQEBAQUA A4GNADCBiQKBgQDzc8SMhm7VBqeGXR0v8+d+zfUwtcchR8KLP/TzCTAnfl+dqUS5 XeRQS/7XQdAX2nKmmfclxeHxH+5HadDCR2P38Ur/qjxmVyk1pMidss9E5MQVIYtN JWHD7sRHUIi2Gdd3BjnYbzTwUZ+GeC5c2aXmM8QppPiOAcDHJ/5zv+o9LwIDAQAB o1MwUTAdBgNVHQ4EFgQUUkiKo3hJvWWl4snshv8Fk9F/pMQwHwYDVR0jBBgwFoAU UkiKo3hJvWWl4snshv8Fk9F/pMQwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B AQsFAAOBgQC47r7mF1+fvmGfBcjSWPeGcIRfSwwq6OUZAyBib5M0DGWvaKyVrDOE fj4EVGkmo/ZgYSU48gwklPxoWvQC4WGMp71jHTnguffXd+oeWxATKYUmv6Dc5n1P /3Mm5NeS875sW1XJb+NOGqawqM5Z6Ewhs7cDfCZxpGGNJJvP2NSKSw== -----END CERTIFICATE-----
Run Netcat server as TLS server using the generated certificate
Netcat terminal emulator:
$ >> nc --verbose --ssl-key key.pem --ssl-cert cert.pem --listen 9051 Ncat: Version 7.80 ( https://nmap.org/ncat ) Ncat: Listening on :::9051 Ncat: Listening on 0.0.0.0:9051 Ncat: Connection from 127.0.0.1. Ncat: Connection from 127.0.0.1:60202. User types message in server terminal, the client sends them back to server [SERVER] User types message in server terminal, the client sends them back to server Turn on control valve [SERVER] Turn on control valve Shutdown cooling system [SERVER] Shutdown cooling system Solve control equations [SERVER] Solve control equations Type Ctrl + C at server terminal to shutdown the connection.^C⏎
TLS Client terminal emulator:
$ ./build/ssl-test localhost 9051
[TRACE] X509 Trust Certificate Directory: /etc/pki/tls/certs
[INFO] Connected to the server Ok. [CERTIFICATE] version = 2
[CERTIFICATE] name = /C=UK/ST=Bristol/L=Yorkshire/O=some company/OU=dummy department/CN=www.myserver.com/emailAddress=company@mail.co.de
[CERTIFICATE] issuer = /C=UK/ST=Bristol/L=Yorkshire/O=some company/OU=dummy department/CN=www.myserver.com/emailAddress=company@mail.co.de
[ERROR] Certificate validation. => May be self signed.
[SERVER] User types message in server terminal, the client sends them back to server
[SERVER] Turn on control valve
[SERVER] Shutdown cooling system
[SERVER] Solve control equations
[INFO] Peer closed the connection.
Attempt to connect to web sites using HTTPS - HTTP/TLS (port 443)
$ >> build/ssl-test www.yahoo.com 443 [TRACE] X509 Trust Certificate Directory: /etc/pki/tls/certs [INFO] Connected to the server Ok. [CERTIFICATE] version = 2 [CERTIFICATE] name = /C=US/ST=California/L=Sunnyvale/O=Oath Inc/CN=*.www.yahoo.com [CERTIFICATE] issuer = /C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert SHA2 High Assurance Server CA [INFO] Certificate validated. Ok. ^C^C⏎ $ >> build/ssl-test www.bing.com 443 [TRACE] X509 Trust Certificate Directory: /etc/pki/tls/certs [INFO] Connected to the server Ok. [CERTIFICATE] version = 2 [CERTIFICATE] name = /CN=www.bing.com [CERTIFICATE] issuer = /C=US/O=Microsoft Corporation/CN=Microsoft RSA TLS CA 02 [INFO] Certificate validated. Ok. ^C⏎
1.30 Sockets with Multiplexed IO APIs (poll, epoll) [DRAFT]
1.30.1 Overview
A recurrent need in network socket programming is to handle multiple incoming client socket connections that need to be managed simultaneously. For instance, if a blocking read operation is performed on a client socket from a http(web) server, the server will not be able to serve the remaining client sockets while the read operation is ongoing. Several solutions have adopted for solving this issue, such as:
- Blocking IO (+) One process per connection
- Peform fork() system call, creating a child process wich is a copy of current process and let it manage the socket file descriptor.
- Drawbacks: Complex, expensive and slow. A process has lots of operating system resources associated with it, including virtual memory, file descriptors and so on. Another issue is the complexity from IPC - Inter-Process Communication.
- Blocking IO (+) One thread per connection
- Spawn/create a new thread for every connection.
- Drawbacks: Although, this approach is less expensive than open-process-per-connection method, it is still expensive and non-scalable, specially for handling thousands of connections.
- Blocking IO (+) Thread Pool
- Reuse a set of pre-existing threads for managing each connection and avoid the overhead of creating and destroying too many threads.
- Drawbacks: Long running connections should have a timeout in order to not block a thread pool and make it unable to server the remaining client socket requests. This method is less scalable than multiplexed IO.
- Multiplexed IO [BEST]
- Many operating systems provide multiplexed IO facilities, such as select, poll and epoll on Linux, keque on BSD-variants and Mac-OSX and IOCP on Windows. Multiplexed IO allow managing multiple file descriptors of any type in a single thread, consequently this feature allows handling multiple client socket connetions with far less overhead than using one thread for every connection. This type IO API is that makes the scalability of Nginx web server and NodeJS.
- Multiplexed IO works in the following way: first the application register the file descriptors it is interested in monitoring; then the calling code calls a multiplexed IO function that blocks the current thread during some amount of time or indefinitely, the IO multiplexing function only returns when any of the file descriptors are ready to be read or written. The calling code handles each file descriptor by iterating over the data structure returned by the IO multiplexing function which contains the file descriptors events.
- Note: It is possible to run multiple multiplexed-IO event loops in different threads for increasing responsiveness and scalability.
- Drawbacks:
- Event-driven with possible callback hell which can be avoided with user-space threads (aka: coroutines, goroutines, green threads).
- Any operation which takes a significant time needs to be run in another thread, otherwise the threads handling the connection will be blocked and unable to server any other client request.
- Wrappers:
- Multiplexed IO APIs are not standardized. As a result,
several high level wrappers have been created for providing
an uniform and cross-platform API for multiplexed IO. Some of
those wrapper libraries are:
- Boost.ASIO - C++ library for scalable and cross-platform network programming.
- libUV - C library used by NodeJS; Julia language and so on.
- libev
- libEvent
- Multiplexed IO APIs are not standardized. As a result,
several high level wrappers have been created for providing
an uniform and cross-platform API for multiplexed IO. Some of
those wrapper libraries are:
- Note: Many documents also calls multiplexed IO as event-driven IO, non-blocking IO and so on.
- Note: The EPool API cannot monitor file descriptors of regular files. For this case, the Linux kernel provides the iNotify API.
See:
1.30.2 Poll() multiplexed IO API
Poll is a standardized multiplexing IO API common to a wide variety of Unix-like operating systems, including Linux-based operating systems, BSD variants and MacOSX. Poll() API is able to monitor multiple file descriptors in a single thread without any wasteful polling. The API blocks the current thread for a certain amount of time and returns until any monitored file descriptor is ready for read or write IO operations.
Observations:
- Multiplexed IO is often also called (there is no a standard terminology yet):
- multiplexed IO (Input/Output)
- asynchronous IO
- async IO
- event-driven IO
- event-based IO
- non-blocking IO (although, it really do blocks the current thread.)
- Poll() is able to monitor:
- Standard terminal IO file descriptors (STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO)
- socket file descriptors
- terminal file descriptors
- anonymous pipes
- named pipes
- character device files
- Poll() is not able to monitor file descriptor for regular files. In this case, the poll() should be used alongside inotify (Linux-only).
Comparison with Epoll
- The Poll() API is less scalable and less efficient than the Epoll() API since that, the Poll API requires client code iterating over all monitored file descriptors in order to find which ones are ready for IO operation. Despite Poll() API shortcomings, the advantage of this API over its comparison counterpat is the higher availability on a broad range of Unix-like operating systems such as MacOSX, BSD variants, QNX and so on. While Epoll API is only available on Linux kernel.
Header
- #include <poll.h>
Functions
- poll() => Blocks the current thread for a certain amount time until one or more file descriptor is ready for IO operations. If the timeout parameter is (-1), the calling thread is blocked indefinitely until some IO event happens.
int poll(struct pollfd *fds, nfds_t nfds, int timeout); int poll( // Array containing monitored file descriptors. // Note: Array alllocated by the caller. struct pollfd* fds // Number of monitored file descriptors , nfds_t nfds // Timeout for which poll() blocks the current thread. // in milliseconds. If the timeout is (-1), the current // thread is blocked indefinitely. , int timeout );
Data Structures
- See: event and rvents flags at (poll(2) - Linux manual page).
struct pollfd { // [INPUT] fd => Monitored file descriptor. // can be set to (-1) when the calling code // is no longer interested in the file descriptor. int fd; // [INPUT] Flags (bitwise-OR) that selects the types of IO events that // will be monitored for this file descriptor. short events; // [OUTPUT] Flags (bitwise-OR) which contains the IO events // that happened to the file descriptor. Those flags are // set by the function poll(). short revents; };
Usage
constexpr size_t nfds = 10; // Caller allocates arrays containing the monitored file descriptors. struct pollfd pollv[nfds]; memset(pollv, 0x00, sizeof(pollfd) * nfds); // === Caller register file descriptors and events it is interested ======// // // Register STDIN (Standard terminal input) pollv[0].fd = STDIN_FILENO; pollv[0].events = POLLIN | POLLER | POLLHUP; // Register socket server pollv[0].fd = server_socket_fd; pollv[0].events = POLLIN | POLLER | POLLHUP | POLLOUT; ... .... ... ... //===================================================// // Run event loop / IO-multiplexing loop // //===================================================// for(;;) { // This thread is blocked indefintely until an IO event happens. int ret = poll(pollv, nfds, -1); // --- Check return value -------// if(ret == 0){ fprintf(stderr, " [TRACE] Timeout \n"); continue; } if(ret == -1){ fprintf(stderr, " [ERROR] Non recoverable error happened. Check ERRNO. Abort() \n"); exit(EXIT_FAILURE); } // Iterate over IO events for(int n = 0; n < nfds ; n++) { auto& pfd = pollv[n]; // Skip elements for which any event happened. if(pfd.revents == 0){ continue; } if(pfd.fd == STDIN_FILENO && pfd.revents & POLLIN) { // .... User typed something in the console. continue; } // A remote machine (client-side) attempts to connect to server. if(pfd.fd == server_socket_fd) { // .... Income request for connecting to server ... // // Register client socket continue; } // Event that happens when client socket is ready for read() operation. if(pfd & POLLIN){ // Read bytes from client socket file descriptor. ssize_t n = read(pfd.fd, buffer, 500); if(n == 1){ perror("read() error"); exit(EXIT_FAILURE); } if(n == 0){ fprintf(stderr, " [TRACE] Client socket closed connection. \n"); close(pfd.fd); // The file descriptor is set to (-1) when the calling code // is no longer interested in monitoring the descriptor. pfd.fd = -1; } // .... Write to STDOUT ... // write(STDOUT_FILENO, buffer, n); } }
References
1.30.3 Poll() sample code
In the following sample code, the Poll() API is used for monitoring IO events from standard terminal input (stdin); socket server and connected client sockets. Anything typed by the user on ther terminal is immediately forwarded to the client sockets.
- GIST with sources at:
File: poll-server.cpp
#include <iostream> #include <string> #include <vector> #include <cassert> #include <cstring> // memcpy // ----- Unix/Linux headers -----// #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <limits.h> #include <sys/ioctl.h> #include <poll.h> // ------------- MAIN() ------------------------------// struct sresult{ std::string msg; ssize_t size; }; sresult read_string(int fd, size_t buffer_size); void write_buffer(int fd, const void* buffer, size_t size); void write_buffer(int fd, const void* buffer, size_t size); void write_string(int fd, std::string const& text); constexpr int UNIX_FAILURE = -1; constexpr int BLOCK_INFINITE_TIMEOUT = -1; int main(int argc, char** argv) { std::fprintf(stderr, " [TRACE] Process Unique Identifier PID = %d \n", ::getpid()); // ======= Server configuration =========// std::uint16_t port = 9056; const char* hostname = "0.0.0.0"; // ====== Create Socket File Descriptor =============// int sockfd = socket ( AF_INET // domain: IPv4 , SOCK_STREAM // type: TCP , 0 // protocol: (value 0) ); std::fprintf(stderr, " [TRACE] sockfd = %d \n", sockfd); // Placeholder for future error handling. // Returns (-1) on failure assert( sockfd != - 1); // Note: t 'struct' is redundant. struct hostent* h = gethostbyname(hostname); assert(h != nullptr); struct sockaddr_in sa; memcpy(&sa.sin_addr.s_addr, h->h_addr, h->h_length); sa.sin_family = AF_INET; sa.sin_port = htons(port); // ----- Bind to Port and wait for client connections ---------// // Enables binding to the same address int enable_reuseaddr = 1; if ( setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &enable_reuseaddr, sizeof(int)) < 0 ) { fprintf(stderr, "Error: unable to set socket option. \n"); return EXIT_FAILURE; } if( ::bind(sockfd, (sockaddr *) &sa, sizeof(sa)) == -1) { fprintf(stderr, "Error: unable to bind socket \n"); return EXIT_FAILURE; } int backlog = 5; assert( listen(sockfd, backlog) != UNIX_FAILURE && " Non-recoverable failure. Check errno. " ); // ================ Server Main Loop ===============// // This heap-allocated array contains all file // descriptors that will be monitored. auto pollv = std::vector<struct pollfd>{}; struct pollfd pfd; // Register STDIN file descriptor pfd.fd = STDIN_FILENO; pfd.events = POLLIN; pfd.revents = 0; pollv.push_back(pfd); // Register socket server file descriptor pfd.fd = sockfd; pfd.events = POLLIN; pfd.revents = 0; pollv.push_back(pfd); std::cout << " [TRACE] pollv.size() = " << pollv.size() << '\n'; // ======= Server Event Loop / IO Multiplexing Loop =======// // for(;;) { std::fprintf(stderr, " [TRACE] Waiting IO events. \n"); // poll() Blocks the current thread until any of the file descriptors become ready // for IO (Input/Output) operations. On failure, the function returns -1 setting ERRNO. // On success, the function poll() returns the number of file descriptors (nfds) // ready to be written or read. // int nfds = ::poll( &pollv[0], pollv.size(), BLOCK_INFINITE_TIMEOUT); std::fprintf(stderr, " [TRACE] File descriptors ready. OK => nfds = %d \n", nfds); if(nfds == UNIX_FAILURE) { std::fprintf(stderr, " [ERROR] errno = %d / %s \n", errno, strerror(errno) ); exit(EXIT_FAILURE); } assert(nfds > 0 && "Nfds supposed to be greater than zero."); // Iterate over ready file descriptors for(auto& pf : pollv) { // Skip entries for which there is no IO event. if(pf.revents == 0){ continue; } // Event that happens when user types something in the console. if(pf.fd == STDIN_FILENO) { std::string line; std::getline(std::cin, line); std::fprintf(stdout, " [EVENT] User entered: %s \n", line.c_str()); // Send STDIN message to all connected socket clients. for(int i = 2; i < pollv.size(); i++){ int fd = pollv[i].fd; if(fd == -1){ continue; } write_string(fd, " [STDIN] " + line + "\n"); } // Skip the parts below this statement. continue; } // Event happens when a socket client attempts to connect. // // if(pf.fd == sockfd && ((pf.revents & POLLIN) == POLLIN) ) if( pf.fd == sockfd ) { sockaddr_in client_sa; socklen_t addrlen = sizeof(sockaddr_in); int client_fd = accept(sockfd, nullptr, nullptr); //if(client_fd == UNIX_FAILURE){ break; } assert( client_fd != UNIX_FAILURE && " Non recoverable failure. \n" ); std::fprintf(stderr, " [TRACE] Received client connection => client_fd = %d \n", client_fd); // Register client socket file descriptor struct pollfd pfd; pfd.fd = client_fd; pfd.events = POLLIN | POLLHUP; pfd.revents = 0; pollv.push_back(pfd); // Greet client socket write_string(client_fd, " [SERVER] Hi, client socket. Welcome to the server. \n\n"); continue; } // Event that happens when any client socket sends a message. if(pf.revents & POLLIN) { auto [msg, size] = read_string(pf.fd, 500); assert( size >= 0 && " Number of bytes returned supposed to be grater or equal to zero " ); // Socket returns 0 bytes when disconnected if(size == 0){ std::fprintf(stdout, " [EVENT] Client socket fd = %d closed connection \n", pf.fd); // Disable file descriptor close(pf.fd); pf.fd = -1; continue; } std::fprintf(stdout, " [EVENT] fd = %d ; message = %s ", pf.fd, msg.c_str()); write_string(pf.fd, "\n [ECHO] fd = " + std::to_string(pf.fd) + " / " + msg + "\n"); continue; } } // ---- End of for() pf iterations --------// } // -------- End of server pf loop -----------------------// close(sockfd); return 0; } // ------------------------------------------------------// // I M P L E M E N T A T I O N S // //-------------------------------------------------------// sresult read_string(int fd, size_t buffer_size) { // Create a string initialized with null characters std::string str(buffer_size, 0x00); ssize_t n = read(fd, &str[0], buffer_size); if(n > 0){ str.resize(n); } return sresult{ str, n }; } void write_buffer(int fd, const void* buffer, size_t size) { // Pre-condition assert( size <= SSIZE_MAX ); size_t len = size; ssize_t nr = -1; auto pbuf = reinterpret_cast<const std::uint8_t*>(buffer); while( len != 0 && ( nr = write(fd, pbuf, len) ) != 0 ) { if(nr == -1 && errno == EINTR){ continue; } if(nr == -1) { throw std::runtime_error("An error has happened, check ERRNO "); break; } len = len - nr; pbuf = pbuf + nr; } } void write_string(int fd, std::string const& text) { write_buffer(fd, text.data(), text.length()); }
Building:
$ g++ poll-server.cpp -o poll-server.bin -std=c++1z -Wall -Wextra -g -O0
Running server in terminal 1:
$ >> ./poll-server.bin [TRACE] Process Unique Identifier PID = 434583 [TRACE] sockfd = 3 [TRACE] pollv.size() = 2 [TRACE] Waiting IO events. [TRACE] File descriptors ready. OK => nfds = 1 [TRACE] Received client connection => client_fd = 4 [TRACE] Waiting IO events. [TRACE] File descriptors ready. OK => nfds = 1 [TRACE] Received client connection => client_fd = 5 [TRACE] Waiting IO events. [TRACE] File descriptors ready. OK => nfds = 1 [EVENT] fd = 4 ; message = I am the client from the terminal 1 [TRACE] Waiting IO events. [TRACE] File descriptors ready. OK => nfds = 1 [EVENT] fd = 5 ; message = I am the client from the terminal 2 [TRACE] Waiting IO events. This message typed in the server terminal (stdin) will be set to all client sockets. [TRACE] File descriptors ready. OK => nfds = 1 [EVENT] User entered: This message typed in the server terminal (stdin) will be set to all client sockets. [TRACE] Waiting IO events. [TRACE] File descriptors ready. OK => nfds = 1 [EVENT] fd = 4 ; message = Turn on valve control system right away. [TRACE] Waiting IO events. [TRACE] File descriptors ready. OK => nfds = 1 [EVENT] fd = 5 ; message = Shutdown colling system right away. [TRACE] Waiting IO events. Operation failure (typed by user) [TRACE] File descriptors ready. OK => nfds = 1 [EVENT] User entered: Operation failure (typed by user) [TRACE] Waiting IO events. [TRACE] File descriptors ready. OK => nfds = 1 [EVENT] fd = 4 ; message = Typed CTRL + C after this message [TRACE] Waiting IO events. [TRACE] File descriptors ready. OK => nfds = 1 [EVENT] Client socket fd = 4 closed connection [TRACE] Waiting IO events. [TRACE] File descriptors ready. OK => nfds = 1 [EVENT] fd = 5 ; message = Type Ctrl+C for disconnecting from server (terminal 2) [TRACE] Waiting IO events. [TRACE] File descriptors ready. OK => nfds = 1 [EVENT] Client socket fd = 5 closed connection [TRACE] Waiting IO events.
Running netcat client (TCP client socket) in terminal 2:
$ nc localhost 9056
[SERVER] Hi, client socket. Welcome to the server.
I am the client from the terminal 1
[ECHO] fd = 4 / I am the client from the terminal 1
[STDIN] This message typed in the server terminal (stdin) will be set to all client sockets.
Turn on valve control system right away.
[ECHO] fd = 4 / Turn on valve control system right away.
[STDIN] Operation failure (typed by user)
Typed CTRL + C after this message
[ECHO] fd = 4 / Typed CTRL + C after this message
^C
Running netcat client (TCP client socket) in terminal 3:
$ nc localhost 9056
[SERVER] Hi, client socket. Welcome to the server.
I am the client from the terminal 2
[ECHO] fd = 5 / I am the client from the terminal 2
[STDIN] This message typed in the server terminal (stdin) will be set to all client sockets.
Shutdown colling system right away.
[ECHO] fd = 5 / Shutdown colling system right away.
[STDIN] Operation failure (typed by user)
Type Ctrl+C for disconnecting from server (terminal 2)
[ECHO] fd = 5 / Type Ctrl+C for disconnecting from server (terminal 2)
^C
1.30.4 Epoll() multiplexed IO API
Epoll is a highly scalable and efficient Linux-only multiplexed IO API which allows monitoring an unlimted amount of file descriptors, and consequentely file descriptors related to regular files, device files and sockets. This API is that makes the scalability of NodeJS and Nginx web server possible.
Manpage:
Header file:
- #include <sys/epoll.h>
Structs:
- The events is a Or bitmaks, some of the flags are:
- EPOLLIN => "The associated file is available for read(2) operations."
- EPOLLOUT => "The associated file is available for write(2) operations."
- EPOLLHUP => "Hang up happened on the associated file descriptor."
- EPOLLET => "Requests edge-triggered notification for the associated file descriptor. The default behavior for epoll is level-triggered. See epoll(7) for more detailed information about edge triggered and level-triggered notification."
typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t; struct epoll_event { uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ };
Functions:
- epoll_create1(int) => Creates epoll object, returns epoll file descriptor.
- "If flags is 0, then, other than the fact that the obsolete size argument is dropped, epoll_create1() is the same as epoll_create(). The following value can be included in flags to obtain different behavior:"
int epoll_create1(int flags); // ------ Usage ---------------------// // Epoll file descriptor int epfd; if( (epfd = epoll_create1(0)) == -1 ) { perror("Unable to get epoll file descriptor. Check ERRNO variable."); exit(1); } // .... Use Epoll .... // Close when no longer needed. close(epfd);
- epoll_ctl() => Modify, remove or add file descriptors to the
interest list, set of monitored file descriptors.
- Doc: $ man epoll_ctl
- Doc: Manpage
- Some values of 'op':
int epoll_ctl( int epfd // Epoll file descriptor (Epoll object) , int op // Operation to be performed , int fd // File descritor , struct epoll_event *event // Pointer to epoll_event ); //---------------------------------------------------// // Usage example for registering file descriptor // // add file descriptor to interest list. // // --------------------------------------------------// int fd = STDIN_FILENO; // Stdin, console epoll_event ev; // Monitor when file descriptor is available for read operations. ev.events = EPOLLIN; // Monitor when file descriptor is available for write operations. ev.events = EPOLLIN | EPOLLOUT; ev.data.fd = fd; if( epoll_ctrl(epfd, EPOLL_CTL_ADD, fd, &ev ) == -1 ) { perror("Error: failure. Check errno."); exit(1); } //---------------------------------------------------// // Usage example for removing file descriptor // // --------------------------------------------------// epoll_event ev; ev.events = EPOLLIN; ev.data.fd = fd; if( epoll_ctrl(epfd, EPOLL_CTL_DEL, fd, ) == -1 ) { perror("Error: failure. Check errno."); exit(1); }
- epoll_wait() - IO multiplexing function - blocks the current
thread for some amount of time until an event related to a
monitored file descriptor has happened.
- Manpage: "wait for an I/O event on an epoll file descriptor"
- epfd => Epoll file descriptor
- events => (Output) Pointer to array containing the events related to the monitored file descriptors. This array is allocated by the calling code.
- maxevents => Maximum number of monitored events (often the size of events array).
- timeout => Number of milliseconds that epoll_wait() blocks the current thread. If this value is (-1), the calling thread is indefinitely until some IO event happens.
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
Usage example of epoll_wait() with level triggered.
// ============ Usage for servers ===================// // void Epoll_fd_option(int epoll_fd, int fd, int events) { epoll_event ev; ev.events = events; ev.data.fd = fd; int res = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev); if(res == -1){ perror("Error: EPoll / unable to register file descritor"); } } constexpr size_t MAX_EVENTS = 10; int server = socket(AF_INET, SOCK_STREAM, 0); .. .... ... ... // Create epoll object int epfd = epoll_create1(0); epoll_event notifications[MAX_EVENTS]; // Monitor incoming connections Epoll_fd_option(epfd, server, EPOLLIN); // Monitor stdin (console file descriptor) Epoll_fd_option(epfd, STDIN_FILENO, EPOLLIN); charr buffer[300]; for(;;) { // Current thread blocked until any event happens. // Returns number of evetns int nfsd = epoll_wait(epfd, notifications, MAX_EVENTS, -1); for(int i = 0; i < nfsd; i++) { epoll_event ev = notifications[i]; if(ev.data.fd == server) { // Accept connection return a client socket int client_fd = accept(server, (sockaddr *) &client.sa, &addrlen); Epoll_fd_option(epfdm, client_fd, EPOLLIN); continue; } // Client socket ready for reading operations. if(ev.events & EPOLLIN) { // Read 300 bytes from client socket file descriptor to buffer int n = read(ev.dada.fd, buffer, 300); if(n == 0){ close(ev.data.fd); continue; } // Send back n bytes to client socket write(ev.data.fd, buffer, n); continue; } if(ev.events & EPOLLOUT) { // Perform write operations ... ... .... ... } } // --- End for --- // } // --- End for --- // close(epfd);
1.30.5 Epoll() example - sample code
GIST:
Description:
- Sample TCP/IPv4 network server using epoll() multiplexed IO API for managing STDIN (console input) and multiple client sockets in a single thread. The server also sends any message typed in server console to all connected clients.
- Note: It uses level triggered IO.
File: multiplex-epoll-server.cpp
// File: socket-echo-server.cpp // Desc: Simple socket server with a single thread. #include <iostream> #include <string> #include <vector> #include <cassert> #include <cstring> // memcpy // ----- Unix/Linux headers -----// #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <signal.h> // Only available on Linux #include <sys/epoll.h> struct Socket { int sockfd; sockaddr_in sa; }; struct RecvMsg { ssize_t size; std::string msg; }; void socket_close(Socket& sock); void fd_write(int fd, std::string const& text); RecvMsg fd_read(int fd, size_t buffer_max = 500); /** Make a server socket */ Socket socket_make_server(uint16_t port, const char* hostname = "0.0.0.0"); /** Accept connetion from a server socket and returns a client socket */ Socket socket_accept(Socket& server); /** Register file descriptor to be monitored by Epoll */ void Epoll_register_fd(int epoll_fd, int fd) { epoll_event ev; ev.events = EPOLLIN; ev.data.fd = fd; int res = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev); if(res == -1){ throw std::runtime_error("Error: EPoll / unable to register file descritor"); } } /** Remove file descriptor from monitoring list. */ void Epoll_remove_fd(int epoll_fd, int fd) { epoll_event ev; int res = epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, &ev); } /* Multiplex IO - Blocks current thread until any of monitored file descriptors * has */ int Epoll_wait(int epoll_fd, size_t max_events, std::vector<epoll_event>& events) { // Returns number of events received int nfd = epoll_wait( epoll_fd // Epoll file descripotr , &events[0] // [output] Array containing received events , max_events // Maximum number of events , -1 // Timeout (-1) => Block indefinitely ); return nfd; } int main(int argc, char **argv) { std::puts(" [INFO] Program started. "); // --- Create socket file descriptor -----------------------// Socket server = socket_make_server(9026, "0.0.0.0"); constexpr size_t MAX_EVENTS = 10; int epfd = epoll_create1(0); assert(epfd != - 1); auto events = std::vector<epoll_event>(MAX_EVENTS); // Register stdin Epoll_register_fd(epfd, STDIN_FILENO); // Register socket server Epoll_register_fd(epfd, server.sockfd); // Contains client sockets file descriptors std::vector<Socket> clients; // Server-accept infinite loop for(;;) { std::printf(" [TRACE] Epoll waiting for events \n"); int nfsd = Epoll_wait(epfd, MAX_EVENTS, events); assert( nfsd != - 1); std::printf(" [TRACE] Epoll events arrived => nfsd = %d \n", nfsd); for(int i = 0; i < nfsd; i++) { auto ev = events[i]; std::printf(" ----------------------------------------------------\n"); if( (ev.events & EPOLLERR) || (ev.events & EPOLLHUP) ) { std::printf(" [ERROR] Epoll error has happened. \n"); continue; } // STDIN event => It happens after user types something and // hit RETURN on terminal. if( ev.data.fd == STDIN_FILENO ) { std::printf(" [TRACE] Received STDIN event \n"); std::string line; std::getline(std::cin, line); std::cout << " \n STDIN = " + line << "\n"; // Send typed message to all client sockets for(Socket& cli: clients) fd_write(cli.sockfd, " STDIN = " + line + "\n"); // Quit server if(line == "exit"){ std::puts(" [INFO] Shutdown server OK."); return EXIT_SUCCESS; } continue; } // Event that happens when there is a new connection. if(ev.data.fd == server.sockfd) { Socket client = socket_accept(server); Epoll_register_fd(epfd, client.sockfd); std::printf(" [TRACE] Accept client connection => ID = %d \n", client.sockfd); fd_write(client.sockfd, "\n => [SERVER INFO] Hello world client side!! \n"); // socket_nonblock(client); clients.push_back(client); continue; } // Event that happens when some client socket sends data. // EPOLIN event means that the file descriptor is ready for input. if(ev.events & EPOLLIN) { int fd = ev.data.fd; std::printf("\n [TRACE] Receive client event => ID = %d \n", fd); RecvMsg resp = fd_read(fd, 500); if(resp.size == 0) { std::printf("\n [CLIENT ID = %d] =>> Disconnected from server. \n", fd); close(fd); continue; } std::printf("\n [CLIENT ID = %d] =>> size = %d ; msg = %s \n", fd, resp.size, resp.msg.c_str()); fd_write(fd, " [SERVER ECHO] => ID = " + std::to_string(fd) + " => " + resp.msg); } } } /* [[ ============== END client loop =========== ]] */ socket_close(server); return 0; } // --- End of main() -------------// // ----------- Function Implementations ----------------------------------------// void socket_close(Socket& sock) { ::close(sock.sockfd); sock.sockfd = -1; } void fd_write(int fd, std::string const& text) { ::write(fd, text.c_str(), text.size()); } RecvMsg fd_read(int fd, size_t buffer_max) { std::string buffer(buffer_max, 0x00); ssize_t n = read(fd, &buffer[0], buffer_max); buffer.resize(n); return RecvMsg{n, buffer}; } Socket socket_make_server(uint16_t port, const char* hostname) { int sockfd = socket(AF_INET, SOCK_STREAM, 0); // Returns (-1) on failure assert(sockfd != -1); // ---------- query address ------------------------------// // Note: the keyword 'struct' is not necessary here (redundant). struct hostent *h = gethostbyname(hostname); assert(h != nullptr); struct sockaddr_in sa; memcpy(&sa.sin_addr.s_addr, h->h_addr, h->h_length); sa.sin_family = AF_INET; // The function htons convert a number to big-endian format. sa.sin_port = htons(port); // ----- Bind to Port and wait for client connections ---------// if (::bind(sockfd, (sockaddr *)&sa, sizeof(sa)) == -1) throw std::runtime_error("Error: unable to bind socket \n"); // Enables binding to the same address int enable_reuseaddr = 1; if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &enable_reuseaddr, sizeof(int)) < 0) throw std::runtime_error("Error: unable to set socket option. \n"); // Ignore SIGPIPE signal which would cause abnormal termination of socket server // Requires: #include <signal.h> header. signal(SIGPIPE, SIG_IGN); fprintf(stderr, " [TRACE] Listening client connection \n"); int backlog = 5; assert(listen(sockfd, backlog) != -1); return Socket{ sockfd, sa }; } Socket socket_accept(Socket& server) { Socket client; socklen_t addrlen = sizeof(sockaddr_in); client.sockfd = accept(server.sockfd, (sockaddr *) &client.sa, &addrlen); if (client.sockfd == -1) { fprintf(stderr, " Error: failure to handle client socket. Check errno \n"); close(client.sockfd); } return client; }
Building:
g++ multiplex-epoll-server.cpp -o out.bin -std=c++1z -Wall -Wextra
Running: (Server - terminal 1)
./out.bin [INFO] Program started. [TRACE] Listening client connection [TRACE] Epoll waiting for events [TRACE] Epoll events arrived => nfsd = 1 ---------------------------------------------------- [TRACE] Accept client connection => ID = 7 [TRACE] Epoll waiting for events [TRACE] Epoll events arrived => nfsd = 1 ---------------------------------------------------- [TRACE] Receive client event => ID = 7 [CLIENT ID = 7] =>> size = 19 ; msg = hello world server [TRACE] Epoll waiting for events [TRACE] Epoll events arrived => nfsd = 1 ---------------------------------------------------- [TRACE] Accept client connection => ID = 8 [TRACE] Epoll waiting for events [TRACE] Epoll events arrived => nfsd = 1 ---------------------------------------------------- [TRACE] Receive client event => ID = 8 [CLIENT ID = 8] =>> size = 30 ; msg = connect server from client 2 [TRACE] Epoll waiting for events message typed in stdin is sent to all client sockets. [TRACE] Epoll events arrived => nfsd = 1 ---------------------------------------------------- [TRACE] Received STDIN event STDIN = message typed in stdin is sent to all client sockets. [TRACE] Epoll waiting for events [TRACE] Epoll events arrived => nfsd = 1 ---------------------------------------------------- [TRACE] Receive client event => ID = 8 [CLIENT ID = 8] =>> size = 24 ; msg = from client 2 to server [TRACE] Epoll waiting for events [TRACE] Epoll events arrived => nfsd = 1 ----------------------------------------------------
Running: (Client 1 netcat from terminal 2)
$ >> nc -v localhost 9026 Ncat: Version 7.80 ( https://nmap.org/ncat ) Ncat: Connection to ::1 failed: Connection refused. Ncat: Trying next address... Ncat: Connected to 127.0.0.1:9026. => [SERVER INFO] Hello world client side!! hello world server [SERVER ECHO] => ID = 7 => hello world server STDIN = message typed in stdin is sent to all client sockets. from client 1 to server [SERVER ECHO] => ID = 7 => from client 1 to server ^C⏎
Running: (Client 2 netcat from terminal 3)
$ nc -v localhost 9026 Ncat: Version 7.80 ( https://nmap.org/ncat ) Ncat: Connection to ::1 failed: Connection refused. Ncat: Trying next address... Ncat: Connected to 127.0.0.1:9026. => [SERVER INFO] Hello world client side!! connect server from client 2 [SERVER ECHO] => ID = 8 => connect server from client 2 STDIN = message typed in stdin is sent to all client sockets. from client 2 to server [SERVER ECHO] => ID = 8 => from client 2 to server ^C
1.31 Ptrace - system call
1.31.1 Overview
Ptrace is common system-call implemented by many Unix-like operating systems, such as Linux and FreeBSD, which provides facilities for manipulating and observing other processes. The purpose of this sytem call is to support the implementation of debuggers such as GDB (GNU Debugger), that use this sytem call for examining, modifying and manipulating the memory of debugged processes. Besides GDB, the ptrace system call is also used by the other tools like ltrace, for tracing library calls, and by strace, for tracing system calls. Although, ptrace has similar capabilities among many different Unix-based operating systems, ptrace is not a standardized system call, therefore there are many specific details such as type signature and request parameters that are operating-system dependent.
Documentation
- PTrace / Linux Manpage
- Ptrace / FreeBSD Manpage
- => Note: Ptrace from FreeBSD is different than Linux's Ptrace.
Ptrace capabilities
- Attach to forked child process.
- Attach to an already running process.
- Read/write tracee process' virtual memory ( represented by the file /proc/<PID>/mem )
- Get/Set tracee's process CPU registers.
- Red/Write tracee's process virtual memory
- Intercept and modify systems calls invoked by the monitored process.
- Trace library calls
- Set breaking points
Known usage
- GDB (GNU Debugger)
- strace => Utility for tracing system calls.
- ltrace => Utility for tracing library calls.
Further Reading
- PTrace / Linux Manpage
- Ptrace / FreeBSD Manpage
- Ptrace documentation, draft #6 -LWN.net (Linux)
- Playing with ptrace, Part I | Linux Journal
- Using C to inspect Linux syscalls | OpsTips
- Pyflame: Uber Engineering's Profiler for Python | Uber Engineering Blog
- Linux Syscalls Reference · GitHub
- Searchable Linux Syscall Table for x86 and x86_64 | PyTux
- The Definitive Guide to Linux System Calls - Packagecloud Blog
- x86 calling conventions - Wikipedia
- Linux Performance Analysis: New Tools and Old Secrets - Brendan Gregg
1.31.2 Linux Ptrace-related functions
Function: waitpid():
- Wait for state change of some process. This function returns (-1) on failure.
- Manpage:
- Headers:
- #include <sys/types.h>
- #include <sys/wait.h>
pid_t waitpid( pid_t pid // (Process ID) Unique numeric idefier of monitored process. , int* wstatus // Allocated by caller. , int options ); // ==== U S A G E - E X A M P L E S ============ // // Wait until any child process terminated. // ------------------------------------------- int status = 0; waitpid(-1, &status, 0); // Check whether process has terminated normally. //----------------------------------------------- int status = 0; waitpid(8922, &status, 0); if( WIFEXITED(status) ){ /* Process terminated normally. */ }
Function ptrace():
- Allows observing and manipulating other processes, such as forked child processes or processes already running.
- Manpage:
- Headers:
- <sys/ptrace.h>
- <linux/ptrace.h>
- <linux/tracehook.h>
- Parameters:
- request => Determines the action to be performed.
- pid => Pid of tracee (monitored) process.
- addr => Address within the tracee virtual memory. This parameter is null (nullptr in C++, or NULL in C) for most of request parameters
- data => Data send to tracee's process virtual memory. For most request parameters, this parameter is null.
- RETURN
- => Return value, returns (-1) on failure setting errno variable. The meaning of the return value depends on the request parameter that was used.
// Returns (-1) on failure. long ptrace( int request // Request or command , pid_t pid // PID of tracee or monitored pro ess , void* addr // Address within the tracee's process virtual memory. , void* data // Data parameter, used by some Ptrace );
Summary for some possible values for the ptrace's request parameters:
- PTRACE_ATTACH
- Attach to an already running process given its PID (Unique numeric id identifier)
- PTRACE_DETACH
- Detach from attached process.
- PTRACE_TRACEME
- Used for tracing a child process spawned with fork() library-call. (Note: fork() on Linux is not a system call)
- PTRACE_PEEKDATA
- Read data from tracee's process memory.
- PTRACE_POKEDATA
- Write/set data to some tracee's process location (addr parameter)
- PTRACE_GETREGS
- Obtain current CPU registers from tracee process.
- PTRACE_SETREGS
- Set tracee process' CPU registers.
- PTRACE_SYSCALL
- Used for tracing and modifying tracee process' system calls.
Struct: user_regs_struct
- => Contains CPU registers (context) of monitored process. The fields struct 'user_regs_struct', which represents the CPU registers used by the monitored process, are dependent on the current ISA (Instruction Set Architecture) of current machine.
- Headers:
- <sys/user.h>
// Fields of user_regs_struct for x86-64 ISA (AMD64 ISA) struct user_regs_struct { __extension__ unsigned long long int r15; __extension__ unsigned long long int r14; __extension__ unsigned long long int r13; __extension__ unsigned long long int r12; __extension__ unsigned long long int rbp; __extension__ unsigned long long int rbx; __extension__ unsigned long long int r11; __extension__ unsigned long long int r10; __extension__ unsigned long long int r9; __extension__ unsigned long long int r8; __extension__ unsigned long long int rax; __extension__ unsigned long long int rcx; __extension__ unsigned long long int rdx; __extension__ unsigned long long int rsi; __extension__ unsigned long long int rdi; __extension__ unsigned long long int orig_rax; __extension__ unsigned long long int rip; __extension__ unsigned long long int cs; __extension__ unsigned long long int eflags; __extension__ unsigned long long int rsp; __extension__ unsigned long long int ss; __extension__ unsigned long long int fs_base; __extension__ unsigned long long int gs_base; __extension__ unsigned long long int ds; __extension__ unsigned long long int es; __extension__ unsigned long long int fs; __extension__ unsigned long long int gs; };
1.31.3 Example - trace system calls of a running process
The following sample code, traces system and logs system calls performed by an already running process.
GIST:
File: ptrace-attach.cpp
- Note A: the function ptrace_get_string() reads a string buffer (character array) from the tracee's process virtual memory. The buffer is defined by memory address the initial character and the buffer size as number of bytes.
- Note B: the function ptrace_get_string_null obtains a null-terminated ('\0') character array from the tracee's process virtual memory.
#include <iostream> #include <string> #include <sstream> #include <cstring> // memcpy, memset, ... #include <cassert> // assert() statement #include <vector> #include <functional> #include <map> // --- Unix-specific headers -----// #include <unistd.h> #include <sys/ptrace.h> #include <sys/reg.h> #include <sys/wait.h> #include <sys/types.h> #include <sys/syscall.h> #include <sys/user.h> #include <fcntl.h> constexpr int FAILURE = -1; enum class WaitResult { STOPPED = 0 , TERMINATED = 1 }; /** Ptrace wrapper that terminates the current process there is any error. */ long _ptrace(int request, pid_t pid, void* addr, void* data); WaitResult ptrace_wait_syscall( pid_t pid ); std::string ptrace_get_string( pid_t pid, std::uintptr_t addr, std::size_t size ); std::string ptrace_get_string_null( pid_t pid, std::uintptr_t addr ); int main(int argc, char** argv) { if(argc < 2) { std::fprintf(stderr, " Usage: $ %s <PID> \n", argv[0]); return EXIT_FAILURE; } // Next line: throws exception. pid_t pid = std::stoi(argv[1]); // Attempt to attach to process. _ptrace(PTRACE_ATTACH, pid, nullptr, nullptr); std::fprintf(stderr, " [TRACE] Attached to process. Ok \n"); // Fix PTRACE sigrap problem. That happens after attaching to child // process with PTRACE_ATTACH and the child process receives a SIGTRAP signal. // See: https://stackoverflow.com/questions/44630382 _ptrace( PTRACE_SETOPTIONS, pid, nullptr, (void*) PTRACE_O_TRACECLONE); // Makes possible to distinguish system-call related stops from other // types of stops. _ptrace( PTRACE_SETOPTIONS, pid, nullptr, (void*) PTRACE_O_TRACESYSGOOD); // CPU registers (context) of the monitored process struct user_regs_struct regs; /// ========== Ptrace event loop ==============// std::fprintf(stderr, " [TRACE] Start event loop. Ok. \n"); for(;;) { // Intercept system call entry if( ptrace_wait_syscall(pid) == WaitResult::TERMINATED) { std::fprintf(stderr, " [TRACE] (1) Monitored process has terminated. \n"); break; } // Intercept system call exit if( ptrace_wait_syscall(pid) == WaitResult::TERMINATED) { std::fprintf(stderr, " [TRACE] (2) Monitored process has terminated. \n"); break; } // Get CPU register for the current process. _ptrace( PTRACE_GETREGS, pid, nullptr, ®s); // Get system call number int syscall = regs.orig_rax; // System call => open() // int openat( int dirfd // Register RDI (1st syscall arg.) // , const char *pathname // Register RSI (2nd syscall arg.) // , int flags // Register RSI (3rd syscall arg.) // ); if(syscall == SYS_openat) { auto ptr = uintptr_t{regs.rsi}; auto file = ptrace_get_string_null(pid, regs.rsi); std::fprintf(stderr, " [TRACE] openat() system call found => file = %s \n", file.c_str()); continue; } // => Note: Linux system calls for x64 is the System V x86-64/ calling convention. // // System call: write( int fd // register RDI (1st syscall argument) // , void* buffer // register RSI (2nd syscall argument) // , size_t size // register RDX (3rd syscall argument) // ) if(syscall == SYS_write) { int write_fd = regs.rdi; // File descriptor of write system call uintptr_t write_ptr = regs.rsi; // Pointer to buffer. size_t write_size = regs.rdx; // Buffer size. auto buffer_text = ptrace_get_string(pid, write_ptr, write_size); std::fprintf( stderr , " [TRACE] Write system (syscall = %d ) " " fd = %d ; buffer_ptr = 0x%x ; size = %zu ; \n => buffer_text = '%s' \n\n" , syscall , write_fd, write_ptr, write_size, buffer_text.c_str()); continue; } } // ====== End of ptrace event loop ====// return 0; } // --- End of main() ------- // // ============================================== // // I M P L E M E N T A T I O N S // //------------------------------------------------// long _ptrace(int request, pid_t pid, void* addr, void* data) { long r = ptrace( (__ptrace_request) request, pid, addr, data); if(r == -1){ std::stringstream ss; ss << " [PTRACE FAILURE] " << " ; errno = " << errno << " ; msg = '" << strerror(errno) << "' \n"; throw std::runtime_error(ss.str()); } return r; } WaitResult ptrace_wait_syscall(pid_t pid) { long result; int status; for(;;){ result = _ptrace(PTRACE_SYSCALL, pid, nullptr, nullptr); waitpid(pid, &status, 0); // This predicate holds when the monitored process // has terminated. if( WIFEXITED(status) ) { return WaitResult::TERMINATED; } // Credits and further reading: // => https://ops.tips/gists/using-c-to-inspect-linux-syscalls if( WIFSTOPPED(status) && WSTOPSIG(status) & 0x80 ) { return WaitResult::STOPPED; } } } std::string ptrace_get_string(pid_t pid, std::uintptr_t addr, std::size_t size) { constexpr size_t long_size = sizeof(long); union ptrace_data { long data; char bytes[long_size]; }; int n = 0; int steps = size / sizeof(long); auto pdata = ptrace_data{}; auto result = std::string(size + 1, 0); char* paddr = &result[0]; assert(result.length() == size + 1); while(n < steps) { pdata.data = _ptrace( PTRACE_PEEKDATA, pid , reinterpret_cast<void*>(addr + n * long_size) , nullptr ); std::memcpy(paddr, pdata.bytes, long_size); n++; paddr = paddr + long_size; } // '%' integer remainder steps = size % long_size; if( steps != 0 ) { pdata.data = _ptrace( PTRACE_PEEKDATA, pid , reinterpret_cast<void*>(addr + n * long_size) , nullptr ); memcpy(paddr, pdata.bytes, steps); } result[size] = '\0'; return result; } std::string ptrace_get_string_null(pid_t pid, std::uintptr_t addr) { constexpr size_t long_size = sizeof(long); union ptrace_data { long data; char bytes[long_size]; }; int nread = 0; auto pdata = ptrace_data{}; auto result = std::string{""}; std::memset(&pdata.bytes[0], 0x00, long_size * sizeof(char)); for(;;) { pdata.data = _ptrace( PTRACE_PEEKDATA , pid , reinterpret_cast<void*>(addr + nread) , nullptr ); result.resize(result.size() + long_size + 1); char* pinit = &result[0]; memcpy(pinit + nread, &pdata.data, 1 * sizeof(long)); nread += long_size; // If the null terminating character '\0' (0x00) is found // , then exit the current loop. for(size_t i = 0; i < long_size; i++) if( pdata.bytes[i] == '\0' ) { goto exit_loop; } } exit_loop: return result; }
Building:
$ git clone https://gist.github.com/339b00fc8ab1b3d1d46ed9167ccbaeeb gist && cd gist $ g++ ptrace-attach.cpp -o ptrace-attach.bin -std=c++1z -Wall -Wextra -g
(TERMINAL EMULATOR 1) => Run python3 interactive interpreter on terminal emulator 1, the run the patrace-attach.bin applicaton on terminal 2 and attach to the PID of Python interpreter. After the ptrace monitor application is attached to the current process, the ptrace monitor application will log open() and write() system calls performed by the python process.
$ >> python Python 3.8.5 (default, Aug 12 2020, 00:00:00) [GCC 10.2.1 20200723 (Red Hat 10.2.1-1)] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import os >>> os.getpid() # =>> Get Process ID unique identifier and attach to it from other terminal 2073259 >>> print("Hello world Ptrace") Hello world Ptrace >>> import dbus >>> fd = open("/tmp/logging.txt", "w") >>> >>> fd = open("/tmp/logging.txt", "w") >>> fd.write("Hello world Python logging"); fd.flush() 26 >>>
(TERMINAL EMULATOR 2) => Run ptrace-attach.bin on terminal emulator 2 and attach to the Python interpreter process.
$ ./ptrace-attach.bin 2073259 [TRACE] Attached to process. Ok [TRACE] Start event loop. Ok. [TRACE] Write system (syscall = 1 ) fd = 1 ; buffer_ptr = 0xf75f7010 ; size = 1 ; => buffer_text = ' ' [TRACE] Write system (syscall = 1 ) fd = 1 ; buffer_ptr = 0xf751a6d0 ; size = 19 ; => buffer_text = 'Hello world Ptrace ' [TRACE] Write system (syscall = 1 ) fd = 1 ; buffer_ptr = 0xf75f7010 ; size = 4 ; => buffer_text = '>>> ' ... ... .. ... ... ... .. ... ... ... .. ... ... ... .. ... ... ... .. ... ... ... .. ... [TRACE] openat() system call found => file = /usr/lib64/python3.8/site-packages/dbus/__pycache__/__init__.cpython-38.pyc [TRACE] openat() system call found => file = /usr/lib64/python3.8/site-packages/dbus [TRACE] openat() system call found => file = /usr/lib64/python3.8/site-packages/dbus/__pycache__/_compat.cpython-38.pyc [TRACE] openat() system call found => file = /usr/lib64/python3.8/site-packages/dbus/__pycache__/exceptions.cpython-38.pyc [TRACE] openat() system call found => file = /usr/lib64/python3.8/site-packages/dbus/__pycache__/types.cpython-38.pyc [TRACE] openat() system call found => file = /usr/lib64/python3.8/site-packages/_dbus_bindings.so [TRACE] openat() system call found => file = /home/mxpkf8/dlang/dmd-2.092.0/linux/lib64/libdbus-1.so.3 [TRACE] openat() system call found => file = /etc/ld.so.cache [TRACE] openat() system call found => file = /lib64/libdbus-1.so.3 [TRACE] openat() system call found => file = /home/mxpkf8/dlang/dmd-2.092.0/linux/lib64/libsystemd.so.0 [TRACE] openat() system call found => file = /lib64/libsystemd.so.0 [TRACE] openat() system call found => file = /home/mxpkf8/dlang/dmd-2.092.0/linux/lib64/librt.so.1 [TRACE] openat() system call found => file = /lib64/librt.so.1 [TRACE] openat() system call found => file = /home/mxpkf8/dlang/dmd-2.092.0/linux/lib64/liblzma.so.5 [TRACE] openat() system call found => file = /lib64/liblzma.so.5 .... .... .... .... .... .... .... .... .... .... .... [TRACE] openat() system call found => file = /tmp/logging.txt [TRACE] Write system (syscall = 1 ) fd = 1 ; buffer_ptr = 0xf75f7010 ; size = 4 ; => buffer_text = '>>> ' ... ... ... ... ... ... ... ... ... ... ... ... ... .. ... ... ... ... .. ... .. . .. . . [TRACE] Write system (syscall = 1 ) fd = 3 ; buffer_ptr = 0xf7643390 ; size = 26 ; => buffer_text = 'Hello world Python logging' [TRACE] Write system (syscall = 1 ) fd = 1 ; buffer_ptr = 0xf75f7010 ; size = 4 ; => buffer_text = '>>> '
1.32 X11 - X Windows System
1.32.1 Overview
Most Unix-like operating systems, including Linux and BSD variants, except Apple's MacOSX, use X-Windows System, also called X11, which is a network-transparent window system developed at project Athena (MIT) in 1984 and nowadays it is led by the X.Org foundation.
The server or display server (XServer) draws windows and widgets on the human operator display and receives input events from mouse and keyboard on behalf of one or more local or remote client applications (XClients). This architecture enables network transparency, which is the ability to operate either locally or over network. In practical terms, network transparency makes possible to interact with windows from graphics applications (x11 clients) running on remote servers as they were local applications and local windows. A X11 client appplication communicates with the XServer by using the X Windows System (X11) network protocol via unix socket if the client application is running on the user machine or via TCP/IP sockets if the client application is running on a remote server.
Main parts:
- X Server (Server-side, located at user's machine, draws on user's Monitor/Screen)
- => Runs on the user (human operator) machine, draws windows and widgets for connected applications x-clients running on local or remote machines.
- X Client => Application running on user machine or remote server.
- => Initiates the connection and makes network requests to the server, which draws the application's widgets.
- X11 - Network Protocol
- => Techinical specification or technical standard about how x-server and x-clients should communicate.
The X-Client (local or remote application) can communicate with the X-Server by using:
- Direct X11 protocol network requests via TCP/IP socket or Unix-sockets, in case of applications running on the local machine.
- XLib C Library => Encapsulates the X11 network protocol.
- XCb C Library => (Lower level than XLib) Similar to XLib, but has a smaller footprint and lower latency, better suport for thread.
Notes and considerations:
- Unlike on Windows and Mac-OSX, the graphics interface is not part of neither Linux kernel nor BSD-variants' kernels. The ability to run headless, without any graphics, makes Linux-based operating systems suitable for network server and embedded systems where a built-in GUI could cause higher overhead and system footprint.
- X11 protocol does not specify styling, widgets (menu, buttons and so on). Those features are provided by window managers and GUI Graphical User Interface toolkits such as, GTK, Qt, Motif, FLTK, and Java Swing (Java Only).
- X11 is not widely used on Embedded Linux systems as it is on Linux desktop systems. Instead many embedded linux operate directly on top of Framebuffer. The following approaches can be used for creating graphical user interfaces on top of framebuffer: in-house libraries, EGL (Embedded OpenGL), SDL or Qt toolkit on top of frambebuffer.
- Although Android operating system is based on Linux kernel, unlike the desktop GNU/Linux distribution counterparts, Android user interface is built directly on top of framebuffer.
- It is not worth developing any application on top of the X-Windows System Protocol as it is low level, lacks widget abstractions and may eventually be replaced by Wayland on major Linux distributions. A better approach is to use higher level libraries such as Qt Widgets, Qt/Quick QML, KDE Libraries (Qt variants), Gtk, Gnome (Gtk variants) or Java Swing as any application built on top of those libraries will be able to work in any operating system using Wayland.
- Despite being network transparent, X11 protocol is unsafe for usage over the internet due to the protocol be unencrypted which makes it vulnerable to MITM, snooping, spoofing and so on. A workaround for safer usage over the network is to use X11 via SSH (Secure Shell) forwarding. Note: On most Linux Distributions and BSD variants, X11 server does not use TCP/IP connections, instead it uses only Unix-domain sockets.
- Another problem that affects X11 servers is the _lack of isolation of X11 clients. Any remote X11 client or a x11 client running a container connected to the current X11 server is able to take screenshots, capture type keys and list windows. Some solutions for this isolation issue are: 1 - Only connect to trusted remote x11 client applications; replace X11 with VNC or RDP - Remote Desktop and etc; use Xephyr x11 server that runs as a X11 window for isolating the current x11 server.
X11 Server and Clients
User Input keyboard, mouse ... | | \ / +-------------------------+ Display <---- Framebuffer <--- | X11 Server | <<----- (X Client 2) (User's monitor) | on user local machine | Java Swing | | Application +-----------+-------------+ running on remote / \ machine and connected | via TCP/IP socket. | (X Client 1) Firefox Browser on user local machine and connected via Unix domain sockets
See also:
X11 X Windows System
- X Window System Internals
- The DISPLAY Environment Variable (Unix Power Tools, 3rd Edition)
- xorg - What is the $DISPLAY environment variable? - Ask Ubuntu
- How many ways to install an Xserver
- LWN - X and Steam OS
- LWN - X11 wire-level analysis with x11vis
- Running X11 software in LXD containers
- How X Window Managers Work, And How To Write One (Part I)
- How X Window Managers Work, And How To Write One (Part II)
- How to run X applications without a desktop or WM (WM - Windows Manager)
- deleted code is tested code: on abandoning the X server
- It's Time To Admit It: The X.Org Server Is Abandonware - Phoronix
- The Linux Security Circus: On GUI isolation
Wayland (Future Replacemente for X11)
- Introduction - The Wayland Protocol
- Wayland - Freedesktop Foundation
- XWayland - Freedesktop Foundation - Backward Compatibility layer for X11.
- GitHub - varmd/wine-wayland - Wine-wayland allows playing DX9/DX11 and Vulkan games using pure wayland and Wine/DXVK.
X11 client alternative Implementations:
- python-xlib (Python) - Alternative implentation for X11 client in pure Python.
- xgb (Go, Golang) - Alternative implementation for X11 client in pure GO, aka Golang language.
1.32.2 X11 Utilities
The following X11 utilities are widely used on many Linux distributions for a wide variety of purposes, including configuration, window manipulation, debugging and desktop automation.
Authentication and authorization:
- xinit - Program that allows starting Xorg (X Server) manually from command line.
- xauth - Display X server authorization information.
- xhost - Arch wiki: "The xhost program is used to add and delete host names or user names to the list allowed to make connections to the X server. In the case of hosts, this provides a rudimentary form of privacy control and security. It is only sufficient for a workstation (single user) environment, although it does limit the worst abuses. Environments which require more sophisticated measures should implement the user-based mechanism or use the hooks in the protocol for passing other authentication data to the server."
Monitor Configuration:
- xrandr - utility for configuring screen/monitor orientation, resolution, dual monitor and so on. By default, it shows the current displays resolutions.
- xcalib - Allows inverting monitor/screen color. Useful for
avoiding eye strain due to long hours starring on screens.
- Usage: $ xcalib -i -a
- xrandr-invert-colors - Similar to xcalib, but works with multiple screens. Note: it is not available on most Linux distributions, thus this utility must be installed from source.
Window manipulation and Desktop Automation:
- xdotool - Utility application for manipulating and automating x11 windows, similar to Microsft Windows' Autohotkey tool for desktop automation.
- wmctrl - A command line tool to interact with an EWMH/NetWM compatible X Window Manager.
- ydotool - Similar to xdotool, but for Wayland.
- xclip - Copy and paste from/to terminal to clipboard.
- Xpra - "Xpra is 'GNU Screen for X': it allows you to run X programs, usually on a remote host, direct their display to your local machine, and then to disconnect from these programs and reconnect from the same or another machine, without losing any state. It gives you remote access to individual applications."
- xmessage => Graphical Message box notification
- Examle: $ xmsessage -center "Some message"
General Utilities:
- xterm - Reference implementation for X11 terminal emulators.
- xkill - command line application that allows forcefully terminating any graphical program by calling $ xkill from command line and clicking at the window whose process will be terminated.
- xdg-open - Open file or directory from command line with default system application. For instance, $ xdg-open file.xls, where file.xls is a Excel spreadsheet, opens the file with Libreoffice, if this application is available in the sytem.
- xclip - Allows copying stdin contents to clipboard and pasting contents from clibpboard to stdout.
X11 Preference and configuration:
- xset - X-Server user preference and display power management.
- Disable display power management and stop screen from blanking:
- $ xset dpms 0 0 0 && xset s noblank && xset s off
- Disable display power management and stop screen from blanking:
- xmodmap - Modify keymaps and pointer buttons mapping in X Windows System X11.
- setxkbmap - Modify Keyboard - allows changing the keyboard to English, Portuguese, Greek, Cyrillic and so on.
- xinput - Configure and test X input devices such as keyboard, mouse, touchpad and etc.
- xev (X event monitor) "Xev creates a window and then asks the X server to send it events whenever anything happens to the window (such as it being moved, resized, typed in, clicked in, etc.). You can also attach it to an existing window. It is useful for seeing what causes events to occur and to display the information that they contain; it is essentially a debugging and development tool, and should not be needed in normal usage."
- xwinfo - Utility for getting display information about windows. It provides a cursor that shows information about any window that user clicks at.
- xsetroot - Root window parametter settings.
- xprop - Similar to xwinfo, but displays font property.
- xvinfo - Display capabilities of video adapters.
- xdriinfo - Get confituration of DRI (direct rendering interface) device drivers.
X11 protocol message tracing/monitor tools (Note: most non-standard tools, non commonly available in most distros.)
- xscope - "XSCOPE is a program to monitor the connections between the X11 window server and a client program. xscope runs as a separate process. By adjusting the host and/or display number that a X11 client attaches to, the client is attached to xscope instead of X11. xscope attaches to X11 as if it were the client. All bytes from the client are sent to xscope which passes them on to X11; All bytes from X11 are sent to xscope which sends them on to the client. xscope is transparent to the client and X11.".
- x11vis - "x11vis aims to be a useful tool for debugging X11 clients. Its main focus is on being as visual and interactive as possible. Replacing X11 atom, window, font or pixmap IDs with human readable names is the most basic functionality. Contrary to other tools like xtrace, x11vis will display human readable IDs on all requests, no matter at which point in time the information gets available. To help you cope with lots of data, x11vis displays only the most important information by default, automatically folds sequences of boring requests, allows you to filter by packet type or client and provides so-called markers between you can navigate. Also, instead of just assigning a number to each connection, x11vis displays the command line with which that client was started. To learn more, please see the manual.".
- xmon - "A monitor for the X Window System core protocol. Xmon monitors the messages sent between an X11 server and a number of X11 clients and displays the contents of the messages in an interactively selectable level of detail."
- xtruss - Tool for tracing/monitoring X11 network protocol.
- Wireshark - Tool that allows debugging and tracing any network protocol and also allows inspecting USB protocol. (Note: scriptable in Lua programming language.)
Animation:
1.32.3 Setting keyboard mode via setxkbmap
There many cases where there is the need for changing the keyboard settings, such as testing a Live-CD linux distribution, that oftens uses the American keyboard settings by default, even though the keyboard model is non-English; type in languages that does not uses the lantin script or practice foreign languages that uses a different type of keyboard configuration. The command line tool setxkbmap is useful for setting the keyboard language mode in X11 X Windows System environment.
Note: The setxkmap does not work on Wayland environment. This tool only works on X11 (X Windows System).
Some commands for changing keyboard layout:
- Reset Capslock/Escape/Ctrl customization
- $ setxkbmap -option
- Swap Ctrl and Capslock (Friendly to Emacs users):
- $ setxkbmap -option ctrl:swapcaps
- Swap ESC (Escape) and Capslock (Friendly to Vi Users):
- $ setxkbmap -option caps:swapescape
- APL Keyboard Layout (for APL Programming Language)
- $ setxkbmap apl
- American English keyboard layout:
- $ setxkbmap us
- American English keyboard layout with dvorak keyboard:
- $ setxkbmap us -variant dvorak
- American and Greek keyboard layouts. (use-case: typing math formulas)
- $ setxkbmap -layout us,gr -option grp:alt_shift_toggle
- Type Shift + Alt for toggling the layout to Greek keyboard layout. This configuration is useful for typing math formulas.
- American and Greek keyboard layout. (use-case: typing math formulas)
- $ setxkbmap -layout us,gr -option grp:win_switch
- Hold Windows-key and the corresponding key to type in Greek. For instance, by holding Windows-Key and typing A, the letter alpha (α) is typed instead of (a). If the same steps is applied to the letter (b), the letter beta (β) is typed. Finally, release the Windows key for switching back to American keyboard layout.
- American English and APL keyboard Layout (use-case: type APL symbols)
- $ setxkbmap -layout us,apl -option grp:win_switch
- French language keyboard layout:
- $ setxkbmap fr
- German language keyboard layout.
- $ setxkbmap de
- Note: German umlaut letters (ö, ä, ü, ß) are hard to type without this layout.)
- Greek language keyboard layout: (α, β, η, κ, λ, ς, ε, σ, Σ)
- $ setxkbmap gr
- APL keyboard Layout (For APL programming language)
- $ setxkbmap apl
- APL symbols, such as ⍺, ⌈, ⌊, ⊂, ⊃, ∩, ⊥, ⊤, ○,≠,∇ ,are hard to type without this layout.
- Polish languag keyboard layout
- $ setxkbmap pl
- Bulgarian language keyboard layout (Cyrillic Script):
- $ setxkbmap bg
- Ukranian language keyboard layout (Cyrillic Script):
- $ setxkmap ua
- Russian language keyboard layout (Cyrillic Script):
- $ setxkmap ru
- Hebrew language keyboard layout:
- $ setxkbmap il
- Turkısh Language keyboard layout:
- $ setxkbmap tr
- Spanish language - Latin America keyboard layout:
- $ setxkbmap latam
- Makes easier to type Spanish grammar symbols (?, ¿, ¡, ñ)
- Spanish/English keyboard layout:
- $ /setxkbmap -layout latam,us -option grp:alt_shift_toggle
- Portuguese language keyboard layout
- $ setxkbmap br
- Portuguese language and APL keyboard layouts, switching layout is similar to American keyboard.
- $ setxkbmap -layout br,apl -option grp:win_switch
- Portuguese language and Greek keyboard layouts (For typing math formulas)
- $ setxkbmap -layout br,gr -option grp:win_switch
- Portuguse/American English keyboard layout: (Use-case: programming)
- $ setxkbmap -layout br,us -option grp:alt_shift_toggle
- Note: The American English keyboard layout is more convenient for programming due to characters such as quotes, double quotes, colon, semicolon, slash (/ and \) and bar be closer to the left fingers.
See also:
1.32.4 Sample X11 Window via Xlib
This sample Xlib applications draw a window with a rectangle and a string. Note: Xlib lacks widgets.
Code available at:
File: CMakeLists.txt
cmake_minimum_required(VERSION 3.9) project(Simple_Cmake_Project) #========== Global Configurations =============# #----------------------------------------------# set(CMAKE_CXX_STANDARD 17) set(CMAKE_VERBOSE_MAKEFILE ON) find_package(X11 REQUIRED) #========== Targets Configurations ============# add_executable( xlib-demo xlib-demo.cpp ) target_link_libraries( xlib-demo ${X11_LIBRARIES} ) # add_executable(app2 application2.cpp)
File: xlib-demo.cpp
#include <iostream> #include <string> #include <cstring> #include <cassert> #include <X11/Xlib.h> #include <cstdlib> void abort_on_failure(bool predicate, const char* message = "Abort runtime.") { if(predicate) return; std::fprintf(stderr, " [ABORT] %s", message); abort(); } int main(int argc, char** argv) { /** If the value passed to XOpenDisplay is null, the XClient (Xlib) attempts * to connect to the display server designated by the environment variable * $DISPLAY, which is often set to the :1.0 or the local machine. *------------------------------------------------------------------------*/ Display* dpy = XOpenDisplay(nullptr); abort_on_failure(dpy != nullptr, "Cannot open display."); /** ----- Query Information about display server ------------------------*/ int version = XProtocolVersion(dpy); std::cout << " [INFO] X11 protocol version = " << version << '\n'; const char* vendor = XServerVendor(dpy); std::cout << " [INFO] X11 XServer vendor = " << vendor << '\n'; const char* disp_str = XDisplayString(dpy); std::cout << " [INFO] X11 Display string = " << disp_str << '\n'; int n_screen = XScreenCount(dpy); std::cout << " [INFO] Number of screens = " << n_screen << '\n'; /** ----- Draw Screen ------------------------------------------------*/ int screen = DefaultScreen(dpy); Window wnd = XCreateSimpleWindow( dpy // Display , RootWindow(dpy, screen) // Parent Window , 10, 20 // Position (x - horizontal, y - vertical) from top-left corner , 400, 500 // Dimensions (width, height) , 1 // Border width , BlackPixel(dpy, screen) // Border , WhitePixel(dpy, screen) // Background color ); /* Casting void means that the return value (type int) is being ignored. */ (void) XStoreName(dpy, wnd, "X Windows-System X11"); // Makes window visible XMapWindow(dpy, wnd); XFlush(dpy); XEvent event; // Select events that the application is interested in. // Only those events will notify the application. (XClient) auto mask = ExposureMask | ButtonPressMask // Event: mouse button press | KeyPressMask // Event: key press (keyboard) | KeyReleaseMask // Event: key release (keyboard) //| PointerMotionMask // Event: Mouse Move ; XSelectInput(dpy, wnd, mask); // Process window close event in X11 event loop for(;;) Atom wnd_del = XInternAtom(dpy, "WM_DELETE_WINDOW", 0); XSetWMProtocols(dpy, wnd, &wnd_del, 1); const char* message = "Draw some string on X11 - X windows system."; /* ------- X11 (X Windows System) - Event loop ------------ */ for(;;) { std::fprintf(stderr, "\n [TRACE] Waiting event. \n");; // Get next X11 event XNextEvent(dpy, &event); if(event.type == ClientMessage){ fprintf(stderr, " [Event] User closed window. => Shutdown \n"); break; } if(event.type == Expose) fprintf(stderr, " [Event] Expose event. \n"); if(event.type == KeyPress) fprintf(stderr, " [Event] Key pressed. \n"); if(event.type == ButtonPress) fprintf(stderr, " [Event] Mouse button pressed. \n"); if(event.type == PointerMotionMask) fprintf(stderr, " [Event] Mouse button pressed. \n"); // X11 Graphics context, useful for drawing GC gctx = DefaultGC(dpy, screen); XFillRectangle( dpy, wnd, gctx , 20, 50 // (x, y) position from top-left corner origin , 100, 200 // width and height ); GC gctx2 = DefaultGC(dpy, screen); XDrawString( dpy // Pointer to display object , wnd // Pointer to window , gctx // Graphics context , 90, 300 // (x, y) Position where string will be drawn (from top-left corner) , message , strlen(message) ); } // ----- End of event loop -------- // fprintf(stderr, " [TRACE] User closed window. Shutdown application. Ok \n"); XDestroyWindow(dpy, wnd); // Always close display XCloseDisplay(dpy); return 0; }
Building:
$ git clone https://gist.github.com/f7f0b892011ad987bfed531336dbae89 temp && cd temp
$ cmake -H. -B_build -DCMAKE_BUILD_TYPE=Debug -G Ninja
$ cmake --build _build --target all
[2/2] Linking CXX executable xlib-demo
Running:
$ >> _build/xlib-demo [INFO] X11 protocol version = 11 [INFO] X11 XServer vendor = Fedora Project [INFO] X11 Display string = :1.0 [INFO] Number of screens = 1 [TRACE] Waiting event. [Event] Expose event. [TRACE] Waiting event. [TRACE] Waiting event. [Event] Mouse button pressed. ... ... ... ... ... ... ... ... ... ... ... ... ... ...
User Interface Screenshot:
1.32.5 Query X11 information via Xlib
The following sample application uses Xlib for querying X11 windows, window information and terminating the Window process.
Documentation of relevant functions and further reading:
- Xlib - C Language X Interface
- Remlab: I hate Xlib and so should you
- How to kill a process tree programmatically on Linux using C
- Xlib Programming Manual: Closing the Display
- Xlib Programming Manual: XQueryTree
- Xlib Programming Manual: XFetchName
- Xlib Programming Manual: XGetWindowAttributes
Files
All files available at:
File: CMakeLists.txt
cmake_minimum_required(VERSION 3.9) project(X11-Xtoll-Project) #========== Global Configurations =============# #----------------------------------------------# set(CMAKE_CXX_STANDARD 17) set(CMAKE_VERBOSE_MAKEFILE ON) find_package(X11 REQUIRED) #========== Targets Configurations ============# add_executable( x11-tool x11-tool.cpp ) target_link_libraries( x11-tool ${X11_LIBRARIES} )
File: x11-tool.cpp
#include <iostream> #include <string> #include <cstring> #include <cassert> // ------ Unix Headers (needed by read_symlink) ------// #include <unistd.h> #include <limits.h> #include <sys/types.h> #include <signal.h> // ------ X11 Headers ------// #include <X11/Xlib.h> #include <X11/Xatom.h> #include <cstdlib> std::string read_symlink (std::string const& path); int x11_get_property_int32(const char* property_name, Display* dpy, Window root_window); std::string x11_get_property_str (const char* property_name, Display* dpy, Window root_window); template<typename Window_Consumer> void x11_iterate_child_windows( Display* dpy , Window root_window , Window_Consumer&& consumer); std::string x11_window_name(Display* dpy, Window wnd); int x11_window_get_pid(Display* dpy, Window wnd); bool x11_show_window_attributes(Display* dpy, Window wnd); void x11_show_window_properties(Display* dpy, Window wnd); int main(int argc, char** argv) { // Print booleans as 'true' or 'false' instead of 1 or 0 std::cout << std::boolalpha; Display* dpy = XOpenDisplay(nullptr); // Placeholder for future error checking in non-prototype code. assert( dpy != nullptr && "Cannot open display" ); const char* disp_str = XDisplayString(dpy); assert( disp_str != nullptr && "Failed to get display string" ); std::cout << " [INFO] X11 Display string = " << disp_str << '\n'; // Get root window Window root = DefaultRootWindow(dpy); //assert( root != nullptr && "Failed to get root window"); if(argc < 2){ std::cerr << " [USAGE] $ " << argv[0] << " <COMMAND> " << '\n'; return EXIT_FAILURE; } auto command = std::string{argv[1]}; // List all windows and their window ids (unique identifiers) if(command == "list") { x11_iterate_child_windows(dpy, root,[](Display* dpy, Window wnd, int index) { if( !x11_show_window_attributes(dpy, wnd)) { return; } x11_show_window_properties(dpy, wnd); }); return EXIT_SUCCESS; } if(command == "winfo") { // WARNING: Throws std::invalid_argument exception on parsing failure. int window_id = std::stoi(argv[2]); x11_show_window_attributes(dpy, window_id); x11_show_window_properties(dpy, window_id); return EXIT_SUCCESS; } // Kill window given its unique identifier (window id) if(command == "kill") { // WARNING: Throws std::invalid_argument exception on parsing failure. int window_id = std::stoi(argv[2]); // Attempt to get window process ID int pid = x11_window_get_pid(dpy, window_id); if(pid < 0){ std::cerr << " [ERROR] Unable to determine window process ID" << '\n'; return EXIT_FAILURE; } std::cout << " Kill process with PID = " << pid << '\n'; // Force process termination kill(pid, SIGTERM); kill(pid, SIGKILL); return EXIT_SUCCESS; } XCloseDisplay(dpy); return 0; } /*================================================================*\ * I M P L E M E N T A T I O N S * *================================================================*/ // Get absolute path to file or symbolic link // Requires: #<unistd.h>, <limits.h> std::string read_symlink(std::string const& path) { // Create a buffer with size PATH_MAX + 1 filled with 0 ('\0'), null characters std::string buffer(PATH_MAX, 0); // ssize_t readlink(const char *pathname, char *buf, size_t bufsiz); ssize_t nread = ::readlink(path.c_str(), &buffer[0], PATH_MAX); if(nread == -1){ throw std::runtime_error("Error: unable to read symbolic link"); } buffer.resize(nread); return buffer; } int x11_get_property_int32(const char* property_name, Display* dpy, Window root_window) { Atom p = XInternAtom(dpy, property_name, false); Atom actual_type; int format; unsigned long num_items, bytes_after; unsigned char* data = nullptr; int status = XGetWindowProperty( dpy, root_window, p, 0L, 1024L , false, XA_CARDINAL, &actual_type , &format, &num_items, &bytes_after , &data ); if( status != 0 || num_items < 1) { //throw std::runtime_error("Error: failed to get property"); return -1; } int value = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); XFree(data); return value; } std::string x11_get_property_str(const char* property_name, Display* dpy, Window root_window) { Atom p = XInternAtom(dpy, property_name, false); Atom actual_type; int format; unsigned long num_items, bytes_after; unsigned char* data = nullptr; int status = XGetWindowProperty( dpy, root_window, p, 0L, 1024L , false, XA_STRING, &actual_type , &format, &num_items, &bytes_after , &data ); if( status != 0 || num_items < 1) { //throw std::runtime_error("Error: failed to get property"); return "<NONE>"; } std::string value(data, data + num_items); XFree(data); return value; } template<typename Window_Consumer> void x11_iterate_child_windows( Display* dpy ,Window root_window ,Window_Consumer&& consumer) { // ----- Return parameters of function XQueryTree() -------// // Note: the memory is allocated and owned by the calling code. // function. // Note: '{}' curly brackets gurantees default initialization as 'nullptr' Window root_return{}; Window parent_return{}; // List of childre window to be returned Window* children_return{}; // Number of children windows unsigned int nchildren_return{}; int result = XQueryTree( dpy // Display , root_window // Root window , &root_return // Root window return , &parent_return // Return paramameter for pointer to parent window , &children_return // List of children to be returned , &nchildren_return // Number of children elements in (children_return ) ) ; assert( result != 0 && "Failed to query windows"); std::cout << " [INFO] Number of children windows = " << nchildren_return << '\n'; for(auto n = 0; n < nchildren_return; n++ ) { consumer(dpy, children_return[n], n); } // Dispose, release memory allocated for children windows return XFree(children_return); } // Get Window name std::string x11_window_name(Display* dpy, Window wnd) { char* window_name; if( !XFetchName(dpy, wnd, &window_name) ) { return ""; } auto output = std::string{window_name}; XFree(window_name); return output; } // Note: It only works for X-client applications running on local machine. int x11_window_get_pid(Display* dpy, Window wnd) { return x11_get_property_int32("_NET_WM_PID", dpy, wnd); } bool x11_show_window_attributes(Display* dpy, Window wnd) { XWindowAttributes attr; if( !XGetWindowAttributes(dpy, wnd, &attr) ) { std::fprintf(stderr, "Error, failed to get window (wnd = %zu) attribute \n" , wnd); //exit(EXIT_FAILURE); return false; } auto window_name = x11_window_name(dpy, wnd); if(window_name == ""){ return false; } auto is_visible = attr.map_state == IsViewable; std::cout << "\n\n [INFO] " << " ; window_id = " << wnd << " ; Visible = " << is_visible << " ; Width = " << attr.width << " ; Height = " << attr.height << '\n'; std::cout << " => Window Name = " << window_name << '\n'; return true; } void x11_show_window_properties(Display* dpy, Window wnd) { int pid = x11_get_property_int32("_NET_WM_PID", dpy, wnd); std::cout << " => Window PID (Process ID) = " << pid << '\n'; try { auto path_to_executable_procfs = std::string("/proc/") + std::to_string(pid) + "/exe"; std::cout << " => Window program (executable) = " << read_symlink(path_to_executable_procfs) << '\n'; } catch(const std::exception& ex) { std::cerr << " [ERROR] " << ex.what() << '\n'; } std::cout << " => WM_NAME = " << x11_get_property_str("WM_NAME", dpy, wnd) << '\n'; std::cout << " => WM_CLASS = " << x11_get_property_str("WM_CLASS", dpy, wnd) << '\n'; std::cout << " => WM_COMMAND = " << x11_get_property_str("WM_COMMAND", dpy, wnd) << '\n'; // Hostname of machine where the window application is located. // A window may belong to an application (X-client) running in a // remote machine. std::cout << " => WM_CLIENT_MACHINE = " << x11_get_property_str("WM_CLIENT_MACHINE", dpy, wnd) << '\n'; }
Building:
$ git clone https://gist.github.com/639df76864d014ead12936fbd361be73 gist && cd gist
$ cmake -H. -B_build -DCMAKE_BUILD_TYPE=Debug
$ cmake --build _build --target all
$ >> _build/x11-tool
[INFO] X11 Display string = :1.0
[USAGE] $ _build/x11-tool <COMMAND>
List Windows:
$ >> _build/x11-tool list [INFO] X11 Display string = :1.0 [INFO] Number of children windows = 116 [INFO] ; window_id = 2097153 ; Visible = false ; Width = 10 ; Height = 10 => Window Name = xfce4-session => Window PID (Process ID) = 1715 => Window program (executable) = /usr/bin/xfce4-session => WM_NAME = xfce4-session => WM_CLASS = xfce4-sessionXfce4-session => WM_COMMAND = xfce4-session => WM_CLIENT_MACHINE = localhost.localdomain ... .... ... .... ... .... ... .... ... .... ... .... [INFO] ; window_id = 69206017 ; Visible = false ; Width = 10 ; Height = 10 => Window Name = Terminal => Window PID (Process ID) = 778969 => Window program (executable) = /usr/libexec/gnome-terminal-server => WM_NAME = Terminal => WM_CLASS = gnome-terminal-serverGnome-terminal-server => WM_COMMAND = gnome-terminal-server => WM_CLIENT_MACHINE = localhost.localdomain [INFO] ; window_id = 69206018 ; Visible = false ; Width = 10 ; Height = 10 => Window Name = Terminal => Window PID (Process ID) = 778969 => Window program (executable) = /usr/libexec/gnome-terminal-server => WM_NAME = Terminal => WM_CLASS = <NONE> => WM_COMMAND = <NONE> => WM_CLIENT_MACHINE = localhost.localdomain
List information for a single window:
$ >> _build/x11-tool winfo 69206017 [INFO] X11 Display string = :1.0 [INFO] ; window_id = 69206017 ; Visible = false ; Width = 10 ; Height = 10 => Window Name = Terminal => Window PID (Process ID) = 778969 => Window program (executable) = /usr/libexec/gnome-terminal-server => WM_NAME = Terminal => WM_CLASS = gnome-terminal-serverGnome-terminal-server => WM_COMMAND = gnome-terminal-server => WM_CLIENT_MACHINE = localhost.localdomain
Check information via xprop x11 utility tools:
$ >> xprop -id 69206017 WM_CLASS(STRING) = "gnome-terminal-server", "Gnome-terminal-server" WM_COMMAND(STRING) = { "gnome-terminal-server" } WM_CLIENT_LEADER(WINDOW): window id # 0x4200001 _NET_WM_PID(CARDINAL) = 778969 WM_LOCALE_NAME(STRING) = "en_US.UTF-8" WM_CLIENT_MACHINE(STRING) = "localhost.localdomain" WM_NORMAL_HINTS(WM_SIZE_HINTS): program specified size: 10 by 10 WM_PROTOCOLS(ATOM): protocols WM_DELETE_WINDOW, WM_TAKE_FOCUS, _NET_WM_PING WM_ICON_NAME(STRING) = "Terminal" _NET_WM_ICON_NAME(UTF8_STRING) = "Terminal" WM_NAME(STRING) = "Terminal" _NET_WM_NAME(UTF8_STRING) = "Terminal" # Get the executable that created this window $ >> readlink -f /proc/778969/exe /usr/libexec/gnome-terminal-server
Terminate process that created the previous window:
$ >> _build/x11-tool kill 69206017 [INFO] X11 Display string = :1.0 Kill process with PID = 778969
1.32.6 Further reading and references
Implementations of X11 Client
- Xlib - C Language X Interface - X Consortium Standard
- XCB X11 Client C Library - Freedeskop foundation
- Python-Xlib Client (alternative inplentations, X11 client in pure Python)
- XGDB (Go, Golang) - Alternative implementation for X11 client in pure GO, aka Golang language.
Implementations of X11 Server
- X Server Source Code / Gitlab [REFERENCE IMPLEMENTATION]
- https://gitlab.freedesktop.org/xorg/xserver
- "The X server accepts requests from client applications to create windows, which are (normally rectangular) "virtual screens" that the client program can draw into. Windows are then composed on the actual screen by the X server (or by a separate composite manager) as directed by the window manager, which usually communicates with the user via graphical controls such as buttons and draggable titlebars and borders."
- The XFree86 - Project Inc - Major implementation of XServer for X11
- https://www.xfree86.org/
- "The XFree86 Project, Inc is a global volunteer organization which produces XFree86®, the freely redistributable open-source implementation of the X Window System continuously since 1992. XFree86 runs primarily on UNIX® and UNIX-like operating systems like Linux, all of the BSD variants, Sun Solaris both native 32 and 64 bit support, Solaris x86, Mac OS X (via Darwin) as well as other platforms like OS/2 and Cygwin. What XFree86 does, is provide a client/server interface between the display hardware (those physical things like the mouse, keyboard, and video displays) and the desktop environment, (this is typically called a window manager as it deals with how X is displayed i.e. the overall appearance). Yet X it goes beyond that and also gives the infrastructure and a standardized application interface (API). All of this which makes XFree86 platform-independent, network-transparent and fully extensible. In short, XFree86 is the premier open source X11-based desktop infrastructure.
- Xvfb (X11 Server with virtual framebuffer)
- X11 Server Implementation that emulates framebuffer using virtual memory, it is useful for machines without any graphics card, GPU or display drivers such as servers using Intel Xeon processors without any GPU. A use-case for this X11 server implementation is running selenium and chrome headless browser for caching SPA (Single Page Applications) or data scraping on server machines without graphics card, GPU and integrated GPUs, such as cloud hosted virtual machines or server machines with Intel Xeon Processor. Another use-case running remote graphical applications hosted in remote servers, without any graphics capability, or in a docker container and access the headless X11 server using X11-VNC tool.
- https://www.x.org/releases/X11R7.6/doc/man/man1/Xvfb.1.xhtml
- https://stackoverflow.com/questions/12050021/how-to-make-xvfb-display-visible
- https://blog.lab26.net/virtual-desktop-vnc-without-x-server/
- Xephyr (X11 server implementation for running X11 clients with isolation)
- https://freedesktop.org/wiki/Software/Xephyr/
- https://elinux.org/images/6/6c/ELC_2019_-_Graphical_Containers_on_Embedded_Presentation.pdf
- https://theinvisiblethings.blogspot.com/2011/04/linux-security-circus-on-gui-isolation.html
- X11 server implementation that solves the lack of isolation security issue related to the original X11 server implementation. This application is useful for isolating X11 clients running in containers or remote servers.
- Note: X11 lack of Isolation problem =>> A remote x11 client connected to the vanilla X11 implementation is not isolated from the user machine, the client application can list all user windows and log anything typed.
- XWayland (Wayland quasi-backward compatible implementation of X11 server)
- https://wayland.freedesktop.org/xserver.html
- Summary: X Server implementation for Microsft Windows NT operating system which allows using remote Linux or BSD graphical applications via X11 X Windows System network protocol.
- Xming X Server for Windows (X11 server for Windows NT)
- https://sourceforge.net/projects/xming/
- http://www.straightrunning.com/XmingNotes/
- Note: It allows accessing graphical applications installed on remote Unix machines from Windows NT desktop.
- X-Win32 (X11 server for Windows NT) [COMMERCIAL]
- https://www.starnet.com/info/xwin32_windows.php
- Summary: "X-Win32 is the most widely used X server on the market. Run Linux on Windows at LAN-like speed. Features: Clarity: Offering picture-perfect rendering, X-Win32 is widely used in IC design; Speed: Outperforms even your local console when rendering 3D graphics; Reliability: X-Win32 has been constantly developed for the last 25 years; Affordable: X-Win32 is affordably priced with discounts for volume, government and educational licenses; Support: Your license and maintenance purchase gives you exclusive access to our fast, responsive support team."
- XQuartz - X11 implementation for Apple's MacOSX
- https://www.xquartz.org/
- Summary: " The XQuartz project is an open-source effort to develop a version of the X.Org X Window System that runs on macOS. Together with supporting libraries and applications, it forms the X11.app that Apple shipped with OS X versions 10.5 through 10.7."
- Microwindows or the Nano-X Window System
- https://github.com/ghaerr/microwindows
- http://www.microwindows.org/
- Summary: "Microwindows or Nano-X is a small graphical windowing system that implements both Win32 and Nano-X (X11-like) APIs for clipped graphics drawing in windows on Linux, Mac OS X, EMSCRIPTEN, Android and other platforms. It is Open Source and licenced under the the Mozilla Public License. For creating GUIs, the Nuklear immediate mode GUI, Win32 builtin controls, and TinyWidget's controls based on Nano-X are included. FLTK can be used with the X11 compability library NX11."
X Windows System
- Documentation for the X Window System - Version 11 Release 7.7 (X11R7.7) [MUST-READ]
- X Window System Protocol - X consortium Standard. [MUST-READ]
- https://www.x.org/releases/X11R7.7/doc/xproto/x11protocol.html
- Summary: Formal specification of X-Windows System Network Protocol.
- X WINDOW SYSTEM PROTOCOL, VERSION 11 - Alpha Update - April 1987
- X Window System core protocol
- https://en.wikipedia.org/wiki/X_Window_System_core_protocol
- "The X Window System core protocol[1][2][3] is the base protocol of the X Window System, which is a networked windowing system for bitmap displays used to build graphical user interfaces on Unix, Unix-like, and other operating systems. The X Window System is based on a client–server model: a single server controls the input/output hardware, such as the screen, the keyboard, and the mouse; all application programs act as clients, interacting with the user and with the other clients via the server. .."
- Telnet Forwarding of X Window System Session Data
- XWindows Utility Description
- Athena Widget Set - C Language Interface (libXaw Version 1.0.13) - X Consortium Standard
- https://www.x.org/releases/current/doc/libXaw/libXaw.html
- "The X Toolkit is made up of two distinct pieces, the Xt Intrinsics and a widget set. The Athena widget set is a sample implementation of a widget set built upon the Intrinsics. In the X Toolkit, a widget is the combination of an X window or subwindow and its associated input and output semantics. Because the Intrinsics provide the same basic functionality to all widget sets it may be possible to use widgets from the Athena widget set with other widget sets based upon the Intrinsics. Since widget sets may also implement private protocols, all functionality may not be available when mixing and matching widget sets. For information about the Intrinsics, see the X Toolkit Intrinsics - C Language Interface. The Athena widget set is a library package layered on top of the Intrinsics and Xlib that provides a set of user interface tools sufficient to build a wide variety of applications. This layer extends the basic abstractions provided by X and provides the next layer of functionality primarily by supplying a cohesive set of sample widgets. Although the Intrinsics are a Consortium standard, there is no standard widget set."
X11 - X Windows System Concepts
- Window manager - Arch Linux Wiki
- https://wiki.archlinux.org/index.php/window_manager
- Summary: Outlines several possible window systems for X Windows Systems, such as 2bwm, 9wm, compiz, blackbox, i3, awesome, qtile, xmonad …
Low Level Graphics Stack - kernel and embedded Linux systems
- Linux Graphics Demystified - Martin Fiedler - Dream Chip Techonologies GMHB
- https://keyj.emphy.de/files/linuxgraphics_en.pdf
- https://web.archive.org/web/20200426101542/https://keyj.emphy.de/files/linuxgraphics_en.pdf
- Summary: Explains about Linux low level graphics stack, including framebuffer, X-Server.
- Linux Framebuffer ( /dev/fb0 )
- https://en.wikipedia.org/wiki/Linux_framebuffer
- "The Linux framebuffer (fbdev) is a graphic hardware-independent abstraction layer to show graphics on a computer monitor, typically on the system console.[1] It allows direct access to the framebuffer (the part of a computer's video memory containing a current video frame) using only the Linux kernel's own basic facilities and its device file system interface."
- Linux Framebuffer - Nicolas Caramelli
- https://github.com/caramelli/higfxback/wiki/Linux-Framebuffer
- Summary: Detailed explanation about Linux kernel framebuffer infrastructure.
- Frmebuffer (Linux) - Toradex Developer resources
- https://developer.toradex.com/knowledge-base/framebuffer-linux
- "The framebuffer (fbdev) is a character device providing access to graphics hardware. Beside the framebuffer, other interfaces exist to access graphics hardware such as the DRI (direct rendering interface) or proprietary interfaces (NVIDIA drivers). Typically, the framebuffer doesn't support any 2D/3D hardware acceleration. …"
- DRI - Direct Rendering Infrastructure
- https://en.wikipedia.org/wiki/Direct_Rendering_Infrastructure
- "The Direct Rendering Infrastructure (DRI) is a framework for allowing direct access to graphics hardware under the X Window System in a safe, efficient way.[6] The main use of DRI is to provide hardware acceleration for the Mesa implementation of OpenGL. DRI has also been adapted to provide OpenGL acceleration on a framebuffer console without a display server running. …"
- Mesa 3D and Direct Rendering Infrastructure wiki
- https://dri.freedesktop.org/wiki/
- "The Direct Rendering Infrastructure, also known as the DRI, is a framework for allowing direct access to graphics hardware under the X Window System in a safe and efficient manner. It includes changes to the X server, to several client libraries, and to the kernel (DRM, Direct Rendering Manager). The most important use for the DRI is to create fast OpenGL implementations providing hardware acceleration for Mesa. Several 3D accelerated drivers have been written to the DRI specification, including drivers for chipsets produced by 3DFX, AMD (formerly ATI), Intel and Matrox."
- Linux GPU Driver Developer's Guide
- https://01.org/linuxgraphics/gfx-docs/drm/gpu/index.html
- "The Linux DRM layer contains code intended to support the needs of complex graphics devices, usually containing programmable pipelines well suited to 3D graphics acceleration. Graphics drivers in the kernel may make use of DRM functions to make tasks like memory management, interrupt handling and DMA easier, and provide a uniform interface to applications. A note on versions: this guide covers features found in the DRM tree, including the TTM memory manager, output configuration and mode setting, and the new vblank internals, in addition to all the regular features found in current kernels."
- Displaying and Rendering Graphics with Linux Training - Bootlin
- Inside Linux - graphics Understanding the components, upgrading drivers, and advanced use cases - Intel
- EGL (API) - Embedded OpenGL
- Qt for Embedded Linux (Also covers Qt directly on top of Framebuffer device)
- The Virtual Framebuffer - Qt Company
- https://doc.qt.io/archives/qt-4.8//qvfb.html
- "Qt for Embedded Linux applications write directly to the framebuffer, eliminating the need for the X Window System and saving memory. For development and debugging purposes, a virtual framebuffer can be used, allowing Qt for Embedded Linux programs to be developed on a desktop machine, without switching between consoles and X11."
- Writing to the Framebuffer - Seena Burns
- Writing to the Framebuffer (Forum Discussion)
- The DRM/KMS subsystem from a newbie’s point of view - Boris Brezilion
- DRM Driver Development For Embedded Systems - Inki Dae, Software Platform Lab - Samsung
- drm - Direct Rendering Manager (DRI kernel support) - Device Drivers Manual (NetBSD)
- CS574 presentation: Linux OpenGL drivers: Direct Rendering Infrastructure
- Platform specifics: Linux - OpenGL - Kronos Group
- https://www.khronos.org/opengl/wiki/Platform_specifics:_Linux
- "This Section explains how to install Drivers to make OpenGL Programs run under Linux and how to use different Libraries/Toolkits to create Opengl Programs. Mesa is an open-source OpenGL implementation, continually updated to support the latest OpenGL specification. Direct Rendering Infrastructure, also known as the DRI, is a framework for allowing direct access to graphics hardware under the X Window System in a safe and efficient manner."
- DRM(Direct Rendering Manager) of Tizen Kernel
- https://download.tizen.org/misc/media/conference2012/wednesday/ballroom-c/2012-05-09-1330-1410-the_drm_(direct_rendering_manager)_of_tizen_kernel.pdf
- https://web.archive.org/web/20161109195212/http://download.tizen.org/misc/media/conference2012/wednesday/ballroom-c/2012-05-09-1330-1410-the_drm_(direct_rendering_manager)_of_tizen_kernel.pdf
- Direct Rendering Infrastructure: Architecture - José Manual Rios Fonseca
Wayland (Note: Eventually may replace X Windows System)
- Wayland v/s Xorg : How Are They Similar & How Are They Different
- Wayland (display server protocol)
- Book: The Wayland Protocol
- https://wayland-book.com/introduction.html
- "Wayland is the next-generation display server for Unix-like systems, designed and built by the alumni of the venerable Xorg server, and is the best way to get your application windows onto your user's screens. Readers who have worked with X11 in the past will be pleasantly surprised by Wayland's improvements, and those who are new to graphics on Unix will find it a flexible and powerful system for building graphical applications and desktops. This book will help you establish a firm understanding of the concepts, design, and implementation of Wayland, and equip you with the tools to build your own Wayland client and server applications. Over the course of your reading, we'll build a mental model of Wayland and establish the rationale that went into its design. Within these pages you should find many "aha!" moments as the intuitive design choices of Wayland become clear, which should help to keep the pages turning. Welcome to the future of open source graphics!"