1
0
Fork 0
forked from wezm/wezm.net

Compare commits

...

26 commits
tv ... master

Author SHA1 Message Date
3d8ae03a2a
Fix typos in OpenBSD crash post 2023-09-22 11:57:16 +10:00
ee4b17e1ca
Add OpenBSD VM post 2023-09-17 12:51:07 +10:00
ed2d4bd2b3
Set v2 base_url to https 2023-07-03 09:35:07 +10:00
d4ed662cdf
Add v2 sitemap.xml to root robots.txt 2023-07-03 09:34:54 +10:00
8c16278ffc
Tweak YouTube post 2023-04-14 12:16:28 +10:00
3b2c0dd92d
Add Psivewri 2023-04-14 10:50:11 +10:00
25b701e10a
Add ANZ YouTubers post 2023-04-11 10:05:01 +10:00
1fe0245e1f
Add new classic Mac OS Rust post 2023-03-31 20:59:03 +10:00
3395f3edac
Add DeRez post 2023-03-31 13:43:52 +10:00
8b7103180c
Fix binutils-powerpc-linux-gnu package name 2023-03-26 14:27:55 +10:00
4e2eeb415f
Update work status 2023-03-06 10:18:50 +10:00
b16ca174ca
Fix typo in podcast URL 2023-03-01 21:55:16 +10:00
4451d995d4
Add XSLT podcast post 2023-03-01 20:06:37 +10:00
531f2fec35
Add rust-on-ppc-classic-mac-os post 2023-02-27 12:46:33 +10:00
4b12ad7fef
Add Docker debug post 2023-02-25 11:43:00 +10:00
ab0d9faafe
Update Mastodon links on old site 2023-01-30 11:13:07 +10:00
fe65d4982d
Update Fediverse accoun 2023-01-30 10:37:40 +10:00
35ad9543a9
Put Fediverse first in post footer 2023-01-21 07:35:31 +10:00
0e5ed74517
Remove Twitter button from home page 2023-01-21 07:35:18 +10:00
e0209f1456
Add sign in with google post 2023-01-21 07:35:07 +10:00
0a4f1d2a45
Update divmod post 2023-01-11 21:11:43 +10:00
e00bb7868c
Add divmod post 2023-01-11 20:32:56 +10:00
7f389d2055
Add referral link 2022-12-01 07:47:26 +10:00
0504ad56a3
Fix link between garage door monitor posts 2022-10-17 16:02:19 +10:00
ae8277de63
Add garage-door-monitor-update post 2022-10-17 15:57:46 +10:00
13a14637e3
Add note to Kobo post that it no longer works 2022-08-22 14:14:08 +10:00
50 changed files with 1720 additions and 24 deletions

View file

@ -43,7 +43,7 @@ data_sources:
# same as the items root, but applies to layouts rather than items.
layouts_root: /
base_url: 'http://www.wezm.net'
base_url: 'https://www.wezm.net'
deploy:
default:
kind: rsync

View file

@ -23,7 +23,7 @@ Find me on the Internet in one of these places:
Aggregator of content related to the <a href="https://www.rust-lang.org/">Rust</a> programming language</li>
<li><a href="http://www.flickr.com/photos/wezm/">Flickr</a></li>
<li><a href="https://github.com/wezm">GitHub</a></li>
<li><a href="https://decentralised.social/wezm">Mastodon/Fediverse</a></li>
<li><a href="https://mastodon.decentralised.social/@wezm">Mastodon/Fediverse</a></li>
<li><a href="https://patreon.com/wezm">Patreon</a></li>
<li><a href="http://stackoverflow.com/users/38820/wes">Stack Overflow</a></li>
<li><a href="https://twitter.com/wezm">Twitter</a></li>

View file

@ -1,3 +1,4 @@
User-Agent: *
Disallow:
Sitemap: <%= @config[:base_url] %>/sitemap.xml
Sitemap: <%= @config[:base_url] %>/v2/sitemap.xml

View file

@ -114,7 +114,7 @@ from.
* [Jeremy Soller] is creating [Redox OS], an MIT licensed OS written in Rust.
$60
* [Gargron] is creating the [Mastodon] federated social network. You can find
me at <https://decentralised.social/wezm>. $40
me at <https://mastodon.decentralised.social/@wezm>. $40
* [Jorge Aparicio] is doing fantastic work building Rust tooling and pushing
the state of the art of [Rust on microcontrollers][embedded-rust]. $35
* [Steve Wills] does a stack of work on [FreeBSD ports/packages][swills-ports].

View file

@ -17,7 +17,7 @@
<path d="M2 4 C6 8 10 12 15 11 A6 6 0 0 1 22 4 A6 6 0 0 1 26 6 A8 8 0 0 0 31 4 A8 8 0 0 1 28 8 A8 8 0 0 0 32 7 A8 8 0 0 1 28 11 A18 18 0 0 1 10 30 A18 18 0 0 1 0 27 A12 12 0 0 0 8 24 A8 8 0 0 1 3 20 A8 8 0 0 0 6 19.5 A8 8 0 0 1 0 12 A8 8 0 0 0 3 13 A8 8 0 0 1 2 4"></path>
</svg>
</a>
<a href="https://decentralised.social/wezm" rel="me" class="no-border">
<a href="https://mastodon.decentralised.social/@wezm" rel="me" class="no-border">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 216.4144 232.00976" class="social-icon"><path d="M211.80734 139.0875c-3.18125 16.36625-28.4925 34.2775-57.5625 37.74875-15.15875 1.80875-30.08375 3.47125-45.99875 2.74125-26.0275-1.1925-46.565-6.2125-46.565-6.2125 0 2.53375.15625 4.94625.46875 7.2025 3.38375 25.68625 25.47 27.225 46.39125 27.9425 21.11625.7225 39.91875-5.20625 39.91875-5.20625l.8675 19.09s-14.77 7.93125-41.08125 9.39c-14.50875.7975-32.52375-.365-53.50625-5.91875C9.23234 213.82 1.40609 165.31125.20859 116.09125c-.365-14.61375-.14-28.39375-.14-39.91875 0-50.33 32.97625-65.0825 32.97625-65.0825C49.67234 3.45375 78.20359.2425 107.86484 0h.72875c29.66125.2425 58.21125 3.45375 74.8375 11.09 0 0 32.975 14.7525 32.975 65.0825 0 0 .41375 37.13375-4.59875 62.915" fill="currentColor"></path><path d="M177.50984 80.077v60.94125h-24.14375v-59.15c0-12.46875-5.24625-18.7975-15.74-18.7975-11.6025 0-17.4175 7.5075-17.4175 22.3525v32.37625H96.20734V85.42325c0-14.845-5.81625-22.3525-17.41875-22.3525-10.49375 0-15.74 6.32875-15.74 18.7975v59.15H38.90484V80.077c0-12.455 3.17125-22.3525 9.54125-29.675 6.56875-7.3225 15.17125-11.07625 25.85-11.07625 12.355 0 21.71125 4.74875 27.8975 14.2475l6.01375 10.08125 6.015-10.08125c6.185-9.49875 15.54125-14.2475 27.8975-14.2475 10.6775 0 19.28 3.75375 25.85 11.07625 6.36875 7.3225 9.54 17.22 9.54 29.675" fill="#fff"></path></svg>
</a>
</div>

View file

@ -13,7 +13,7 @@
<img src="/images/comment.svg" width="32" height="32" alt="Comment icon" class="comment align-top" />
Stay in touch!
</h2>
<p>Follow me on <a href="https://twitter.com/wezm">Twitter</a> or <a href="https://decentralised.social/wezm">Mastodon</a>, <a href="/feed/">subscribe to the feed</a>, or <a href="mailto:wes@wezm.net">send me an email</a>.
<p>Follow me on <a href="https://twitter.com/wezm">Twitter</a> or <a href="https://mastodon.decentralised.social/@wezm">Mastodon</a>, <a href="/feed/">subscribe to the feed</a>, or <a href="mailto:wes@wezm.net">send me an email</a>.
</p>
</div>
</article>

View file

@ -8,3 +8,7 @@ deploy: build
# build with zola
build:
zola build
# start zola dev server
serve:
zola serve

View file

@ -8,8 +8,7 @@ body_class = "home"
Hi I'm Wes 👋. I like warm weather and tinkering with computers; ranging from
small microcontrollers, up to large servers and the operating systems that run
upon them. I'm a [Rustacean] {{ quiet(text="🦀") }} with a fondness for
mechanical keyboards. I work at [YesLogic] on the [Prince] HTML to PDF
converter. [Read more on the about page →](@/about.md)
mechanical keyboards. [Read more on the about page →](@/about.md)
[Prince]: https://www.princexml.com/
[Rustacean]: https://www.rust-lang.org/learn/get-started#ferris

View file

@ -22,11 +22,6 @@ Ninja 2, which has had the factory controller replaced with a programmable one.
community and curate [Read Rust], a website that collects interesting posts
from the Rust community.
I work at [YesLogic] on the [Prince] HTML to PDF converter. Prince is a very
interesting product to work on as it is essentially a web-browser that outputs
well typeset PDFs. It is largely implemented in the [Mercury] logic programming
language, with portions now in [Rust] as well.
I am fine with either he/him or they/them pronouns.
[Alpine Linux]: https://alpinelinux.org/
@ -37,7 +32,6 @@ I am fine with either he/him or they/them pronouns.
[my desktop]: https://bitcannon.net/page/ryzen9-pc/
[my laptop]: https://bitcannon.net/post/huawei-matebook-x-pro-void-linux/
[my server]: https://www.wezm.net/technical/2019/02/alpine-linux-docker-infrastructure/
[Prince]: https://www.princexml.com/
[Read Rust]: https://readrust.net/
[Rust]: https://www.rust-lang.org/
[Void Linux]: https://voidlinux.org/

View file

@ -6,7 +6,7 @@ date = 2021-08-26T09:10:54+10:00
#updated = 2021-05-15T10:15:08+10:00
+++
On 24 August I received an email from Vultr saying that my server had used 78%
On 24 August I received an email from [Vultr] saying that my server had used 78%
of its 3Tb bandwidth allocation for the month. This was surprising as last time
I looked I only used a small fraction of this allocation across the various
[things I host][alpine-docker].
@ -75,3 +75,4 @@ my peace with linking to Twitter.
[nitter-instance]: https://decentralised.social/notice/A41E2cjuM14UYFAF7o
[robots.txt]: https://github.com/wezm/nitter/commit/4e7bd7b8853bf36008a3d1e79ee97deaa68743da
[Varnish]: https://varnish-cache.org/
[Vultr]: https://www.vultr.com/?ref=7903263

View file

@ -0,0 +1,51 @@
+++
title = "Garage Door Monitor Update"
date = 2022-10-17T15:04:44+10:00
#[extra]
#updated = 2022-04-21T09:07:57+10:00
+++
[The garage door monitor that I built earlier in the year](@/posts/2022/garage-door-monitor/index.md)
has by all accounts been running perfectly since I installed it. Recently I
implemented a couple of new features that I've wished for over the last few
months.
<!-- more -->
The first new feature is a bit more visibility into the state of the system. I
added uptime and memory info to the web page:
{{ figure(image="posts/2022/garage-door-monitor-update/webpage.png", link="posts/2022/garage-door-monitor-update/webpage.png", alt="Screenshot of the web page served by the garage door monitor. It shows the state of the door, uptime of the device, and memory usage information.", caption="Screenshot of the web page now including uptime and memory info.", width=625) }}
The second and more practical addition is a subsequent notification when the
door is closed again after it was left open for longer that the trigger time (5
minutes).
{{ figure(image="posts/2022/garage-door-monitor-update/new-mattermost-notification.png", link="posts/2022/garage-door-monitor-update/new-mattermost-notification.png", alt="Screenshot of the message posted to Mattermost by the garage door monitor. It reads: Garage door closed after 5 minutes open.", caption="The new notification", width=403, border=true) }}
Since I set up the garage door monitor I haven't accidentally left it open.
However, I have triggered the notification when washing the car or packing for
a road trip. It's this latter scenario that I wanted to address. The issue is
that because the notification is triggered during packing if I did set off and
forget to close the door I wouldn't know. One way to deal with this would be
recurring notifications. I chose another option though.
When the door is closed after notifying that it was left open, it sends another
message saying that it's now closed. The benefit of this approach is that the
message log now properly tracks the state of the door, so if I'm a few kms down
the road and wonder if I closed the door it's just a matter of checking the
channel in Mattermost.
Before I powered it off to update the Buildroot image on the SD card I hooked
up a serial console and checked its uptime. I was pleased to see 100% uptime
since it was first installed:
```
02:03:14 up 183 days, 20:26, load average: 0.00, 0.00, 0.00
```
That's all for now, on to the next few hundred days of service!
[Garage door monitor source on GitHub](https://github.com/wezm/garage-door-monitor)

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

View file

@ -3,7 +3,7 @@ title = "Monitoring My Garage Door With a Raspberry Pi, Rust, and a 13Mb Linux S
date = 2022-04-20T06:38:27+10:00
[extra]
updated = 2022-04-21T09:07:57+10:00
updated = 2022-10-17T15:51:20+10:00
+++
I've accidentally left our garage door open a few times. To combat this I built
@ -301,6 +301,9 @@ happens.
All the code, hardware designs, and Buildroot configuration/overlay is
available in [the git repository][repo] if you're interested.
[See part two](@/posts/2022/garage-door-monitor-update/index.md) for a couple of new
features I added a few months later.
[app]: https://github.com/wezm/garage-door-monitor/tree/main/app
[Awesome]: https://github.com/awesomeWM/awesome
[FreeCAD]: https://www.freecadweb.org/

View file

@ -2,8 +2,8 @@
title = "Fixing Monospace Text in Kobo eReaders"
date = 2022-04-10T09:14:50+10:00
# [extra]
# updated = 2022-01-27T21:07:32+10:00
[extra]
updated = 2022-08-22T14:10:50+10:00
+++
After verifying with friends that eBook readers do a decent job of rendering
@ -14,6 +14,11 @@ text with CSS rules like `font-family: monospace` in a monospace font.
<!-- more -->
**Update Aug 2022:** I was told and have confirmed myself that the workaround
described in this post no longer works with the latest firmware. I tested
version 4.33.19759. If anyone knows how to restore the behaviour please get in
touch.
[Skip to Instructions ⮷](#instructions)
I quickly discovered this is a known issue with Kobo readers dating back years:

View file

@ -0,0 +1,6 @@
+++
title = "2023"
sort_by = "date"
paginate_by = 5
transparent = true
+++

View file

@ -0,0 +1,39 @@
+++
title = "Australian and New Zealand Makers on YouTube"
date = 2023-04-11T10:04:27+10:00
[extra]
updated = 2023-04-14T12:16:17+10:00
+++
I decided I wanted to add some more local folks into my YouTube subscriptions.
I [put the call out on Mastodon][toot] for suggestions for folks doing videos
about machining, woodworking, electronics, software, that type of thing. I
received a number of helpful replies and thought it might be useful to collect
the list (as well as ones I'm already subscribed to) on this page in case
others are looking for new channels to check out.
<!-- more -->
* [Andrew Newton](https://www.youtube.com/@AndrewNewton)
* [Artisan Makes](https://www.youtube.com/@artisanmakes)
* [BensWorx](https://www.youtube.com/@BensWorx)
* [Clickspring](https://www.youtube.com/@Clickspring)
* [Cutting Edge Engineering Australia](https://www.youtube.com/@CuttingEdgeEngineering)
* [EEVblog](https://www.youtube.com/@EEVblog)
* [iforce2d](https://www.youtube.com/@iforce2d)
* [Luke Towan](https://www.youtube.com/@LukeTowan)
* [Mach Super](https://www.youtube.com/@machsuper)
* [Maker's Muse](https://www.youtube.com/@MakersMuse)
* [MattKC](https://www.youtube.com/@MattKC) (Matt moved to the US but he still counts 🙂)
* [MooreTech](https://www.youtube.com/@MooreTech) -- It's me!
* [Pask Makes](https://www.youtube.com/@PaskMakes)
* [Primitive Technology](https://www.youtube.com/@primitivetechnology9550)
* [Psivewri](https://www.youtube.com/@psivewri)
* [SuperHouseTV](https://www.youtube.com/@SuperHouseTV)
* [Unexpected Maker](https://www.youtube.com/@UnexpectedMaker)
That's the list for now. If you have other suggestions (especially if they can
mix up the diversity of the list a bit) please let me know.
[toot]: https://mastodon.decentralised.social/@wezm/110165869937514088

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

View file

@ -0,0 +1,182 @@
+++
title = "How to use DeRez"
date = 2023-03-20T12:44:38+10:00
#[extra]
#updated = 2023-03-01T21:54:06+10:00
+++
After my post on trying to run
[Rust on Classic Mac OS post](@/posts/2023/rust-on-ppc-classic-mac-os/index.md) I continued trying to
find a modern language that I can use to build classic Mac OS software. I've
had some success with [Nim] and built a little
[temperature converter application][nim-temp-src]. As part of this I wanted to be able to use
[ResEdit] to edit the layout of the dialog. The problem was that I need a way
to convert the modified resources back into the textual representation used in
the source code. In this post I describe how I did this with `DeRez`.
<!-- more -->
{{ video(video="posts/2023/derez/mac-nim-temp.mp4", height=227, poster="png", preload="auto", alt="Video of the temperature converter application.", caption="Video of the temperature converter application.") }}
To build the temperature converter I started with the [Dialog sample] from
[Retro68], which looks like this:
{{ figure(image="posts/2023/derez/Dialog.png", link="posts/2023/derez/Dialog.png", pixelated=true, alt="Screenshot of the Dialog sample from Retro68. It has a static text item, edit text item, check box, two radio buttons, and a Quit button.", caption="Dialog Sample") }}
I opened it up in ResEdit and edited the `DITL` (Dialog Template) resource to
add the icon and temperature fields. I also added a new `ICON` resource and
drew a little thermometer:
{{ figure(image="posts/2023/derez/DITL.png", link="posts/2023/derez/DITL.png", pixelated=true, alt="Screenshot of the ResEdit DITL editor editing the DITL resource for my temperature converter.", caption="Editing DITL resource in ResEdit") }}
With the changes made, I now wanted to convert the binary resources stored in
the resource fork back into the [textual format used in the source code][dialog.r].
I believe the format is called `Rez`, here's a snippet of it:
```
resource 'DITL' (128) {
{
{ 190-10-20, 320-10-80, 190-10, 320-10 },
Button { enabled, "Quit" };
{ 190-10-20-5, 320-10-80-5, 190-10+5, 320-10+5 },
UserItem { enabled };
{ 10, 10, 30, 310 },
StaticText { enabled, "Static Text Item" };
{ 40, 10, 56, 310 },
EditText { enabled, "Edit Text Item" };
{ 70, 10, 86, 310 },
CheckBox { enabled, "Check Box" };
{ 90, 10, 106, 310 },
RadioButton { enabled, "Radio 1" };
{ 110, 10, 126, 310 },
RadioButton { enabled, "Radio 2" };
}
};
```
This turned out to be a bit of journey and the motivation for this blog post.
As part of the [Macintosh Programmers Workshop][mpw] (MPW) theres is a tool
called `DeRez` that does what I want. First up I had to work out how to operate
MPW. It's an editable shell where you run commands with ⌘-Return. Once I worked
that out I could run `DeRez` on my edited application but I only got the
fullback hexadecimal representation of the resources, not the structured output
I wanted:
```
data 'DITL' (128) {
$"0007 0000 0000 00A0 00E6 00B4 0136 0404" /* ....... .æ.´.6.. */
$"5175 6974 0000 0000 009B 00E1 00B9 013B" /* Quit.....›.á.¹.; */
$"0000 0000 0000 0046 000A 005A 0136 0808" /* .......F...Z.6.. */
$"4865 6C6C 6F20 5E30 0000 0000 001E 000A" /* Hello ^0........ */
$"003E 002A A002 0597 0000 0000 0014 0032" /* .>.* ..—.......2 */
$"0024 007D 8807 4365 6C73 6975 7300 0000" /* .$.}ˆ.Celsius... */
$"0000 0014 00AA 0024 00F5 8809 4661 7265" /* .....ª.$.õˆÆFare */
$"6E68 6569 7400 0000 0000 0029 0036 0039" /* nheit......).6.9 */
$"0081 1009 4564 6974 2054 6578 7400 0000" /* ..ÆEdit Text... */
$"0000 002B 00AE 003B 00F9 1009 4564 6974" /* ...+.®.;.ù.ÆEdit */
$"2054 6578 7400" /* Text. */
};
```
`Help DeRez` in MPW didn't shed much light on the problem but after a lot of
searching online I eventually found some extra details in the [man page for
`DeRez`][DeRez man] shipped on Mac OS X. Specifically:
> The type declarations for the standard
> Macintosh resources are contained in the `Carbon.r` resource header file,
> contained in the Carbon framework. You may use the ${RIncludes} shell
> environment variable to define a default path to resource header files.
> If you do not specify any type declaration files, `DeRez` produces data
> statements in hexadecimal form.
and
> You can also specify resource description
> files containing type declarations. For each type declaration file on
> the command line, DeRez applies the following search rules:
>
> 1. DeRez tries to open the file with the name specified as is.
>
> 2. If rule 1 fails and the filename contains no colons or begins with a
> colon, DeRez appends the filename to each of the pathnames specified by
> the {RIncludes} environment variable and tries to open the file.
With this information I was able to construct a command that worked:
```
DeRez -i 'Macintosh HD:MPW-GM:Interfaces&Libraries:Interfaces:RIncludes:' "Macintosh HD:Retro68:Retro68App" Carbon.r
```
`-i` sets the include path for type declarations and `Carbon.r` tells it to use
that file for resource descriptions. Running the command I was now rewarded
with textual resources:
{{ figure(image="posts/2023/derez/MPW.png", link="posts/2023/derez/MPW.png", pixelated=true, alt="Screenshot of an MPW worksheet on Mac OS 8 showing the output of running DeRez on an application.", caption="DeRez output in MPW") }}
To get the text out of the VM I copied and pasted it into a new document in
[BBEdit] (version 5.0) and saved it with Unix line endings to the Unix folder
that [SheepShaver] shares with the host and with that I was able to update the
[resource file in my temperature converter project][my dialog.r].
### Honorable Mention
Whilst trying to work out how to do all this I was also reminded of Ninji's
[mpw-emu] project ([detailed write-up on their blog][mpw-emu-blog]). It
combines an emulator with implementations of library functions in order to be
able to run MPW tools directly (outside a Mac OS emulator). It has gained
support for `DeRez` so you can run `DeRez` directly on a host system like
Linux.
I [MacBinaried][MacBinary] `DeRez` in SheepShaver and copied it to my Linux host. Then with a bit
of fussing with `mpw-emu` Rust code I was able to run it:
```
$ mpw-emu ~/Documents/Classic\ Mac/Shared\ 2/DeRez.bin
[2023-03-20T02:11:07Z ERROR emulator] Unimplemented call to InterfaceLib::SetFScaleDisable @10012C6C
[2023-03-20T02:11:07Z ERROR stdio] Unimplemented format character: P
[2023-03-20T02:11:07Z ERROR emulator] Unimplemented call to InterfaceLib::SecondsToDate @1000B2A4
### /home/wmoore/Documents/Classic Mac/Shared 2/DeRez.bin - No filename to de-compile was specified.
### /home/wmoore/Documents/Classic Mac/Shared 2/DeRez.bin - Usage: /home/wmoore/Documents/Classic Mac/Shared 2/DeRez.bin resourceFile [-c] [-d name[=value]] [-e] [-i path] [-m n] [-noResolve [output | include]] [-only type[(id[:id])]] [-p] [-rd] [-s type[(id[:id])]] [-script japanese | tradChinese | simpChinese | korean] [-u name] [file…].
```
Amazing!
Unfortunately I don't think `DeRez` will work this way outside a macOS host. It
needs to be able to read the resource fork of the application I edited with
ResEdit and that is not preserved on Linux:
```
$ mpw-emu ~/Documents/Classic\ Mac/Shared\ 2/DeRez.bin Dialog.APPL
[2023-03-20T02:14:05Z ERROR emulator] Unimplemented call to InterfaceLib::SetFScaleDisable @10012C6C
[2023-03-20T02:14:05Z ERROR stdio] Unimplemented format character: P
[2023-03-20T02:14:05Z ERROR emulator] Unimplemented call to InterfaceLib::SecondsToDate @1000B2A4
### /home/wmoore/Documents/Classic Mac/Shared 2/DeRez.bin - The resource fork of "Dialog.APPL" is empty and uninitialized.
```
If you're on macOS I think that this would actually work. Although now I think
about it Xcode ships (or at least used to) a native version of `DeRez` so now
I'm not sure what Ninji's motivation for making it work in `mpw-emu` was.
Perhaps it is possible to use on Linux somehow...
[Nim]: https://nim-lang.org/
[ResEdit]: https://en.wikipedia.org/wiki/ResEdit
[Retro68]: https://github.com/autc04/Retro68
[Dialog sample]: https://github.com/autc04/Retro68/tree/5f882506013a0a8a4335350197a1b7c91763494e/Samples/Dialog
[dialog.r]: https://github.com/autc04/Retro68/blob/5f882506013a0a8a4335350197a1b7c91763494e/Samples/Dialog/dialog.r
[DeRez man]: https://www.manpagez.com/man/1/DeRez/
[BBEdit]: http://www.barebones.com/products/bbedit/index.html
[SheepShaver]: https://sheepshaver.cebix.net/
[my dialog.r]: https://github.com/wezm/classic-mac-nim/blob/39e6ed7c2b31c20b775782319cde8ae5a43e1512/dialog.r
[mpw-emu]: https://github.com/Treeki/mpw-emu
[mpw-emu-blog]: https://wuffs.org/blog/emulating-mac-compilers
[mpw]: https://en.wikipedia.org/wiki/Macintosh_Programmer%27s_Workshop
[MacBinary]: https://en.wikipedia.org/wiki/MacBinary
[nim-temp-src]: https://github.com/wezm/classic-mac-nim/tree/39e6ed7c2b31c20b775782319cde8ae5a43e1512

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View file

@ -0,0 +1,112 @@
+++
title = "divmod, Rust, x86, and Optimisation"
date = 2023-01-11T19:48:09+10:00
[extra]
updated = 2023-01-11T21:11:28+10:00
+++
While reviewing some Rust code that did something like this:
```rust
let a = n / d;
let b = n % d;
```
I lamented the lack of a `divmod` method in Rust (that would return both the
quotient and remainder). My colleague [Brendan] pointed out that he actually
[added it][rust-div-mod] back in 2013 but it was moved out of the standard
library before the 1.0 release.
<!-- more -->
I also learned that the [`div` instruction on x86][div] provides the remainder
so there is potentially some benefit to combining the operation. I suspected
that LLVM was probably able to optimise the separate operations and a trip to
[the Compiler Explorer][compiler-explorer] confirmed it.
This function:
```rust
pub fn divmod(n: usize, d: usize) -> (usize, usize) {
(n / d, n % d)
}
```
Compiles to the following assembly, which I have annotated with my
understanding of each line (Note: I'm still learning x86 assembly):
```asm
; rdi = numerator, rsi = denominator
example::divmod:
test rsi, rsi ; check for denominator of zero
je .LBB0_5 ; jump to div zero panic if zero
mov rax, rdi ; load rax with numerator
or rax, rsi ; or rax with denominator
shr rax, 32 ; shift rax right 32-bits
je .LBB0_2 ; if the result of the shift sets the zero flag then numerator and
; denominator are 32-bit since none of the upper 32-bits are set.
; jump to 32-bit division implementation
mov rax, rdi ; move numerator into rax
xor edx, edx ; zero edx (I'm not sure why, might be relevant to the calling
; convention and is used by the caller?)
div rsi ; divide rax by rsi
ret ; return, quotient is in rax, remainder in rdx
; 32 bit implementation
.LBB0_2:
mov eax, edi ; move edi to eax (32-bit regs)
xor edx, edx ; zero edx
div esi ; divide eax by esi
ret
; div zero panic
.LBB0_5:
push rax
lea rdi, [rip + str.0]
lea rdx, [rip + .L__unnamed_1]
mov esi, 25
call qword ptr [rip + core::panicking::panic@GOTPCREL]
ud2
.L__unnamed_2:
.ascii "/app/example.rs"
.L__unnamed_1:
.quad .L__unnamed_2
.asciz "\017\000\000\000\000\000\000\000\002\000\000\000\006\000\000"
str.0:
.ascii "attempt to divide by zero"
```
I found it interesting that after checking for a zero denominator there's an
additional check to see if the values fit into 32-bits, and if so it jumps to an
instruction sequence that uses 32-bit registers. According to [the testing done
in this report][timing] 32-bit `div` has lower latency—particularly on older
models.
~~I wasn't able to work out why each implementation zeros `edx`. If you know,
send me a message and I'll update the post.~~
**Update:** [Brion Vibber on the Fediverse][edx] provided this explanation as to why `edx`
is being zeroed:
> iirc rdx / edx is the top word for the x86 division operation, which takes a double-word numerator -- the inverse of multiplication producing a double-word output.
This makes sense and looking back at [the docs][div] it does say that:
> 32-bit: Unsigned divide EDX:EAX by r/m32, with result stored in EAX := Quotient, EDX := Remainder.
> 64-bit: Unsigned divide RDX:RAX by r/m64, with result stored in RAX := Quotient, RDX := Remainder.
[View the Example on Compiler Explorer](https://rust.godbolt.org/z/hj9rb4Txa)
[Brendan]: https://github.com/brendanzab
[rust-div-mod]: https://github.com/rust-lang/rust/commit/f39152e07baf03fc1ff4c8b2c1678ac857b4a512
[div]: https://www.felixcloutier.com/x86/div
[compiler-explorer]: https://rust.godbolt.org/
[timing]: https://gmplib.org/~tege/x86-timing.pdf
[edx]: https://bikeshed.vibber.net/@brion/109670222269686433

View file

@ -0,0 +1,45 @@
+++
title = "Hide Sign in With Google Pop Up"
date = 2023-01-20T20:48:02+10:00
#[extra]
#updated = 2023-01-11T21:11:28+10:00
+++
Inspired by
[Rach Smith's post on using userstyles to hide YouTube shorts][rachsmith]
I came up with some CSS to hide those
annoying Sign in with Google pop-ups.
<!-- more -->
I never want to sign in with Google and use [Firefox Multi-Account Containers][containers]
to ensure that the bulk of my browsing is done without ever being
signed in to a Google account. This means that I see a lot of these pop ups
encouraging me to sign in, so Google can track me more.
{{ figure_no(image="posts/2023/hide-sign-in-with-google/sign-in-with-google.png", border=true, width=357, alt="Screenshot of the pop up", caption="Be gone evil pop up") }}
I use [Stylus] to manage user styles. I created a new rule that applies to all
sites and put this CSS in there:
```css
#credential_picker_container:has(iframe[src*="accounts.google.com"]) {
display: none;
}
```
Note that it uses the [`:has`][has] selector, which is not on by default in Firefox
at the time of writing. In Firefox 103 onwards you can enable it by toggling
`layout.css.has-selector.enabled` (the usual caveats about poking around in
[`about:config`][about-config] apply).
If you do browse while signed in to a Google account, Aranjedeath on the Fediverse
pointed out that [you can turn them off in your account settings][Aranjedeath].
[containers]: https://support.mozilla.org/en-US/kb/containers
[Aranjedeath]: https://hachyderm.io/@Aranjedeath/109720032830494381
[rachsmith]: https://rachsmith.com/remove-youtube-shorts/
[Stylus]: https://github.com/openstyles/stylus
[about-config]: https://support.mozilla.org/en-US/kb/about-config-editor-firefox
[has]: https://developer.mozilla.org/en-US/docs/Web/CSS/:has

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View file

@ -0,0 +1,44 @@
+++
title = "Fixing OpenBSD panic dc_atapi_start: not ready in KVM"
date = 2023-09-17T12:13:24+10:00
[extra]
updated = 2023-09-22T11:56:57+10:00
+++
I tried creating an OpenBSD 7.3 virtual machine on my new computer (Arch Linux
host) and the installer kept crashing with the error:
{{ figure(image="posts/2023/openbsd-db-atapi-start-not-ready/openbsd_panic_dc_atapi_start_not_ready.png", link="posts/2023/openbsd-db-atapi-start-not-ready/openbsd_panic_dc_atapi_start_not_ready.png", alt="Screenshot of the installer crash.", caption="Screenshot of the installer crash.") }}
```
dc_atapi_start: not ready, st = 50
fatal protection fault in supervisor mode trap type 4 code 0 rip ffffffff810089d9 cs 8 rflags 10282 cr2 287eb3000 cpl 6 rsp ffff800014fd11a0
gssbase Oxffffffff818fbff0 kgsbase Ox0
panic: trap type 4, code=0, pc=ffffffff810089d9
syncing disks...12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 _
```
<!-- more -->
I did a bunch of searching online and tried a several different suggestions but that one that
worked for me was from [this Reddid thread][reddit]:
> **tinneriw31**
>
> Switch the virtual cd drive from ide to sata. Worked for me. Exact same issue.
I use [virt-manager] to manage VMs. These are the steps to do that when creating the VM:
1. Create the VM and at the last step choose "Customize configuration before install"
2. Click on the "IDE CDROM 1" tab and change "Disk bus" to SATA
3. Then click Apply, and then Begin installation in the top left.
After that the VM installed successfully.
{{ figure(image="posts/2023/openbsd-db-atapi-start-not-ready/virt-manager-customize-configuration.png", link="posts/2023/openbsd-db-atapi-start-not-ready/virt-manager-customize-configuration.png", alt="Screenshot of step 5 in the new virtual machine wizard in virt-manager showing the 'Customize configuration before install' option checked.", caption="Customize configuration before install.", width=504) }}
{{ figure(image="posts/2023/openbsd-db-atapi-start-not-ready/virt-manager-sata-cd-rom.png", link="posts/2023/openbsd-db-atapi-start-not-ready/virt-manager-sata-cd-rom.png", alt="Screenshot of the virt-manager CD ROM tab showing 'Disk bus: SATA' selected.", caption="Disk bus: SATA", width=1028) }}
[reddit]: https://www.reddit.com/r/openbsd/comments/12jzg2y/when_i_tried_to_install_openbsd_73_in_qemu_i/jhhk1gx/
[virt-manager]: https://virt-manager.org/

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

View file

@ -0,0 +1,297 @@
+++
title = "Building a Classic Mac OS App in Rust"
date = 2023-03-31T13:26:07+10:00
#[extra]
#updated = 2023-03-26T14:27:05+10:00
+++
Instead of using my funemployment to build useful things I have continued to
build things for old versions of Mac OS. Through some luck and a little
persistence I have actually managed to get Rust code running on classic Mac OS
(I've tried Mac OS 7.5 and 8.1). In this post I'll cover how I got here and
show a little network connected demo application I built—just in time for
the end of [#MARCHintosh].
<!-- more -->
Before I get into the details this is where we're headed:
{{ video(video="posts/2023/rust-classic-mac-os-app/ferris-weather-2023-03-31_12.37.49_edit.mp4", height=480, poster="png", preload="auto", alt="Video showing the Ferris Weather application in operation. Initially there is a window with a Ferris icon, the text, 'An application that exercises my Open Transport Rust bindings and HTTP client', and a button 'Get Weather'. Clicking the button results in an alert that says: 'The temperature in Brisbane is 26.9°C'.", caption="Ferris Weather, a weather app built with Rust (and some C)") }}
### DeRez Redux
In [the last post](@/posts/2023/derez/index.md) I got Nim code running on Mac
OS and toyed with DeRez. [The author of `mpu-emu` replied on Mastodon][Ninji]
letting me know that DeRez _should_ run via `mpw-emu` on Linux as the
filesystem layer transparently handles [MacBinary] files.
I spent some time in the debugger and worked out that `mpw-emu` supported
MacBinary III but [Retro68] produced MacBinary II files. I contributed code
to `mpw-emu` to add MacBinary II support and enabled some latent support for
UNIX paths. After that DeRez did work (almost):
```
$ mpw-emu ~/Documents/Classic\ Mac/Shared\ 2/DeRez.bin Root:home:wmoore:Projects:classic-mac-rust:cmake-build-retro68ppc:Dialog.bin
[2023-03-31T04:51:34Z ERROR emulator] Unimplemented call to InterfaceLib::SetFScaleDisable @10012C6C
[2023-03-31T04:51:34Z ERROR stdio] Unimplemented format character: P
[2023-03-31T04:51:34Z ERROR emulator] Unimplemented call to InterfaceLib::SecondsToDate @1000B2A4
data 'DITL' (128) {
$"0007 0000 0000 00A0 00E6 00B4 0136 0404" /* .......†.Ê.¥.6.. */
$"5175 6974 0000 0000 009B 00E1 00B9 013B" /* Quit.....õ.·.π.; */
$"0000 0000 0000 0046 000A 005A 0136 0818" /* .......F...Z.6.. */
$"436F 6E76 6572 7369 6F6E 2070 6F77 6572" /* Conversion power */
$"6564 2062 7920 5E30 0000 0000 001E 000A" /* ed by ^0........ */
$"003E 002A A002 0080 0000 0000 0014 0032" /* .>.*†..Ä.......2 */
$"0024 007D 8807 4365 6C73 6975 7300 0000" /* .$.}à.Celsius... */
$"0000 0014 00AA 0024 00F5 8809 4661 7265" /* .....™.$.ıà∆Fare */
$"6E68 6569 7400 0000 0000 0029 0036 0039" /* nheit......).6.9 */
$"0081 1002 3235 0000 0000 002B 00AE 003B" /* .Å..25.....+.Æ.; */
$"00F9 1002 3737" /* .˘..77 */
};
/* etc */
```
Success! DeRez running on Linux… only thing is that when you point at the type
definitions to get structured output instead of hex dumps it hits an
unimplemented function in `mpw-emu`. It's on my to-do list to fix that:
```
$ mpw-emu ~/Documents/Classic\ Mac/Shared\ 2/DeRez.bin Root:home:wmoore:Projects:classic-mac-rust:cmake-build-retro68ppc:Dialog.bin Root:home:wmoore:Source:github.com:autc04:Retro68:InterfacesAndLibraries:Interfaces\&Libraries:Interfaces:RIncludes:Carbon.r
[2023-03-31T04:53:07Z ERROR emulator] Unimplemented call to InterfaceLib::SetFScaleDisable @10012C6C
[2023-03-31T04:53:07Z ERROR stdio] Unimplemented format character: P
[2023-03-31T04:53:07Z ERROR emulator] Unimplemented call to InterfaceLib::SecondsToDate @1000B2A4
[2023-03-31T04:53:07Z ERROR emulator] Unimplemented call to StdCLib::fseek @10006A8C
File "Root:home:wmoore:Source:github.com:autc04:Retro68:InterfacesAndLibraries:Interfaces&Libraries:Interfaces:RIncludes:CoreServices.r"; Line 0; ### /home/wmoore/Documents/Classic Mac/Shared 2/DeRez.bin - Can't FSeek on file Root:home:wmoore:Source:github.com:autc04:Retro68:InterfacesAndLibraries:Interfaces&Libraries:Interfaces:RIncludes:CoreServices.r.
File "Root:home:wmoore:Source:github.com:autc04:Retro68:InterfacesAndLibraries:Interfaces&Libraries:Interfaces:RIncludes:CoreServices.r"; Line 0; ### /home/wmoore/Documents/Classic Mac/Shared 2/DeRez.bin - Fatal Error, can't recover.
```
### MacBinary
Poking at the MacBinary code in `mpw-emu` got me wondering if there was already
a MacBinary crate that could be used. Turns out there wasn't so I somehow
nerd-sniped myself into [building one][macbinary-crate].
The first challenge was finding a decent specification for the three versions
of MacBinary. I was eventually I was able to dig up the following:
- [MacBinary I]
- [MacBinary II]
- [MacBinary III]
I then set about building the parser. I reused the binary parser code from
[Allsorts] since I was already familiar with that code. I hit another roadblock
when it came to the CRC in the header. Nothing describes the actual CRC
algorithm used. I tried the CRC reversing tool [CRC RevEng][RevEng] without
success. A lot of existing code seemed to use an implementation that originated
in a late 80's UNIX utility, [mcvert], that has unclear licensing. I wanted to
use the Rust [crc crate] instead.
I eventually stumbled on the blog post,
[Detecting MacBinary format](https://entropymine.wordpress.com/2019/02/13/detecting-macbinary-format/),
which included the line:
> Note that the spec does not even tell you what CRC algorithm to use — you
> have to be a detective to figure it out. (Its the one sometimes called
> CRC16-CCITT.)
That was the tip I needed and with a little trial an error I eventually worked
out that it was [CRC-16/XMODEM] also known as `CRC-16/CCITT-FALSE`. In
hindsight I could probably have worked this out from the discussion of XMODEM
in the [MacBinary I spec][MacBinary I].
With that sorted I was able to wrap up the parser and do some testing. I could
now read the resource and data forks and figured it would be interesting to be
able to parse the resource data too, so I added a resource fork parser as well.
I wrote the parsers in a way that does not require heap allocation—only
borrowing from the underlying data. Due to this it was straightforward to make
the crate compatible with `no_std`, which allows it to be used in embedded
environments and WebAssembly.
As something of a test-bed I created some
WebAssembly bindings and built a page that allows you inspect MacBinary
files online, with all parsing done client-side via the crate. You can
try it out at: <https://7bit.org/macbinary/>
### Rust on Mac OS
Now that I was well and truly in the classic Mac space again I took another
stab at compiling Rust for PPC Mac OS
([see this post for my previous attempt](@/posts/2023/rust-on-ppc-classic-mac-os/index.md)).
It seemed that using the
`powerpc-ibm-aix` LLVM target was most likely to produce a compatible library
(Apple used AIX conventions for PPC Mac OS). Problem was that it was hitting
unimplemented code in LLVM:
> LLVM ERROR: relocation for paired relocatable term is not yet supported
I set about trying to work out how this code path was being hit and ran `rustc`
in a debugger. Unsurprisingly there were no debug symbols so I built `rustc`
and LLVM from source. This was my `config.toml` for the Rust repo:
```toml
[llvm]
release-debuginfo = true
download-ci-llvm = false
link-jobs = 4
```
After repeatedly running out of disk space and memory compiling LLVM (the
binaries with debug info are huge) I eventually had new Rust compiler.
Some of the LLVM binaries:
```
.rwxr-xr-x 2.0G wmoore 26 Mar 20:05 llc
.rwxr-xr-x 2.1G wmoore 26 Mar 20:10 llvm-opt-fuzzer
.rwxr-xr-x 2.1G wmoore 26 Mar 20:04 bugpoint
.rwxr-xr-x 2.2G wmoore 26 Mar 20:09 llvm-lto2
.rwxr-xr-x 2.2G wmoore 26 Mar 20:06 llvm-lto
.rwxr-xr-x 2.2G wmoore 26 Mar 20:11 opt
.rwxr-xr-x 2.3G wmoore 26 Mar 20:11 llvm-reduce
```
I linked the new compiler into `rustup` and then repeated my previous steps in
the debugger… except this time the code compiled and did not hit the
unimplemented LLVM code. This was my first lucky break. I'm not sure what
changed but it was now happily compiling the code. I switched to a recent
nightly compiler and that worked too! No need to build from source.
I repeated the step described in my original post of using
`powerpc-linux-gnu-objcopy` to convert the static library archive (`.a`) to a
format that Retro68 would accept. After some fighting with `binutils` I was
finally able to get it to link!
<iframe src="https://mastodon.decentralised.social/@wezm/110098546361010915/embed" class="mastodon-embed" style="max-width: 100%; border: 0" width="400" allowfullscreen="allowfullscreen"></iframe><script src="https://mastodon.decentralised.social/embed.js" async="async"></script>
I rebuilt the temperature converter that I'd built in Nim in Rust ([source code][rust-temp]) and ran into
more linker/`binutils` issues. After a _lot_ of trial-and-error and some more
luck I was able to solve that by using the updated `binutils` on the
`gcc12-update branch` branch of Retro68. I now had a working temperature
converter:
{{ video(video="posts/2023/rust-classic-mac-os-app/classic-mac-rust-2023-03-28_20.00.31.mp4", height=480, poster="png", preload="auto", alt="Video of the temperature converter converting values to and from Celsius, running on Mac OS 8.1 (in emulator).", caption="The temperature converter application ported to Rust") }}
It worked on Mac OS 7.5 too:
{{ figure(image="posts/2023/rust-classic-mac-os-app/rust-on-mac-os-7.png", link="posts/2023/rust-classic-mac-os-app/rust-on-mac-os-7.png", pixelated=true, border=1, alt="Screenshot of the temperature converter application running on Mac OS 7.5 (in emulator).", caption="Temperature converter application running on Mac OS 7.5") }}
The Rust version is a bit more efficient than the [Nim version][nim-version] as
it avoids some copying and heap allocation. That latter of which because I'm
coding in a `no_std` environment without a heap.
The Rust standard library is divided into three main parts (crates):
1. `core` for things that do not require heap allocation, I/O, etc.
2. `alloc` for things that use heap allocation but not I/O etc.
3. `std`, the rest: files, networking, threads, etc. `std` re-exports the
other two.
By defining a custom allocator that called `malloc` and `free` provided by the
Retro68 environment I was able to use the `alloc` crate in addition to `core`.
This gained me access `String`, `Vec`, and friends.
#### Networking
I now wanted to build something a little more involved than a single dialog. I
set about building bindings to Open Transport, Apple's network stack introduced
with PCI Power Macs (like my 9500).
Due to its heritage most of the Mac OS toolbox functions use the Pascal calling
convention, [which LLVM does not support][llvm-pascal]. To bridge the C (and
Rust) world to this Pascal world I had to create [trampoline functions] in C
for each toolbox function that I wanted to call from Rust (if there's a better
way to do this I'd love to know how). This works because `gcc` in Retro68
understands both C and Pascal calling conventions. I appended an underscore to
each of the wrapper functions. For example:
```c
OSStatus OTConnect_(EndpointRef ref, TCall *sndCall, TCall *rcvCall) {
return OTConnect(ref, sndCall, rcvCall);
}
```
I used the "Downloading a URL With HTTP" example from the [Networking With Open Transport]
book as a guide for the functions I needed. Once the bindings were created I
implemented the `TcpClientStack` trait from the [embedded-nal] \(embedded network abstraction layer)
crate against Open Transport. Next I used this with the [http_io] crate to be able
to make HTTP requests.
As an initial test I wrote an app to fetch a friend's website (since it's
available over plain HTTP) and show an alert with the number of bytes read.
Amazingly this worked on the first try: the Open Transport bindings, the
`TcpClientStack` implementation, the HTTP client, and my test code all worked!
Finally I used my newfound networking abilities to build [Ferris
Weather][ferris-weather], the application shown at the start of the post. This
uses the HTTP client to fetch a JSON file containing weather observations,
parses it with [serde] and then shows an alert with the most recent
observation. I also drew a little 1-bit [Ferris the Rustacean][Ferris] in
[ResEdit] for it.
The idea for this was prompted by the [Australian Bureau of Meteorology][bom]
still being accessible over HTTP. Unfortunately it wasn't working and after a
lot of debugging I eventually discovered that I triggering their anti-scraping
blocker for some reason. To work around this I copied a snapshot of the JSON to
my own server. So, unfortunately the data shown by the application does not
update but you still get the idea.
{{ figure(image="posts/2023/rust-classic-mac-os-app/Ferris%20Weather.png", link="posts/2023/rust-classic-mac-os-app/Ferris%20Weather.png", pixelated=true, border=1, alt="Screenshot of the Ferris Weather application showing an alert with the temperature in Brisbane.", caption="Ferris Weather") }}
So there you have it, that's how I built an application in Rust (and some C)
for classic Mac OS. The [source code to Ferris Weather][ferris-weather] is on
GitHub.
### Next
My intention is to take a bit of a break from classic Mac OS for a bit and work
on some other projects—ones that might be useful to people in this century—but
there are some things I want to look at when I come back to it:
First is TLS support for the HTTP client. I think this should be relatively
straightforward with the [embedded-tls] crate.
Next I'd like to improve how Open Transport is used. I think with either
the synchronous, non-blocking mode I'm using now or the asynchronous mode it
should be possible to tie it into the async Rust ecosystem, which would allow
it to play nicer with the event loop and cooperative multi-tasking.
Finally, so far I've been working without the full Rust standard library, only
`core` and `alloc`. It seems like it should be possible to implement a lot of
the remaining standard library (io, networking), on top of the Mac OS toolbox,
but that's a lot of work and will have to wait for another time.
### Hire Me
As mentioned at the start of this post I'm currently taking a break from
employment but I will be looking for a new role next month, so if you're looking
for a Rust developer get in touch.
[#MARCHintosh]: https://www.marchintosh.com/
[Ninji]: https://vulpine.club/@Ninji/110053455721324087
[mcvert]: https://web.mit.edu/~mkgray/jik/sipbsrc/src/mcvert/mcvert.c
[Networking With Open Transport]: https://developer.apple.com/library/archive/documentation/mac/NetworkingOT/NetworkingOpenTransport.pdf
[ferris-weather]: https://github.com/wezm/ferris-weather
[Retro68]: https://github.com/autc04/Retro68
[MacBinary]: https://en.wikipedia.org/wiki/MacBinary
[macbinary-crate]: https://lib.rs/crates/macbinary
[Allsorts]: https://github.com/yeslogic/allsorts
[RevEng]: https://reveng.sourceforge.io/
[crc crate]: https://lib.rs/crates/crc
[CRC-16/XMODEM]: https://reveng.sourceforge.io/crc-catalogue/16.htm#crc.cat.crc-16-ibm-3740
[MacBinary I]: https://web.archive.org/web/20050307030202/http://www.lazerware.com/formats/macbinary/macbinary.html
[MacBinary II]: https://web.archive.org/web/20050305042909/http://www.lazerware.com/formats/macbinary/macbinary_ii.html
[MacBinary III]: https://web.archive.org/web/20050305044255/http://www.lazerware.com/formats/macbinary/macbinary_iii.html
[llvm-pascal]: https://github.com/llvm/llvm-project/blob/bd20a344bbf813b2c39b57ad1a5248bff915ce25/clang/lib/CodeGen/CGCall.cpp#L60
[trampoline functions]: https://en.wikipedia.org/wiki/Trampoline_(computing)
[embedded-nal]: https://docs.rs/embedded-nal/latest/embedded_nal/
[http_io]: https://lib.rs/crates/http_io
[serde]: https://serde.rs/
[bom]: http://www.bom.gov.au/
[Ferris]: https://rustacean.net/
[ResEdit]: https://en.wikipedia.org/wiki/ResEdit
[embedded-tls]: https://lib.rs/crates/embedded-tls
[rust-temp]: https://github.com/wezm/classic-mac-rust
[nim-version]: https://github.com/wezm/classic-mac-nim

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 609 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

View file

@ -0,0 +1,451 @@
+++
title = "Trying to Run Rust on Classic Mac OS"
date = 2023-02-27T10:06:28+10:00
[extra]
updated = 2023-03-26T14:27:05+10:00
+++
I recently acquired a Power Macintosh 9500/150 and after cleaning it up and
building a [BlueSCSI] to replace the failed hard drive it's now in a
semi-operational state. This weekend I thought I'd see if I could build a Mac
app for it that called some Rust code. This post details my trials and
tribulations.
<!-- more -->
I started by building [Retro68], which is a modernish GCC based toolchain
that allows cross-compiling applications for 68K and PPC Macs. With Retro68
built I set up a VM in [SheepShaver] running Mac OS 8.1. Using the LaunchAAPL
and LaunchAAPLServer tools that come with Retro68 I was able to build the
sample applications and launch them in the emulated Mac.
With the basic workflow working I set about creating a Rust project that built
a static library with one very basic exported function. It just returns a
static [Pascal string] when called.
```rust
#![no_std]
#![feature(lang_items)]
use core::panic::PanicInfo;
static MSG: &[u8] = b"\x04Rust";
#[no_mangle]
pub unsafe extern "C" fn hello_rust() -> *const u8 {
MSG.as_ptr()
}
#[panic_handler]
fn panic(_panic: &PanicInfo<'_>) -> ! {
loop {}
}
#[lang = "eh_personality"]
extern "C" fn eh_personality() {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_msg_is_pascal_string() {
assert_eq!(MSG[0], MSG[1..].len().try_into().unwrap());
}
}
```
Classic Mac OS is not a target that Rust knows about so I created a custom
target JSON definition named `powerpc-apple-macos.json` based on prior work by
kmeisthax in [this GitHub discussion](https://github.com/autc04/Retro68/discussions/123#discussioncomment-597268):
```json
{
"arch": "powerpc",
"data-layout": "E-m:a-p:32:32-i64:64-n32",
"executables": true,
"llvm-target": "powerpc-unknown-none",
"max-atomic-width": 32,
"os": "macosclassic",
"vendor": "apple",
"target-endian": "big",
"target-pointer-width": "32",
"linker": "powerpc-apple-macos-gcc",
"linker-flavor": "gcc",
"linker-is-gnu": true
}
```
I was able to build the static library with this cargo invocation:
```
cargo +nightly build --release -Z build-std=core --target powerpc-apple-macos.json
```
It's using nightly because it's using unstable features to build `core` and the
`eh_personality` lang item in the code.
This successfully compiles and produces
`target/powerpc-apple-macos/release/libclassic_mac_rust.a`
I used the [Dialog sample] from Retro68 as the basis of my Mac app. Here it is
running prior to Rust integration:
{{ figure(image="posts/2023/rust-on-ppc-classic-mac-os/dialog-sample.png", link="posts/2023/rust-on-ppc-classic-mac-os/dialog-sample.png", alt="Screenshot of SheepShaver running Mac OS 8.1. It shows some Finder windows with a frontmost dialog that has a text label, text field, radio buttons, check box and Quit button.", caption="Dialog Sample") }}
This is my tweaked version of the C file:
```c
/*
Copyright 2015 Wolfgang Thaller.
This file is part of Retro68.
Retro68 is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Retro68 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Retro68. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Quickdraw.h>
#include <Dialogs.h>
#include <Fonts.h>
#ifndef TARGET_API_MAC_CARBON
/* NOTE: this is checking whether the Dialogs.h we use *knows* about Carbon,
not whether we are actually compiling for Cabon.
If Dialogs.h is older, we add a define to be able to use the new name
for NewUserItemUPP, which used to be NewUserItemProc. */
#define NewUserItemUPP NewUserItemProc
#endif
extern ConstStringPtr hello_rust(void);
pascal void ButtonFrameProc(DialogRef dlg, DialogItemIndex itemNo)
{
DialogItemType type;
Handle itemH;
Rect box;
GetDialogItem(dlg, 1, &type, &itemH, &box);
InsetRect(&box, -4, -4);
PenSize(3,3);
FrameRoundRect(&box,16,16);
}
int main(void)
{
#if !TARGET_API_MAC_CARBON
InitGraf(&qd.thePort);
InitFonts();
InitWindows();
InitMenus();
TEInit();
InitDialogs(NULL);
#endif
DialogPtr dlg = GetNewDialog(128,0,(WindowPtr)-1);
InitCursor();
SelectDialogItemText(dlg,4,0,32767);
ConstStr255Param param1 = hello_rust();
ParamText(param1, "\p", "\p", "\p");
DialogItemType type;
Handle itemH;
Rect box;
GetDialogItem(dlg, 2, &type, &itemH, &box);
SetDialogItem(dlg, 2, type, (Handle) NewUserItemUPP(&ButtonFrameProc), &box);
ControlHandle cb, radio1, radio2;
GetDialogItem(dlg, 5, &type, &itemH, &box);
cb = (ControlHandle)itemH;
GetDialogItem(dlg, 6, &type, &itemH, &box);
radio1 = (ControlHandle)itemH;
GetDialogItem(dlg, 7, &type, &itemH, &box);
radio2 = (ControlHandle)itemH;
SetControlValue(radio1, 1);
short item;
do {
ModalDialog(NULL, &item);
if(item >= 5 && item <= 7)
{
if(item == 5)
SetControlValue(cb, !GetControlValue(cb));
if(item == 6 || item == 7)
{
SetControlValue(radio1, item == 6);
SetControlValue(radio2, item == 7);
}
}
} while(item != 1);
FlushEvents(everyEvent, -1);
return 0;
}
```
And this is the resource file (`dialog.r`):
```
/*
Copyright 2015 Wolfgang Thaller.
This file is part of Retro68.
Retro68 is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Retro68 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Retro68. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Dialogs.r"
resource 'DLOG' (128) {
{ 50, 100, 240, 420 },
dBoxProc,
visible,
noGoAway,
0,
128,
"",
centerMainScreen
};
resource 'DITL' (128) {
{
{ 190-10-20, 320-10-80, 190-10, 320-10 },
Button { enabled, "Quit" };
{ 190-10-20-5, 320-10-80-5, 190-10+5, 320-10+5 },
UserItem { enabled };
{ 10, 10, 30, 310 },
StaticText { enabled, "Hello ^0" };
{ 40, 10, 56, 310 },
EditText { enabled, "Edit Text Item" };
{ 70, 10, 86, 310 },
CheckBox { enabled, "Check Box" };
{ 90, 10, 106, 310 },
RadioButton { enabled, "Radio 1" };
{ 110, 10, 126, 310 },
RadioButton { enabled, "Radio 2" };
}
};
#include "Processes.r"
resource 'SIZE' (-1) {
reserved,
acceptSuspendResumeEvents,
reserved,
canBackground,
doesActivateOnFGSwitch,
backgroundAndForeground,
dontGetFrontClicks,
ignoreChildDiedEvents,
is32BitCompatible,
#ifdef TARGET_API_MAC_CARBON
isHighLevelEventAware,
#else
notHighLevelEventAware,
#endif
onlyLocalHLEvents,
notStationeryAware,
dontUseTextEditServices,
reserved,
reserved,
reserved,
#ifdef TARGET_API_MAC_CARBON
500 * 1024, // Carbon apparently needs additional memory.
500 * 1024
#else
100 * 1024,
100 * 1024
#endif
};
```
The main differences are:
* `extern` declaration for the Rust function
* Using the string returned from `hello_rust` to set `ParamText`
* Changing the StaticText control's text to "Hello ^0" in order to make use of
the `ParamText`
* Adding `target_link_libraries(Dialog ${CMAKE_SOURCE_DIR}/target/powerpc-apple-macos/release/libclassic_mac_rust.a)`
to `CMakeLists.txt` to have CMake link with the Rust library.
{{ figure(image="posts/2023/rust-on-ppc-classic-mac-os/ParamText.jpg", link="posts/2023/rust-on-ppc-classic-mac-os/ParamText.jpg", alt="Photo of ParamText documentation from my copy of Inside Macintosh Volume ", caption="ParamText documentation from my copy of Inside Macintosh Volume ") }}
Now when building the project we get…
```
ninja: Entering directory `cmake-build-retro68ppc'
[1/4] Linking C executable Dialog.xcoff
FAILED: Dialog.xcoff
: && /home/wmoore/Source/github.com/autc04/Retro68-build/toolchain/bin/powerpc-apple-macos-gcc -Wl,-gc-sections CMakeFiles/Dialog.dir/dialog.obj -o Dialog.xcoff /home/wmoore/Projects/classic-mac-rust/target/powerpc-apple-macos/release/libclassic_mac_rust.a && :
/home/wmoore/Source/github.com/autc04/Retro68-build/toolchain/lib/gcc/powerpc-apple-macos/9.1.0/../../../../powerpc-apple-macos/bin/ld:/home/wmoore/Projects/classic-mac-rust/target/powerpc-apple-macos/release/libclassic_mac_rust.a: file format not recognized; treating as linker script
/home/wmoore/Source/github.com/autc04/Retro68-build/toolchain/lib/gcc/powerpc-apple-macos/9.1.0/../../../../powerpc-apple-macos/bin/ld:/home/wmoore/Projects/classic-mac-rust/target/powerpc-apple-macos/release/libclassic_mac_rust.a:1: syntax error
collect2: error: ld returned 1 exit status
ninja: build stopped: subcommand failed.
```
It doesn't like `libclassic_mac_rust.a`. Some investigation shows that the objects in the library
are in ELF format. `powerpc-apple-macos-objcopy --info` shows that Retro68 does not handle
ELF:
```
BFD header file version (GNU Binutils) 2.31.1
xcoff-powermac
(header big endian, data big endian)
powerpc:common
rs6000:6000
srec
(header endianness unknown, data endianness unknown)
powerpc:common
rs6000:6000
symbolsrec
(header endianness unknown, data endianness unknown)
powerpc:common
rs6000:6000
verilog
(header endianness unknown, data endianness unknown)
powerpc:common
rs6000:6000
tekhex
(header endianness unknown, data endianness unknown)
powerpc:common
rs6000:6000
binary
(header endianness unknown, data endianness unknown)
powerpc:common
rs6000:6000
ihex
(header endianness unknown, data endianness unknown)
powerpc:common
rs6000:6000
xcoff-powermac srec symbolsrec verilog tekhex binary ihex
powerpc:common xcoff-powermac srec symbolsrec verilog tekhex binary ihex
rs6000:6000 xcoff-powermac srec symbolsrec verilog tekhex binary ihex
```
It looks like it really only supports `xcoff-powermac`, which was derived from
rs6000 AIX. At this point I tried to find a way to convert my ELF objects to
XCOFF. I eventually stumbled across
[this thread on the Haiku forum](https://discuss.haiku-os.org/t/xcoff-pef/12445/15)
that mentions that `powerpc-linux-gnu-binutils` on Debian knows about
`aixcoff-rs6000`. So I fired up a Debian docker container and tried converting
my `.a`, and it worked:
```
docker run --rm -it -v $(pwd):/src debian:testing
apt update
apt install binutils-powerpc-linux-gnu
powerpc-linux-gnu-objcopy -O aixcoff-rs6000 /src/target/powerpc-apple-macos/release/libclassic_mac_rust.a /src/target/powerpc-apple-macos/release/libclassic_mac_rust.obj
```
Examining the objects in the new archive showed that they were now in the same
format as the objects generated by Retro68. I updated the `CMakeLists.txt` to
point at the new library and tried building again:
```
/home/wmoore/Source/github.com/autc04/Retro68-build/toolchain/lib/gcc/powerpc-apple-macos/9.1.0/../../../../powerpc-apple-macos/bin/ld: /home/wmoore/Projects/classic-mac-rust/target/powerpc-apple-macos/release/libclassic_mac_rust.obj(classic_mac_rust-80e61781bab75910.classic_mac_rust.9ba2ce33-cgu.0.rcgu.o): class 2 symbol `hello_rust' has no aux entries
```
Now we get further. It can read the `.a` now and even sees the `hello_rust`
symbol but it
[looks like it's looking for an aux entry to determine the symbol type](https://github.com/autc04/Retro68/blob/5f882506013a0a8a4335350197a1b7c91763494e/binutils/bfd/xcofflink.c#L1461-L1478)
but not finding one. AUX entries are an
[XCOFF](https://www.ibm.com/docs/en/aix/7.2?topic=formats-xcoff-object-file-format)
thing.
One other thing I tried was setting the `llvm-target` in the custom target JSON
to `powerpc-ibm-aix`. Due to the heritage of PPC Mac OS the ABI is the same
(Apple used the AIX toolchain, which is why object files use XCOFF even though
executables use PEF). This target would be ideal as it would use the right ABI
and emit XCOFF by default.
Unfortunately it runs into unimplemented parts of LLVM's XCOFF implementation:
> LLVM ERROR: relocation for paired relocatable term is not yet supported
Rust uses a fork/snapshot of LLVM but the
[issue is still present in LLVM master](https://github.com/rust-lang/llvm-project/blob/5ef9f9948fca7cb39dd6c1935ca4e819fb7a0db2/llvm/lib/MC/XCOFFObjectWriter.cpp).
[This post on writing a Mac OS 9 application in Swift][swift] goes down a
similar path using the AIX target and also mentions patching the Swift compiler
to avoid the unsupported parts of LLVMs XCOFF implementation. That's an avenue
for future experimentation.
### rustc\_codegen\_gcc
At this point I decided to try a different approach.
[rustc\_codegen\_gcc](https://github.com/rust-lang/rustc_codegen_gcc) is a
codegen plugin that uses [libgccjit] for code generation instead of LLVM. The
motivation of the project is promising for my use case:
> The primary goal of this project is to be able to compile Rust code on
> platforms unsupported by LLVM.
I found the instructions for using `rustc_codegen_gcc` a bit difficult to
follow, especially when trying to build a cross-compiler.
I eventually managed to rebuild Retro68 with `libgccjit` enabled and then coax
`rustc_codegen_gcc` to use it. Unsurprisingly that quickly failed as Retro68 is
based on GCC 9.1 and `rustc_codegen_gcc` is building against GCC master and
there were many missing symbols.
Undeterred I noted that there is a WIP GCC 12.2 branch in the Retro68 repo so I
built that and tweaked `rustc_codegen_gcc` to disable the `master` cargo
feature that should in theory allow it to build against a GCC release. This did
in fact allow me to get a bit further but I ran into more issues in the step
that attempts to build `compiler-rt` and `core`. Eventually I gave up on this
route too. I was probably too far off the well tested configuration of x86,
against GCC master.
Future work here is to trying building a `powerpc-ibm-aix` libgccjit from GCC
master and see if that works.
### Wrap Up
[Bastian on Twitter](https://twitter.com/turbolent/status/1617231570573873152)
has had some success compiling Rust to Web Assembly, Web Assembly to C89, C89
to Mac OS 9 binary, which is definitely cool but I would still love to be able
to generate native PPC code directly from `rustc` somehow.
This is where I have parked this project for now. I actually only discovered
the post on building a Mac OS 9 application with Swift while writing this post.
There are perhaps some ideas in there that I could explore further.
[swift]: https://belkadan.com/blog/2020/04/Swift-on-Mac-OS-9/
[BlueSCSI]: https://github.com/erichelgeson/BlueSCSI
[Retro68]: https://github.com/autc04/Retro68
[SheepShaver]: https://sheepshaver.cebix.net/
[Dialog sample]: https://github.com/autc04/Retro68/tree/5f882506013a0a8a4335350197a1b7c91763494e/Samples/Dialog
[Pascal string]: https://en.wikipedia.org/wiki/String_(computer_science)#Length-prefixed
[libgccjit]: https://gcc.gnu.org/onlinedocs/jit/

View file

@ -0,0 +1,221 @@
+++
title = "Debugging a Docker Core Dump"
date = 2023-02-25T10:39:49+10:00
#[extra]
#updated = 2023-01-11T21:11:28+10:00
+++
On my main machine I use an excellent cross-platform tool called [Docuum] that
automatically cleans up unused docker images. This allows me to use Docker
without the need to periodically wonder why I'm out of disk space, run `docker
system prune` and recover half my disk.
I installed Docuum via [the AUR package][aurpkg] (although tweaked to build the
latest Docuum release) and ran it via the bundled systemd service definition.
This worked great for a while but some time back it started failing. Every time
Docuum would try to check for things to clean up I'd see the following in the
system journal:
<!-- more -->
```
Feb 25 10:03:12 ryzen docuum[77751]: [2023-02-25 10:03:12 +10:00 INFO] Performing an initial vacuum on startup…
Feb 25 10:03:12 ryzen kernel: audit: type=1326 audit(1677283392.831:2): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=77763 comm="docker" exe="/usr/bin/docke>
Feb 25 10:03:12 ryzen systemd[1]: Created slice Slice /system/systemd-coredump.
Feb 25 10:03:12 ryzen systemd[1]: Started Process Core Dump (PID 77768/UID 0).
Feb 25 10:03:13 ryzen systemd-coredump[77769]: [🡕] Process 77763 (docker) of user 0 dumped core.
Stack trace of thread 77763:
#0 0x00005568dbcb5c4e n/a (docker + 0x243c4e)
#1 0x00005568dbd35a3b n/a (docker + 0x2c3a3b)
#2 0x00005568dbd3482f n/a (docker + 0x2c282f)
#3 0x00005568dbd6c2ee n/a (docker + 0x2fa2ee)
#4 0x00005568dbcfafa8 n/a (docker + 0x288fa8)
#5 0x00005568dbcfaef1 n/a (docker + 0x288ef1)
#6 0x00005568dbced953 n/a (docker + 0x27b953)
#7 0x00005568dbd1eb41 n/a (docker + 0x2acb41)
ELF object binary architecture: AMD x86-64
Feb 25 10:03:13 ryzen docuum[77751]: [2023-02-25 10:03:13 +10:00 ERROR] Unable to list images.
Feb 25 10:03:13 ryzen docuum[77751]: [2023-02-25 10:03:13 +10:00 INFO] Retrying in 5 seconds…
Feb 25 10:03:13 ryzen systemd[1]: systemd-coredump@0-77768-0.service: Deactivated successfully.
```
This would repeat every 5 seconds. I ignored this for a while but finally decided
to investigate it today. To find the failing command I ran
`coredumpctl list` then identified one of the docker crashes and ran
`coredumpctl info` with its PID:
```
$ coredumpctl info 78255
PID: 78255 (docker)
UID: 0 (root)
GID: 0 (root)
Signal: 31 (SYS)
Timestamp: Sat 2023-02-25 10:03:23 AEST (44min ago)
Command Line: docker image ls --all --no-trunc --format $'{{.ID}}\\t{{.Repository}}\\t{{.Tag}}\\t{{.CreatedAt}}'
Executable: /usr/bin/docker
Control Group: /system.slice/docuum.service
Unit: docuum.service
Slice: system.slice
Boot ID: 0ac9f0dd246548949c3a90a0e7494665
Machine ID: affcb0b7a7d1464385d65464d9be450e
Hostname: ryzen
Storage: /var/lib/systemd/coredump/core.docker.0.0ac9f0dd246548949c3a90a0e7494665.78255.1677283403000000.zst (inaccessible)
Message: Process 78255 (docker) of user 0 dumped core.
```
Strangely I could run the command myself (`Command Line` line) just fine. I
figured I needed to see where in docker it was crashing. I learned how to
access systemd coredumps with gdb and ran: `sudo coredumpctl gdb 78255`. `sudo`
is needed because the core dump belongs to root due the crashing `docker`
process belonging to root. This didn't yield much extra info as debug symbols
were not present for the binary. It did identify why it crashed though:
> Program terminated with signal SIGSYS, Bad system call.
Knowing that docker was implemented in Go and that they make system calls
manually on Linux I wondered if this was some sort of Go bug—although given
the popularity of Docker this did seem unlikely.
To get more info I needed debug symbols. Arch Linux makes these available via
[debuginfod](https://wiki.archlinux.org/title/Debuginfod) and gdb can
automatically download debuginfo files if `DEBUGINFOD_URLS` is set. I reran
`gdb`, telling `sudo` to pass the `DEBUGINFOD_URLS` environment variable
through (I had already set `DEBUGINFOD_URLS=https://debuginfod.archlinux.org/`
in my Zsh config some time ago):
```
sudo --preserve-env=DEBUGINFOD_URLS coredumpctl gdb 78255
```
Now there was a proper backtrace:
```
#0 runtime/internal/syscall.Syscall6 () at runtime/internal/syscall/asm_linux_amd64.s:36
#1 0x00005588e7b93c33 in syscall.RawSyscall6 (num=160, a1=7, a2=94046491589710, a3=7, a4=824634289408, a5=0, a6=0, r1=<optimized out>, r2=<optimized out>,
errno=<optimized out>) at runtime/internal/syscall/syscall_linux.go:38
#2 0x00005588e7c13a3b in syscall.RawSyscall (trap=160, a1=7, a2=94046491589710, a3=7, r1=<optimized out>, r2=<optimized out>, err=<optimized out>)
at syscall/syscall_linux.go:62
#3 0x00005588e7c1282f in syscall.Setrlimit (resource=<optimized out>, rlim=<optimized out>, err=...) at syscall/zsyscall_linux_amd64.go:1326
#4 0x00005588e7c4a2ee in os.init.1 () at os/rlimit.go:30
#5 0x00005588e7bd8fa8 in runtime.doInit (t=0x5588e91a7be0 <os.[inittask]>) at runtime/proc.go:6506
#6 0x00005588e7bd8ef1 in runtime.doInit (t=0x5588e91a9900 <main.[inittask]>) at runtime/proc.go:6483
#7 0x00005588e7bcb953 in runtime.main () at runtime/proc.go:233
#8 0x00005588e7bfcb41 in runtime.goexit () at runtime/asm_amd64.s:1598
#9 0x0000000000000000 in ?? ()
```
So the issue seems to be a call to `setrlimit`. Looking at [the code][gocode]
and searching the Go issue tracker didn't turn up anyone else having this
issue, which pointed at an issue on my system.
I'm honestly not sure what led me to the next step but I decided to take a look
at the Docuum service definition. I was surprised to see that it was more
complicated than most definitions I'm used to seeing:
```ini
[Unit]
Description=LRU eviction of Docker images
Documentation=https://github.com/stepchowfun/docuum
DefaultDependencies=false
After=docker.service docker.socket
Requires=docker.service docker.socket
[Service]
Type=simple
Environment=DOCUUM_THRESHOLD=10GB
EnvironmentFile=-/etc/default/docuum
ExecStart=/usr/bin/docuum --threshold $DOCUUM_THRESHOLD
ProtectSystem=full
PrivateTmp=true
PrivateDevices=true
PrivateNetwork=true
CapabilityBoundingSet=
KeyringMode=private
RestrictNamespaces=~cgroup ipc net mnt pid user uts
RestrictAddressFamilies=AF_UNIX
ReadWritePaths=/var/run/docker.sock
DeviceAllow=
IPAddressDeny=any
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
PrivateMounts=true
PrivateUsers=true
ProtectControlGroups=true
ProtectSystem=strict
ProtectHome=tmpfs
ProtectKernelModules=true
ProtectKernelTunables=true
RestrictSUIDSGID=true
SystemCallArchitectures=native
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources
RestrictRealtime=true
LockPersonality=true
MemoryDenyWriteExecute=true
UMask=0077
ProtectHostname=true
[Install]
WantedBy=multi-user.target
```
Suspiciously it seemed to be doing some sandboxing and filtering system calls
(`SystemCallFilter`). A bit more research pointed me to `systemd-analyze
syscall-filter`, which lists which system calls belong to the predefined system
call sets (`@privileged`, `@resources`, etc.).
`setrlimit` was listed under `@resources`:
```
@resources
# Alter resource settings
ioprio_set
mbind
migrate_pages
move_pages
nice
sched_setaffinity
sched_setattr
sched_setparam
sched_setscheduler
set_mempolicy
setpriority
setrlimit
```
The [systemd docs for `SystemCallFilter`][SystemCallFilter] also mentioned:
> If the first character of the list is "~", the effect is inverted: only the
> listed system calls will result in immediate process termination
> (deny-listing)
So we finally had our culprit: the service definition was denying system calls in
the `@resources` set and at some point `docker` had started making `setrlimit` calls,
which were resulting in termination.
The fix was simple enough: I removed `@resources` from the deny list, rebuilt
the package, and then re-enabled the `docuum` service (I'd previously disabled
it do to the constant crashes). I was pleased to see it start successfully and
begin vacuuming up a few months of Docker image detritus.
### Conclusion
This small debugging session taught me a number of things: I learned how to
find and list core dumps managed by systemd, how to open them in GDB with
symbols present, and that systemd has powerful, fine-grained system call
sandboxing.
I was ultimately able to resolve the issue and get Docuum working again.
I have published my patched version of the AUR package to my personal AUR
repo in case it's useful to anyone else:
<https://github.com/wezm/aur/tree/master/docuum>.
[aurpkg]: https://aur.archlinux.org/packages/docuum
[Docuum]: https://github.com/stepchowfun/docuum
[gocode]: https://github.com/golang/go/blob/203e59ad41bd288e1d92b6f617c2f55e70d3c8e3/src/syscall/zsyscall_linux_amd64.go#L1335
[service]: https://aur.archlinux.org/cgit/aur.git/tree/docuum.service?h=docuum&id=650d2c24fe9df712e8a98dde37f3ee47d3af4e47
[SystemCallFilter]: https://www.freedesktop.org/software/systemd/man/systemd.exec.html#System%20Call%20Filtering

View file

@ -0,0 +1,179 @@
+++
title = "Creating a Podcast From a Mastodon Account With XSLT"
date = 2023-03-01T18:33:33+10:00
[extra]
updated = 2023-03-01T21:54:06+10:00
+++
{% aside(title="Just want the feed?", float="right") %}
Here you go:<br>
[ATPrewind podcast feed](https://files.wezm.net/atprewind.rss)
{% end %}
I recently discovered the [ATPrewind account on Mastodon][ATPrewind]. It's an account
sharing "gems discovered while re-listening to [@atpfm] from the very first
episode. By [@joshua]". ATP is a tech Podcast that's been running for about 10
years. Each post (so far) from ATPrewind includes a short clip from the show in the
form of a little video.
This post describes how I was nerd sniped into creating a podcast from the ATPrewind posts.
<!-- more -->
It all started when [I posted the following][my-post] on Mastodon:
> Ahh this ATP Rewind account is gold https://social.tupo.space/@ATPrewind
>
> Keep up the great work @joshua
[Kashyap replied][Kashyap]:
> Indeed! This should also be a podcast 🙃
This was a great idea and it got me thinking about how to do it with the least
amount of effort.
In this day and age I imagine many programmers would reach for
their favourite programming language and code up something to generate a
podcast feed (real podcasts are just RSS). Perhaps using the Mastodon API or
similar and then work out a way to host their program.
With [my recent experience with Deno Deploy][deno-deploy] fresh in my mind I
considered using it. However I opted for a decidedly late 90s solution: [XSLT].
According to Wikipedia "XSLT is a language originally designed for transforming
XML documents into other XML documents". The 'originally' refers to fact that
you can now generate any text with it, not just XML.
Since it was created in the era of "XML ALL THE THINGS", XSL templates are
themselves XML documents. It also makes extensive use of [XPath] expressions to
select nodes and extract their content.
### Creating a Podcast Feed
Every Mastodon account has an RSS feed so I created an XSL template to process
the ATPrewind RSS feed and add the missing elements required to turn it into a
valid podcast feed. With some help from [this Dr. Drang][drdrang] post this is
what I came up with:
```xml
<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:media="http://search.yahoo.com/mrss/">
<xsl:output method="xml" encoding="utf-8" indent="yes"/>
<!-- First, get everything. -->
<xsl:template match="node() | @*">
<xsl:copy>
<xsl:apply-templates select="node() | @*"/>
</xsl:copy>
</xsl:template>
<!-- Update items to include title and enclosure elements. -->
<xsl:template match="/rss/channel/item">
<item>
<xsl:apply-templates select="node()" />
<title>Post on <xsl:value-of select="pubDate"/></title>
<enclosure url="{media:content/@url}" length="{media:content/@fileSize}" type="audio/mp4; codecs=&quot;mp4a.40.2&quot;" />
</item>
</xsl:template>
</xsl:stylesheet>
```
XML noise aside this document should be fairly self explanatory. It copies all
nodes from the source RSS feed, then processes each `item` element. To each one
it adds a `title` element derived from the text "Post on" and the publication
date of the post, and an `enclosure` element derived from the `media:content`
element.
I lie a bit by saying that the enclosure MIME type is `audio/mp4;
codecs="mp4a.40.2"`. I.e. AAC-LC in MP4 container for the video in each post.
Running `curl -L https://social.tupo.space/@ATPrewind.rss | xsltproc
podcast.xsl` produces the podcast feed.
It took a few tries to get it to work but eventually I was able to convince
[Overcast] that it was a real podcast. One trick that I'm exploiting here is
that the MP4 container for video and audio is the same, the audio only version
is just lacking the video stream. I figured that podcast players might still be
able to play the video and at least for Overcast it works:
{{ figure(image="posts/2023/xslt-podcast/overcast-screenshot.png", link="posts/2023/xslt-podcast/overcast-screenshot.png", alt="Screenshot of Overcast showing the podcast 'episodes'.", caption="Screenshot of Overcast showing the 'episodes'.", width=393) }}
### Deployment
To deploy this contraption I created a Docker image with `curl` and `libxslt`
installed:
```dockerfile
FROM wezm-alpine:3.17.2
# UID needs to match owner of /home/rss/feeds volume
ARG PUID=1000
ARG PGID=1000
ARG USER=rss
RUN addgroup -g ${PGID} ${USER} && \
adduser -D -u ${PUID} -G ${USER} -h /home/${USER} -D ${USER}
RUN apk --update add curl libxslt
COPY ./entrypoint.sh /home/${USER}/entrypoint.sh
COPY ./podcast.xsl /home/${USER}/podcast.xsl
WORKDIR /home/${USER}
USER ${USER}
VOLUME ["/home/rss/feeds"]
ENTRYPOINT ["./entrypoint.sh"]
```
I made a small script as the entrypoint to the container:
```sh
#!/bin/sh
set -e
trap 'exit' TERM INT
while true; do
curl -L https://social.tupo.space/@ATPrewind.rss | xsltproc podcast.xsl - > /home/rss/feeds/atprewind.rss
sleep 3600 # 1 hour
done
```
It regenerates the podcast feed each hour. I bind mount the directory for
files.wezm.net into the container in my Docker Compose config, which takes
advantage of the fact that nginx is already serving that directory.
Some may argue that the Docker part of this is not in the spirit of, "least
amount of effort", but I already have all this set up on my server so adding
one more container is very little effort. I've written previously about [my
Alpine Linux server][server] if you'd like to read more.
Once I pushed the Docker image the podcast was live. The URL is:<br>
<https://files.wezm.net/atprewind.rss> if you'd like to subscribe.
### Future Work
I whipped all this up before work today and made an assumption that might not
always hold: there is a single video media attachment on each post. So far
that's true but I should update the XSL template to only try to generate an
`enclosure` element if the attachment is present. It would also be a good idea
to filter for audio and video only in case a post appears with images.
For now I shall eagerly look forward to the next post appearing in Overcast.
[@atpfm]: https://mastodon.social/@atpfm
[@joshua]: https://social.tupo.space/@joshua
[ATPrewind]: https://social.tupo.space/@ATPrewind
[Overcast]: https://overcast.fm/
[XPath]: https://www.w3.org/TR/xpath-31/
[XSLT]: https://www.w3.org/TR/xslt-30/
[deno-deploy]: https://www.youtube.com/watch?v=d-tsfUVg4II
[drdrang]: https://leancrew.com/all-this/2022/08/filtering-my-rss-reading/
[server]: https://www.wezm.net/technical/2019/02/alpine-linux-docker-infrastructure/
[my-post]: https://mastodon.decentralised.social/@wezm/109940341596949214
[Kashyap]: https://mastodon.social/@kgrz/109942702497796855

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

View file

@ -183,6 +183,9 @@ figure {
.figure-border img {
border: 1px solid #AAA;
}
.figure-pixelated img {
image-rendering: pixelated;
}
figcaption {
font-size: 16px;
font-weight: 500;
@ -360,6 +363,20 @@ ul.projects {
margin-bottom: 3em;
}
.no-overlay {
position: relative;
}
.no-overlay::after {
content: '🚫';
position: absolute;
top: 30px;
left: 0;
right: 0;
bottom: 0;
font-size: 96pt;
z-index: 1;
}
.socials {
margin-top: 2em;
@ -373,6 +390,9 @@ ul.projects {
text-decoration: none;
}
}
.mastodon-embed {
display: block;
}
@media screen and (max-width: 800px) {
.tagline {

View file

@ -17,7 +17,7 @@
<div class="flex">
<section class="posts-section">
<h2>
Recent Posts&#8194;<a href="https://twitter.com/wezm?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false">Follow @wezm</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
Recent Posts
</h2>
{% set section = get_section(path="posts/_index.md") %}

View file

@ -18,7 +18,7 @@
<a href="{{ config.base_url }}/rss.xml">RSS</a>
<a href="mailto:{{ config.extra.email }}">Email</a>
<a href="https://twitter.com/wezm">Twitter</a>
<a href="https://decentralised.social/wezm">Fediverse</a>
<a href="https://mastodon.decentralised.social/@wezm" rel="me">Fediverse</a>
<a href="https://github.com/wezm">GitHub</a>
<a href="https://github.com/sponsors/wezm">Support My Work</a>
</div>

View file

@ -61,7 +61,11 @@
</svg>
Stay in touch!
</h3>
<p> Follow me on <a href="https://twitter.com/wezm">Twitter</a> or the <a href="https://decentralised.social/wezm">Fediverse</a>, <a href="{{ config.base_url }}/rss.xml">subscribe to the feed</a>, or <a href="mailto:wes@wezm.net">send me an email</a>.
<p>
Follow me on the <a href="https://mastodon.decentralised.social/@wezm">Fediverse</a>
(or <a href="https://twitter.com/wezm">Twitter</a>),
<a href="{{ config.base_url }}/rss.xml">subscribe to the feed</a>,
or <a href="mailto:wes@wezm.net">send me an email</a>.
</p>
</div>
</article>

View file

@ -1,8 +1,11 @@
{% set figure_class = ["text-center"] %}
{% if border %}
<figure class="text-center figure-border">
{% else %}
<figure class="text-center">
{% set figure_class = figure_class | concat(with="figure-border") %}
{% endif %}
{% if pixelated %}
{% set figure_class = figure_class | concat(with="figure-pixelated") %}
{% endif %}
<figure class="{{ figure_class | join(sep=" ") }}">
<a href="{{ config.base_url }}/{{ link }}">
{% if resize_width %}
{% set image = resize_image(path=image, width=resize_width, op="fit_width", quality=quality | default(value=75)) %}

View file

@ -0,0 +1,26 @@
{% if border %}
<figure class="text-center figure-border no-overlay">
{% else %}
<figure class="text-center no-overlay">
{% endif %}
{% if resize_width %}
{% set image = resize_image(path=image, width=resize_width, op="fit_width", quality=quality | default(value=75)) %}
{% if width %}
<img src="{{ image.url }}" width="{{ width }}" alt="{{ alt }}" />
{% else %}
<img src="{{ image.url }}" alt="{{ alt }}" />
{% endif %}
{% elif resize_height %}
{% set image = resize_image(path=image, height=resize_height, op="fit_height", quality=quality | default(value=75)) %}
{% if height %}
<img src="{{ image.url }}" style="max-height: {{ height }}px" alt="{{ alt }}" />
{% else %}
<img src="{{ image.url }}" alt="{{ alt }}" />
{% endif %}
{% elif width %}
<img src="{{ config.base_url }}/{{ image }}" width="{{ width }}" alt="{{ alt }}" />
{% else %}
<img src="{{ config.base_url }}/{{ image }}" alt="{{ alt }}" />
{% endif %}
<figcaption>{{ caption }}</figcaption>
</figure>

View file

@ -1,4 +1,13 @@
{% if poster %}
{% else %}
{% set poster = "jpg" %}
{% endif %}
{% if preload %}
{% else %}
{% set preload = "none" %}
{% endif %}
<figure class="text-center">
<video controls preload="none" src="{{ config.base_url }}/{{ video }}" poster="{{ config.base_url }}/{{ video }}.jpg" style="max-height: {{ height }}px"></video>
<video controls preload="{{ preload }}" src="{{ config.base_url }}/{{ video }}" poster="{{ config.base_url }}/{{ video }}.{{ poster }}" style="max-height: {{ height }}px" aria-label="{{ alt }}"></video>
<figcaption>{{ caption }}</figcaption>
</figure>