So tell me what you're aiming for with this system? I'm impressed that you've written an os, but apart from the fun of it, what advantages does it have?
comments
I would do the loop instead the if condition, where letter != 'Y' or 'N' generally speaking, using while(1) for x86_64 programming is bad practice, specific cases like embedded system design is where it's more commonly used.
No problem, If you have any further questions about my or your code, feel free to ask.
That's so kind of youπ, please don't feel like you have to, was a pleasure to help, my ko-fi: https://ko-fi.com/hkwln
I'd recommend not to alias compilers like that. Rather, if you're interested, you could just use a simple Makefile like shown below and get it over with. Save it into a file called Makefile (yes, uppercase M followed by akefile all lowercase) in the same directory as your program.c file.
Note, I have used -std=c89 but you can delete that line or maybe even use a more modern standard such as -std=c23 if you like. If you delete, both gcc and clang will default to the latest ISO standard supported by that version of compiler.
Also since you're compiling only a single file, I have used program.c as input producing program executable on UNIX. To run this Makefile you need to invoke make from the same directory as Makefile on command-line as:
make W=1
to enable all the warnings and compile. If you set W=0 it will not warn or even print any diagnostic unless compiler really cannot generate the executable. You can also pass D=0 for optimised/release build, D=1 for debug build so you can backtrack in gdb. You can also pass A=1 if you want address sanitisers enabled. It will report if your program is touching or even looking at pointers/memory regions the wrong way, leaking memory or just generally doing operations that cannot guarantee correct behaviour. Passing V=1 will display the exact command executed to compile, otherwise it will silently compile.
Here's the full Makefile I use:
# Control build verbosity.
V ?= 0
ifeq ($(V),1)
Q :=
else
Q := @
endif
# Control DEBUG (1) or RELEASE (0) build.
D ?= 1
# Add some warning flags?
W ?= 0
# Add some address/undefined sanitiser flags?
A ?= 1
ifeq ($(shell $(CC) -v 2>&1 | grep -c "gcc version"),1)
COMPILER := gnu
else ifeq ($(shell $(CC) -v 2>&1 | grep -c "clang version"),1)
COMPILER := llvm
endif
# Delete environment garbage so we can generate absolutely controlled
LDFLAGS :=
CFLAGS :=
ifeq ($(D),1)
CFLAGS_DEFS += -D_DEBUG
else
CFLAGS_DEFS += -DNDEBUG
endif
CFLAGS_DEFS += -D_XOPEN_SOURCE=600
ifeq ($(D),0)
CFLAGS_DEFS += -D_FORTIFY_SOURCE=2
endif
CFLAGS_DEFS += -D_LARGEFILE64_SOURCE=1
CFLAGS_DEFS += -D_LARGEFILE_SOURCE=1
ifeq ($(W),1)
CFLAGS_STDS += -std=c89
CFLAGS_STDS += -Wpedantic
CFLAGS_STDS += -pedantic-errors
CFLAGS_STDS += -Werror
CFLAGS_STDS += -Wall
CFLAGS_STDS += -Wextra
ifeq ($(COMPILER),gnu)
CFLAGS_STDS += -Wbad-function-cast
CFLAGS_STDS += -Wcast-align
CFLAGS_STDS += -Wcast-qual
CFLAGS_STDS += -Wduplicated-branches
CFLAGS_STDS += -Wduplicated-cond
CFLAGS_STDS += -Wfloat-equal
CFLAGS_STDS += -Wformat-nonliteral
CFLAGS_STDS += -Wformat-security
CFLAGS_STDS += -Wformat-signedness
CFLAGS_STDS += -Wformat-truncation=2
CFLAGS_STDS += -Wformat=2
CFLAGS_STDS += -Winline
CFLAGS_STDS += -Wlogical-op
CFLAGS_STDS += -Wmissing-declarations
CFLAGS_STDS += -Wmissing-format-attribute
CFLAGS_STDS += -Wmissing-include-dirs
CFLAGS_STDS += -Wmissing-noreturn
CFLAGS_STDS += -Wmissing-prototypes
CFLAGS_STDS += -Wnested-externs
CFLAGS_STDS += -Wno-unused-result
CFLAGS_STDS += -Wnull-dereference
CFLAGS_STDS += -Wold-style-definition
CFLAGS_STDS += -Wpointer-arith
CFLAGS_STDS += -Wpointer-sign
CFLAGS_STDS += -Wredundant-decls
CFLAGS_STDS += -Wrestrict
CFLAGS_STDS += -Wreturn-type
CFLAGS_STDS += -Wshadow
CFLAGS_STDS += -Wsign-compare
CFLAGS_STDS += -Wsign-conversion
CFLAGS_STDS += -Wstrict-aliasing
CFLAGS_STDS += -Wstrict-prototypes
CFLAGS_STDS += -Wswitch-enum
CFLAGS_STDS += -Wtrampolines
CFLAGS_STDS += -Wundef
CFLAGS_STDS += -Wuninitialized
CFLAGS_STDS += -Wunreachable-code
CFLAGS_STDS += -Wunused
CFLAGS_STDS += -Wunused-but-set-parameter
CFLAGS_STDS += -Wunused-but-set-variable
CFLAGS_STDS += -Wunused-label
CFLAGS_STDS += -Wunused-local-typedefs
CFLAGS_STDS += -Wunused-parameter
CFLAGS_STDS += -Wunused-variable
CFLAGS_STDS += -Wwrite-strings
else
CFLAGS_STDS += -Wmost
CFLAGS_STDS += -Warray-bounds-pointer-arithmetic
CFLAGS_STDS += -Wassign-enum
CFLAGS_STDS += -Wcomma
CFLAGS_STDS += -Wconditional-uninitialized
CFLAGS_STDS += -Wformat-type-confusion
CFLAGS_STDS += -Widiomatic-parentheses
CFLAGS_STDS += -Wloop-analysis
CFLAGS_STDS += -Wshift-sign-overflow
CFLAGS_STDS += -Wshorten-64-to-32
CFLAGS_STDS += -Wstrict-aliasing=2
CFLAGS_STDS += -Wstrict-overflow=5
CFLAGS_STDS += -Wtautological-constant-in-range-compare
CFLAGS_STDS += -Wthread-safety
CFLAGS_STDS += -Wunreachable-code-aggressive
CFLAGS_STDS += -Wunused
CFLAGS_STDS += -Wunused-argument
CFLAGS_STDS += -Wunused-but-set-parameter
CFLAGS_STDS += -Wunused-but-set-variable
CFLAGS_STDS += -Wunused-comparison
CFLAGS_STDS += -Wunused-const-variable
CFLAGS_STDS += -Wunused-exception-parameter
CFLAGS_STDS += -Wunused-function
CFLAGS_STDS += -Wunused-label
CFLAGS_STDS += -Wunused-local-typedefs
CFLAGS_STDS += -Wunused-macros
CFLAGS_STDS += -Wunused-parameter
CFLAGS_STDS += -Wunused-value
CFLAGS_STDS += -Wunused-variable
CFLAGS_STDS += -Wunused-volatile-lvalue
endif
else
# Disable all manners of warnings, even default ones.
CFLAGS_STDS += -w
endif
CFLAGS_OPTS += -fPIC
# These are needed anyway.
ifeq ($(D),1)
CFLAGS_OPTS += -O0 -g3 -ggdb3
CFLAGS_OPTS += -fno-omit-frame-pointer
else
CFLAGS_OPTS += -O3
CFLAGS_OPTS += -ffunction-sections
CFLAGS_OPTS += -fdata-sections
CFLAGS_OPTS += -fmerge-all-constants
endif
ifeq ($(A),1)
CFLAGS_OPTS += -fsanitize=address,leak,undefined
endif
# Add more include directories to your liking
CFLAGS_INCS += -I.
# Assemble them all into a singfle
CFLAGS += $(CFLAGS_DEFS)
CFLAGS += $(CFLAGS_INCS)
CFLAGS += $(CFLAGS_STDS)
CFLAGS += $(CFLAGS_OPTS)
LDFLAGS += -Wl,--as-needed
LDFLAGS += -Wl,--gc-sections
LDFLAGS += -Wl,--no-undefined
LDFLAGS += -Wl,-z,defs
LDFLAGS += -Wl,-z,nocombreloc
LDFLAGS += -Wl,-z,now
LDFLAGS += -Wl,-z,relro
# The $(Q)$(CC) has a real TAB before it, not space. If you copy paste from Lemmy, you might end up with spaces.
program: program.c
$(Q)$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
.DEFAULT_GOAL := all
.PHONY: all
all: program
.PHONY: clean
# The space before $(Q)$(RM) is real TAB as well.
clean:
$(Q)$(RM) program
Amazing advice! Thank you so much! I have bookmarked this comment! AND I'll create an alias for clang so it uses all those switches by default. Also, GREAT analogy with the story telling!
For 1, I'd like for you to think you're telling a story, one that is really air tight and unambiguous. If you think putting all the calls in main() can help you tell that story, then so be it.
For 2, of late industrial software development has largely moved to keeping variables as local as possible so its easier to express provenance. This has been the case for at least a good 3-4 decades now.
At no extra cost, I'd like to give you an unsolicited advice. If you're using modern, real C compilers like gcc or clang, I'd strongly recommend you pass -Wpedantic -pedantic-errors -Wall -Wextra on the command line.
gcc -Wpedantic -pedantic-errors -Wall -Wextra -Werror program.c
and if you're using clang, also pass -Wmost along with the rest. Learning C is one thing. But following the standard rules right from the start, it will save you from unexpected grief later on. If you try compiling your code under these flags now, it will refuse to compile. I'd suggest you try fixing as much as you can, then go back to compiling the normal way until you get a hang of things.
Thank you so much for this! I'll have to digest your comments slowly, since I don't have any knowledge of or experience with enums, structs or function pointers. YET. π
Let me know if you have ko-fi or the likes and I'll buy you a cup of coffee!
I think this is more about readability and style. I have looked at your codei and changed a few thingsπ , maybe this helps you. This is how I would do it: (I am as well far from expert level in C)
CODE
#include <limits.h> // added for INT_MIN
#include <stdio.h>
#include <stdlib.h> // added for exit()
#include <string.h>
// you use scanf getchar and fgets, I would choose one
// Function declarations, also chooose your naming convention!
void prompt_choice_main(char *choice);
void prompt_choice_main_again(void);
void prompt_input(void);
int prompt_choice_trim(void);
void (*execute_choice[])(void);
void trim_numbers(void);
void trim_whitespace(void);
void trim_letters(void);
void trim_special(void);
void specify_special(void);
void print_result(char input[]);
// Global variables; I would put as much of these as parameters/structs or
// return values because of clearer ownership and readability
char choice_detail = 0x00;
char choice_special = 0x00;
char input[1000] = "";
char previous_input[1000] = "";
// Remove specific numbers, letters, punctuation or whitespace characters from
// input.
int main() {
printf(
"\nWelcome! This program trims text by removing unwanted characters.\n");
while (1) {
char choice_main = 0x00;
prompt_choice_main(&choice_main);
if (choice_main == 'T') {
prompt_input();
int choice_trim = prompt_choice_trim();
execute_choice[choice_trim]();
print_result(input);
} else if (choice_main == 'P') {
snprintf(input, 1000, "%s",
previous_input); // i would prefer snprintf instead of sprintf
// because of safety
printf("\nYou are trimming previously trimmed text: %s\n", input);
int choice_trim = prompt_choice_trim();
execute_choice[choice_trim]();
print_result(input);
} else if (choice_main == 'E') {
printf("\nGoodbye!\n");
exit(0);
}
}
return 0;
}
// Function definitions
void prompt_choice_main(char *choice_main) {
while (1) {
printf("\nPress T and ENTER to trim text");
if (strlen(previous_input) != 0) {
printf(", P for previously trimmed text ");
}
printf(" or E and ENTER to exit: ");
*choice_main = getchar();
while (getchar() != '\n') {
}
if (*choice_main == 'T' || *choice_main == 'E' || *choice_main == 'P') {
break;
} else {
printf("\nInvalid input!\n");
}
}
}
void prompt_input(void) {
printf("\nEnter the text that you would like to trim and press ENTER: ");
size_t c = 0;
do
input[c++] = getchar();
while (input[c] != '\n');
input[c] = '\0';
}
int prompt_choice_trim(void) {
while (1) {
int choice_trim = 0;
printf("\nWhat would you like to trim?\n1) Numbers (1, 2, 3...)\n2) "
"Whitespace (space, tab or newline) \n3) Letters (A,B,C... "
"a,b,c...)\n4) Special characters (!,?, . , ...)\nType one of the "
"above numbers and press ENTER: ");
char c;
c = getchar();
choice_trim = c - '0';
while (getchar() != '\n') {
}
if (choice_trim < 1 || choice_trim > 4) {
printf("\nInvalid choice!\n");
} else {
return choice_trim - 1;
}
}
// INFO: Through the return type you can do a bit of error handling i you did
// not already now
return INT_MIN;
}
// Here I would use a function pointer, but your version is also perfectly fine
void (*execute_choice[])(void) = {
trim_numbers, // 123 etc
trim_whitespace, // space, tab, newline
trim_letters, // ABC..., abc...
trim_special, // ! ? , . etc.
};
// I would not replace characters with 0x18 but instead keep chars which a valid
// and delete chars which are unvalid in a
void trim_numbers(void) {
size_t w = 0;
for (size_t r = 0; input[r] != '\0'; r++) {
if (input[r] != '0' && input[r] != '1' && input[r] != '2' &&
input[r] != '3' && input[r] != '4' && input[r] != '5' &&
input[r] != '6' && input[r] != '7' && input[r] != '8' &&
input[r] != '9') {
input[w++] = input[r];
}
}
input[w] = '\0';
return;
}
void trim_whitespace(void) {
while (1) {
printf("\nType S to trim SPACE, T to trim TAB, N to trim NEWLINE or A to "
"trim all whitespace: ");
scanf("%c", &choice_detail);
while (getchar() != '\n') {
}
if (choice_detail == 'S' || choice_detail == 'T' || choice_detail == 'A') {
break;
} else {
printf("\nInvalid input!\n");
}
}
int n = 0;
for (n = strlen(input) - 1; n >= 0; n--) {
if (choice_detail == 'S') {
if (input[n] == 0x20) {
input[n] = 0x18;
}
} // space
else if (choice_detail == 'T') {
if (input[n] == 0x09) {
input[n] = 0x18;
}
} // tab
else if (choice_detail == 'N') {
if (input[n] == 0x0A) {
input[n] = 0x18;
}
} // newline
else if (choice_detail == 'A') {
if (input[n] == 0x20 || input[n] == 0x09 || input[n] == 0x0A) {
input[n] = 0x18;
}
}
}
return;
}
void trim_letters(void) {
while (1) {
printf("\nType U to trim uppercase letters, L to trim lowercase letters "
"or A to trim all letters: ");
scanf("%c", &choice_detail);
while (getchar() != '\n') {
}
if (choice_detail == 'U' || choice_detail == 'L' || choice_detail == 'A') {
break;
} else {
printf("\nInvalid input!\n");
}
}
int n = 0;
for (n = strlen(input) - 1; n >= 0; n--) {
if (choice_detail == 'U') {
if (input[n] >= 0x41 && input[n] <= 0x5A) {
input[n] = 0x18;
}
} // Uppercase
else if (choice_detail == 'L') {
if (input[n] >= 0x61 && input[n] <= 0x7A) {
input[n] = 0x18;
}
} // Lowercase
else if (choice_detail == 'A') {
if (input[n] >= 0x41 && input[n] <= 0x5A ||
input[n] >= 0x61 && input[n] <= 0x7A) {
input[n] = 0x18;
}
}
}
return;
}
void trim_special(void) {
while (1) {
printf("\nType A to trim all special characters or S to specify which "
"character to remove: ");
scanf("%c", &choice_detail);
while (getchar() != '\n') {
}
if (choice_detail == 'A' || choice_detail == 'S') {
break;
} else {
printf("\nInvalid input!\n");
}
}
int n = 0;
for (n = strlen(input) - 1; n >= 0; n--) {
if (choice_detail == 'A') {
if (input[n] >= 0x21 && input[n] <= 0x2F ||
input[n] >= 0x3A && input[n] <= 0x40 ||
input[n] >= 0x5B && input[n] <= 0x60 ||
input[n] >= 0x7B && input[n] <= 0x7E) {
input[n] = 0x18;
}
} // All whitespace
}
if (choice_detail == 'S') {
specify_special();
} // Let user specify character.
return;
}
void specify_special(void) {
while (1) {
printf("\nEnter special character to trim and press ENTER: ");
scanf("%c", &choice_special);
while (getchar() != '\n') {
}
if (choice_special >= 0x21 && choice_special <= 0x2F ||
choice_special >= 0x3A && choice_special <= 0x40 ||
choice_special >= 0x5B && choice_special <= 0x60 ||
choice_special >= 0x7B && choice_special <= 0x7E) {
break;
} else {
printf("\nNot a special character!\n");
}
}
int n = 0;
for (n = strlen(input) - 1; n >= 0; n--) {
if (input[n] == choice_special) {
input[n] = 0x18;
}
}
return;
}
void print_result(char input[]) {
printf("\nTrimmed text:\n\n%s\n", input);
sprintf(previous_input, "%s", input); // Save trimmed text for reuse.
return;
}
// TODO
// Create error handling when trimming non existing characters.
// Replace characters (uppercase/lowercase, user selected, etc).
Going too hard with your scan may upset your ISP as well, even if you don't lose packets along the way.
I gave it some further thought and I might be completely misguided to try and max throughput. Datagrams are completely connectionless and therefore canβt know if your routerβs send buffer is full or not, unless Iβm missing something internal between the kernel and the router that makes sendto block (which AFAIK only happens when the socketβs send buffer is full). Therefore most βextraβ datagrams I send would just be dropped anyways. I know ICMP was a dated method of congestion control but have no idea if it would still be in use for simple pings.
Edit: apparently source quench is a thing but still, no clue if the kernel intercepts this or if it is even sent in 2026 due to deprecation
Dgram would work, I didn't think of that. When you have a hammer... Raw does let you do other scan methods (syn scan, open port scan, etc). Raw may require using bpf filtering.
You should be able to go pretty fast, masscan can scan the entire internet in around 5 minutes: https://github.com/robertdavidgraham/masscan
Is there any reason why it has to be a raw socket, rather than a dgram icmp socket? (which allows you to run as non-root) Silly me for not realizing I only needed one socket. Ofc my speed would only be at most 1-2Mbps. I currently track outgoing pings in memory so I would probably need to keep a single large circular array instead for timeout purposes -- not too different from what I'm doing currently.
if you want to see how you can write your own: https://linux-is-best.pckt.blog/writing-your-own-print-function-16xt8wv
Check out GLIBC, the standard libc implementation for GNU/Linux, or the easier to read, minimalist, alternative libc MUSL.
Bootlin, runs the Elixir Code Cross Referencer that lets you easily hop between files. For instance, Here is the code for printf in GLIBC.
The streamlined way to do this is raw sockets. You only need one, and you can just craft all of the ICMP requests and send them continuously. Then in a separate thread you can read all the replies.

I dunno if this helps, but this screenshot shows the memory view of a program with a string. The hex representation in the middle is what is actually stored in memory. Each pair is one byte/char, and that is what your input[n] <= 0x39 is comparing against.
I C (and most other languages) you can enter a value like thirty-two using many different number bases: β β as a char, 32 in decimal, 0x20 is hex, 040 in octal or 0b00010000. All of these mean thirty-two, just written differently using different characters.
Note that each of these representations use different digits and/or letters to specify the same number - the mathematical entity thirty-two. These letters and digits are characters stored as numbers, eg the digit zero (0) is coded in ASCII as 48.
In the end, the C compiler will compile the text β32β, β0x40β or β0b00010000β into binary and store it in memory the single byte where the 5th bit set to 1 and the others to 0. This is the way computer memory is used to represent the number, and itβs linked to the way the electric circuits do things like add numbers together.
The compiler is the thing that understands that the characters like β3β, β+β or βFβ and works out what they represent (a number, a variable name or a sum). When it understands that, it will turn the code into bits that the CPU can work with, for example passing it to the compare instruction CMP used to test whether the input is less than the number thirty-two.
This is very meta, and I hope this clarifies rather than confuses. Compilers are notoriously confusing, such as the thought βthe compiler compiles the compiler code into an executable that can compile itselfβ.
Thanks! Every piece of advice is appreciated! :D
Thanks! I'll have to noodle this around a bit because it's hard for me to understand. xD
Maybe this is a bit too much if you are just starting but read the first paragraph at least and then also look at "Relationship to ASCII" and "Relationship to Unicode"
You are inspecting a byte, which contains 0x39. That it corresponds to "9" doesn't matter at all to the compiler at all. If you were on a system that didn't match "9" <=> 0x39 it would still replace those byte values.
Thank you canβt believe I forgot this
Thanks!
There are multiple implementations, the one often found on linux is glibc, you can find the source code for that online. printf is going to be a small wrapper for vfprintf though, so that is where you will probably find what you are looking for
Been a while since I've done this kind of thing, but you should be able to create a pool of sockets primed with the first batch of IP addresses, then send the packets.
On reply or timeout, you don't destroy the socket and create a new one. Instead you alter the connection information in the socket descriptor in the existing socket to point to the next address to check, and then go again.