

> They're all talks, not real hands-on trainings like you did.
I love listening to good, well-structured talks. Problem is, not everybody is a good speaker and many screw it up. 🥴 I’m certainly not a great speaker, which is why I gravitate more towards “workshops”, in the hopes that people ask questions and discussions arise. Doesn’t always work out. 🤣 At the very least, I almost always have some other person connect to the projector/beamer/screenshare and then they do the stuff – this avoids me being *wwwwaaaaaaaaayyyy* too fast.
We are usually drowned in stress and tight deadlines, hence events like today are super rare … We used to do it more often until ~10 years ago.
> Once a year the security guys organize a really great hacking event, though.
Oh dear, I’d love to participate in that. 🤯 That sounds like a lot of fun. (Why don’t we do this?!)~
> They're all talks, not real hands-on trainings like you did.
I love listening to good, well-structured talks. Problem is, not everybody is a good speaker and many screw it up. 🥴 I’m certainly not a great speaker, which is why I gravitate more towards “workshops”, in the hopes that people ask questions and discussions arise. Doesn’t always work out. 🤣 At the very least, I almost always have some other person connect to the projector/beamer/screenshare and then they do the stuff – this avoids me being *wwwwaaaaaaaaayyyy* too fast.
We are usually drowned in stress and tight deadlines, hence events like today are super rare … We used to do it more often until ~10 years ago.
> Once a year the security guys organize a really great hacking event, though.
Oh dear, I’d love to participate in that. 🤯 That sounds like a lot of fun. (Why don’t we do this?!)~
https://mastodon.social/@grimalkina/114717549619229029
I don’t know enough about these things to form an opinion. 🫤 I sure wish it was true, though. 😅
https://mastodon.social/@grimalkina/114717549619229029
I don’t know enough about these things to form an opinion. 🫤 I sure wish it was true, though. 😅
- People used the Intel docs to figure out the instruction encodings.
- Then they wrote a little DOS program that exits with a return code and they used uhex in DOSBox to do that. Yes, we wrote a COM file manually, no Assembler involved. (Many of them had never used DOS before.)
- DEBUG from FreeDOS was used to single-step through the program, showing what it does.
- This gets tedious rather quickly, so we switched to SVED from SvarDOS for writing the rest of the program in Assembly language. nasm worked great for us.
- At the end, we switched to BIOS calls instead of DOS syscalls to demonstrate that the same binary COM file works on another OS. Also a good opportunity to talk about bootloaders a little bit.
- (I think they even understood the basics of segmentation in the end.)
The 8086 / 16-bit real-mode DOS is a great platform to explain a lot of the fundamentals without having to deal with OS semantics or executable file formats.
Now that was a lot of fun. 🥳 It’s very rare that we do something like this, sadly. I love doing this kind of low-level stuff.
- People used the Intel docs to figure out the instruction encodings.
- Then they wrote a little DOS program that exits with a return code and they used uhex in DOSBox to do that. Yes, we wrote a COM file manually, no Assembler involved. (Many of them had never used DOS before.)
- DEBUG from FreeDOS was used to single-step through the program, showing what it does.
- This gets tedious rather quickly, so we switched to SVED from SvarDOS for writing the rest of the program in Assembly language. nasm worked great for us.
- At the end, we switched to BIOS calls instead of DOS syscalls to demonstrate that the same binary COM file works on another OS. Also a good opportunity to talk about bootloaders a little bit.
- (I think they even understood the basics of segmentation in the end.)
The 8086 / 16-bit real-mode DOS is a great platform to explain a lot of the fundamentals without having to deal with OS semantics or executable file formats.
Now that was a lot of fun. 🥳 It’s very rare that we do something like this, sadly. I love doing this kind of low-level stuff.
We’re talking about this pattern, right?
f, err := os.Open("filename.ext")
if err != nil {
log.Fatal(err)
}
Nothing stops you from leaving out the
if
, right? 🤔
We’re talking about this pattern, right?
f, err := os.Open("filename.ext")
if err != nil {
log.Fatal(err)
}
Nothing stops you from leaving out the
if
, right? 🤔
https://racingbunny.com/@mookie/114718466149264471
> 18 rules of Software Engineering
>
> 0. You will regret complexity when on-call
> 1. Stop falling in love with your own code
> 2. Everything is a trade-off. There's no "best" 3. Every line of code you write is a liability 4. Document your decisions and designs
> 5. Everyone hates code they didn’t write
> 6. Don't use unnecessary dependencies
> 7. Coding standards prevent arguments
> 8. Write meaningful commit messages
> 9. Don't ever stop learning new things
> 10. Code reviews spread knowledge
> 11. Always build for maintainability
> 12. Ask for help when you’re stuck
> 13. Fix root causes, not symptoms
> 14. Software is never completed
> 15. Estimates are not promises
> 16. Ship early, iterate often
> 17. Keep. It. Simple.
Solid list, even though 14 is up for debate in my opinion: Software can be completed. You have a use case / problem, you solve that problem, done. Your software is completed now. There might still be bugs and they should be fixed – but this doesn’t “add” to the program. Don’t use “software is never done” as an excuse to keep adding and adding stuff to your code.
https://racingbunny.com/@mookie/114718466149264471
> 18 rules of Software Engineering
>
> 0. You will regret complexity when on-call
> 1. Stop falling in love with your own code
> 2. Everything is a trade-off. There's no "best" 3. Every line of code you write is a liability 4. Document your decisions and designs
> 5. Everyone hates code they didn’t write
> 6. Don't use unnecessary dependencies
> 7. Coding standards prevent arguments
> 8. Write meaningful commit messages
> 9. Don't ever stop learning new things
> 10. Code reviews spread knowledge
> 11. Always build for maintainability
> 12. Ask for help when you’re stuck
> 13. Fix root causes, not symptoms
> 14. Software is never completed
> 15. Estimates are not promises
> 16. Ship early, iterate often
> 17. Keep. It. Simple.
Solid list, even though 14 is up for debate in my opinion: Software can be completed. You have a use case / problem, you solve that problem, done. Your software is completed now. There might still be bugs and they should be fixed – but this doesn’t “add” to the program. Don’t use “software is never done” as an excuse to keep adding and adding stuff to your code.
Option
and error handling. (Or the more complex Result
, but it’s easier to explain with Option
.)fn mydiv(num: f64, denom: f64) -> Option
// (Let’s ignore precision issues for a second.)
if denom 0.0 {
return None;
} else {
return Some(num / denom);
}
}
fn main() {
// Explicit, verbose version:
let num: f64 = 123.0;
let denom: f64 = 456.0;
let wrapped_res = mydiv(num, denom);
if wrapped_res.is_some() {
println!("Unwrapped result: {}", wrapped_res.unwrap());
}
// Shorter version using "if let":
if let Some(res) = mydiv(123.0, 456.0) {
println!("Here’s a result: {}", res);
}
if let Some(res) = mydiv(123.0, 0.0) {
println!("Huh, we divided by zero? This never happens. {}", res);
}
}
You can’t divide by zero, so the function returns an “error” in that case. (
Option
isn’t really used for errors, IIUC, but the basic idea is the same for Result
.)`Option` is an enum. It can have the value
Some
or None
. In the case of Some
, *you can attach additional data* to the enum. In this case, we are attaching a floating point value.The caller then has to decide: Is the value
None
or Some
? Did the function succeed or not? If it is Some
, the caller can do .unwrap()
on this enum to get the inner value (the floating point value). If you do .unwrap()
on a None
value, the program will panic and die.The
if let
version using destructuring is much shorter and, once you got used to it, actually quite nice.Now the trick is that you *must* somehow handle these two cases. You *must* either call something like
.unwrap()
or do destructuring or something, otherwise you can’t access the attached value at all. As I understand it, it is impossible to just completely ignore error cases. And *the compiler enforces it*.(In case of
Result
, the compiler would warn you if you ignore the return value entirely. So something like doing write()
and then ignoring the return value would be caught as well.)=
Option
and error handling. (Or the more complex Result
, but it’s easier to explain with Option
.)fn mydiv(num: f64, denom: f64) -> Option
// (Let’s ignore precision issues for a second.)
if denom 0.0 {
return None;
} else {
return Some(num / denom);
}
}
fn main() {
// Explicit, verbose version:
let num: f64 = 123.0;
let denom: f64 = 456.0;
let wrapped_res = mydiv(num, denom);
if wrapped_res.is_some() {
println!("Unwrapped result: {}", wrapped_res.unwrap());
}
// Shorter version using "if let":
if let Some(res) = mydiv(123.0, 456.0) {
println!("Here’s a result: {}", res);
}
if let Some(res) = mydiv(123.0, 0.0) {
println!("Huh, we divided by zero? This never happens. {}", res);
}
}
You can’t divide by zero, so the function returns an “error” in that case. (
Option
isn’t really used for errors, IIUC, but the basic idea is the same for Result
.)`Option` is an enum. It can have the value
Some
or None
. In the case of Some
, *you can attach additional data* to the enum. In this case, we are attaching a floating point value.The caller then has to decide: Is the value
None
or Some
? Did the function succeed or not? If it is Some
, the caller can do .unwrap()
on this enum to get the inner value (the floating point value). If you do .unwrap()
on a None
value, the program will panic and die.The
if let
version using destructuring is much shorter and, once you got used to it, actually quite nice.Now the trick is that you *must* somehow handle these two cases. You *must* either call something like
.unwrap()
or do destructuring or something, otherwise you can’t access the attached value at all. As I understand it, it is impossible to just completely ignore error cases. And *the compiler enforces it*.(In case of
Result
, the compiler would warn you if you ignore the return value entirely. So something like doing write()
and then ignoring the return value would be caught as well.)=
https://www.uninformativ.de/desktop/2025%2D06%2D21%2D%2Dkatriawm%2Dold%2Dxorg%2Dapps.png
Good luck figuring out which of these UI elements are click-able – unless you examine every pixel on the screen.
https://www.uninformativ.de/desktop/2025%2D06%2D21%2D%2Dkatriawm%2Dold%2Dxorg%2Dapps.png
Good luck figuring out which of these UI elements are click-able – unless you examine every pixel on the screen.
Skimming through the manual: I had no idea that keeping the “up” cursor pressed actually slows you down at some point. 🤦
Skimming through the manual: I had no idea that keeping the “up” cursor pressed actually slows you down at some point. 🤦
Alt+.
all the time, it’s great. 👌FWIW, another thing I often use is
!!
to recall the entire previous command line:$ find -iname '*foo*'
./This is a foo file.txt
$ cat "$(!!)"
cat "$(find -iname '*foo*')"
This is just a test.
Yep!
Or:
$ ls -al subdir
ls: cannot open directory 'subdir': Permission denied
$ sudo !!
sudo ls -al subdir
total 0
drwx------ 2 root root 60 Jun 20 19:39 .
drwx------ 7 jess jess 360 Jun 20 19:39 ..
-rw-r--r-- 1 root root 0 Jun 20 19:39 nothing-to-see
Alt+.
all the time, it’s great. 👌FWIW, another thing I often use is
!!
to recall the entire previous command line:$ find -iname '*foo*'
./This is a foo file.txt
$ cat "$(!!)"
cat "$(find -iname '*foo*')"
This is just a test.
Yep!
Or:
$ ls -al subdir
ls: cannot open directory 'subdir': Permission denied
$ sudo !!
sudo ls -al subdir
total 0
drwx------ 2 root root 60 Jun 20 19:39 .
drwx------ 7 jess jess 360 Jun 20 19:39 ..
-rw-r--r-- 1 root root 0 Jun 20 19:39 nothing-to-see
https://movq.de/v/7531158962/etr.mp4
https://movq.de/v/7531158962/etr.mp4
@lyse Yeah, YMMV. Some games work(ed) great in Wine, others not at all. I just use it because it’s easier than firing up my WinXP box. (I don’t use Wine for regular applications, just games.)
@lyse Yeah, YMMV. Some games work(ed) great in Wine, others not at all. I just use it because it’s easier than firing up my WinXP box. (I don’t use Wine for regular applications, just games.)
- 16-bit support is gone.
- Performance of 3D games is horrible and unplayable.
Arch is shipping a WoW64 build now, which is not yet ready for prime time.
And *then* I realized that there’s actually only one stable Wine release per year but Arch has been shipping development releases all the time. That’s quite unusual. I’m used to Arch only shipping stable packages … huh.
Hopefully things will improve again. I’m not eager to build Wine from source. I’d rather ditch it and resort to my real Windows XP box for the little (retro)gaming that I do … 🫤
- 16-bit support is gone.
- Performance of 3D games is horrible and unplayable.
Arch is shipping a WoW64 build now, which is not yet ready for prime time.
And *then* I realized that there’s actually only one stable Wine release per year but Arch has been shipping development releases all the time. That’s quite unusual. I’m used to Arch only shipping stable packages … huh.
Hopefully things will improve again. I’m not eager to build Wine from source. I’d rather ditch it and resort to my real Windows XP box for the little (retro)gaming that I do … 🫤
There’s also a project page which lists some limitations of this study: https://www.media.mit.edu/projects/your-brain-on-chatgpt/overview/
It certainly sounds plausible. “Use it or lose it.”
There’s also a project page which lists some limitations of this study: https://www.media.mit.edu/projects/your-brain-on-chatgpt/overview/
It certainly sounds plausible. “Use it or lose it.”
> I'm Alex, COO at ColdIQ. Built a $4.5M ARR business in under 2 years.
Some “C-level” guy telling people what to do, yeah, I have my doubts.
> I'm Alex, COO at ColdIQ. Built a $4.5M ARR business in under 2 years.
Some “C-level” guy telling people what to do, yeah, I have my doubts.
@aelaraji I wish I had the luxury of not reading that junk. 😅 But instead, I have a Mutt hotkey that pipes an HTML mail through elinks … Bah.
@aelaraji I wish I had the luxury of not reading that junk. 😅 But instead, I have a Mutt hotkey that pipes an HTML mail through elinks … Bah.
getpeername()
, for example, so I don’t have a choice), so I have to do some FFI stuff and deal with raw pointers and all that, which is very gnarly in Rust – because you’re not supposed to do this. Things like that are trivial in C or even Assembler, but I have not yet understood what Rust does under the hood. How and when does it allocate or free memory … is the pointer that I get even still valid by the time I do the libc call? Stuff like that.I hope that I eventually learn this over time … but I get slapped in the face at every step. It’s very frustrating and I’m always this 🤏 close to giving up (only to try again a year later).
Oh, yeah, yeah, I guess I could “just” use some 3rd party library for this. socket2 gets mentioned a lot in this context. But I don’t want to. I literally need *one*
getpeername()
call during the lifetime of my program, I don’t even do the socket()
, bind()
, listen()
, accept()
dance, I already have a fully functional file descriptor. Using a library for that is total overkill and I’d rather do it myself. (And look at the version number: 0.5.10
. The library is 6 years old but they’re still saying: “Nah, we’re not 1.0 yet, we reserve the right to make breaking changes with every new release.” So many Rust libs are still unstable …)… and I could go on and on and on … 🤣
getpeername()
, for example, so I don’t have a choice), so I have to do some FFI stuff and deal with raw pointers and all that, which is very gnarly in Rust – because you’re not supposed to do this. Things like that are trivial in C or even Assembler, but I have not yet understood what Rust does under the hood. How and when does it allocate or free memory … is the pointer that I get even still valid by the time I do the libc call? Stuff like that.I hope that I eventually learn this over time … but I get slapped in the face at every step. It’s very frustrating and I’m always this 🤏 close to giving up (only to try again a year later).
Oh, yeah, yeah, I guess I could “just” use some 3rd party library for this. socket2 gets mentioned a lot in this context. But I don’t want to. I literally need *one*
getpeername()
call during the lifetime of my program, I don’t even do the socket()
, bind()
, listen()
, accept()
dance, I already have a fully functional file descriptor. Using a library for that is total overkill and I’d rather do it myself. (And look at the version number: 0.5.10
. The library is 6 years old but they’re still saying: “Nah, we’re not 1.0 yet, we reserve the right to make breaking changes with every new release.” So many Rust libs are still unstable …)… and I could go on and on and on … 🤣
pledge()
and unveil()
syscalls:https://www.youtube.com/watch?v=bXO6nelFt-E
Not only are they super useful (the program itself can drop privileges – like, it can initialize itself, read some files, whatever, and then tell the kernel that it will never do anything like that again; if it does, e.g. by being exploited through a bug, it gets killed by the kernel), but they are also extremely easy to use.
Imagine a server program with a connected socket in file descriptor 0. Before reading any data from the client, the program can do this:
unveil("/var/www/whatever", "r");
unveil(NULL, NULL);
pledge("stdio rpath", NULL);
Done. It’s now limited to reading files from that directory, communicating with the existing socket, stuff like that. But it cannot ever read any other files or
exec()
into something else.I can’t wait for the day when we have something like this on Linux. There have been some attempts, but it’s not that easy. And it’s certainly not mainstream, yet.
I need to have a closer look at Linux’s Landlock soon (“soon”), but this is considerably more complicated than
pledge()
/unveil()
:https://landlock.io/
pledge()
and unveil()
syscalls:https://www.youtube.com/watch?v=bXO6nelFt-E
Not only are they super useful (the program itself can drop privileges – like, it can initialize itself, read some files, whatever, and then tell the kernel that it will never do anything like that again; if it does, e.g. by being exploited through a bug, it gets killed by the kernel), but they are also extremely easy to use.
Imagine a server program with a connected socket in file descriptor 0. Before reading any data from the client, the program can do this:
unveil("/var/www/whatever", "r");
unveil(NULL, NULL);
pledge("stdio rpath", NULL);
Done. It’s now limited to reading files from that directory, communicating with the existing socket, stuff like that. But it cannot ever read any other files or
exec()
into something else.I can’t wait for the day when we have something like this on Linux. There have been some attempts, but it’s not that easy. And it’s certainly not mainstream, yet.
I need to have a closer look at Linux’s Landlock soon (“soon”), but this is considerably more complicated than
pledge()
/unveil()
:https://landlock.io/
https://www.os2museum.com/wp/learn-something-old-every-day-part-xv-keyb-is-half-of-keyboard-bios/
https://www.os2museum.com/wp/learn-something-old-every-day-part-xv-keyb-is-half-of-keyboard-bios/
fn sub(foo: &String) {
println!("We got this string: [{}]", foo);
}
fn main() {
// "Hello", 0x00, 0x00, "!"
let buf: [u8; 8] = [0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x00, 0x00, 0x21];
// Create a string from the byte array above, interpret as UTF-8, ignore decoding errors.
let lossy_unicode = String::from_utf8_lossy(&buf).to_string();
sub(&lossy_unicode);
}
Create a string from a byte array, but the result isn’t a string, it’s a cow 🐮, so you need another
to_string()
to convert your “string” into a string.- https://doc.rust-lang.org/std/string/struct.String.html#method.from_utf8_lossy
- https://doc.rust-lang.org/std/borrow/enum.Cow.html
I still have a lot to learn.
(
into_owned()
instead of to_string()
also works and makes more sense to me, it’s just that the compiler suggested to_string()
first, which led to this funny example.)
fn sub(foo: &String) {
println!("We got this string: [{}]", foo);
}
fn main() {
// "Hello", 0x00, 0x00, "!"
let buf: [u8; 8] = [0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x00, 0x00, 0x21];
// Create a string from the byte array above, interpret as UTF-8, ignore decoding errors.
let lossy_unicode = String::from_utf8_lossy(&buf).to_string();
sub(&lossy_unicode);
}
Create a string from a byte array, but the result isn’t a string, it’s a cow 🐮, so you need another
to_string()
to convert your “string” into a string.- https://doc.rust-lang.org/std/string/struct.String.html#method.from_utf8_lossy
- https://doc.rust-lang.org/std/borrow/enum.Cow.html
I still have a lot to learn.
(
into_owned()
instead of to_string()
also works and makes more sense to me, it’s just that the compiler suggested to_string()
first, which led to this funny example.)
By the way, looks like there was a bit of a discussion regarding that name:
https://github.com/rust-lang/rust/issues/120048
By the way, looks like there was a bit of a discussion regarding that name:
https://github.com/rust-lang/rust/issues/120048
https://doc.rust-lang.org/std/path/struct.Path.html#method.display
Note the little
1.0.0
in the top right corner, which means that this function has been “stable since Rust version 1.0.0”. We’re at 1.87 now, so we’re good.Then I compiled my program on OpenBSD with Rust 1.86, i.e. just one version behind, but well ahead of 1.0.0.
The compiler said that I was using an unstable library feature.
Turns out, that function internally uses this:
https://doc.rust-lang.org/std/ffi/struct.OsStr.html#method.display
And that is only available since Rust 1.87.
How was I supposed to know this? 🤨
https://doc.rust-lang.org/std/path/struct.Path.html#method.display
Note the little
1.0.0
in the top right corner, which means that this function has been “stable since Rust version 1.0.0”. We’re at 1.87 now, so we’re good.Then I compiled my program on OpenBSD with Rust 1.86, i.e. just one version behind, but well ahead of 1.0.0.
The compiler said that I was using an unstable library feature.
Turns out, that function internally uses this:
https://doc.rust-lang.org/std/ffi/struct.OsStr.html#method.display
And that is only available since Rust 1.87.
How was I supposed to know this? 🤨
Gopher is a small world. It’s slow and cozy.
And much like twtxt, the protocol is simple(r), so it’s easier to tinker with it.
Gopher is a small world. It’s slow and cozy.
And much like twtxt, the protocol is simple(r), so it’s easier to tinker with it.
I’ll probably keep reading Fedi stuff, I just won’t post that much, I think.
I’ll probably keep reading Fedi stuff, I just won’t post that much, I think.
gopher://uninformativ.de
(No, I won’t do multi-protocol twtxt again. 😅)
gopher://uninformativ.de
(No, I won’t do multi-protocol twtxt again. 😅)
I’m struggling with this, using 3rd party libs for so many things isn’t really my cup of tea. I’ll probably make my own tiny little “standard library”. It’s silly, but I don’t see any other options. 🤷
I’m struggling with this, using 3rd party libs for so many things isn’t really my cup of tea. I’ll probably make my own tiny little “standard library”. It’s silly, but I don’t see any other options. 🤷