From 89dce0f0821c32dce669c04a0c56f06d82555e63 Mon Sep 17 00:00:00 2001 From: Andrew Kesterson Date: Sun, 11 Jan 2026 03:04:02 +0000 Subject: [PATCH] Deploying upstream@d2475acaac55013d628cdd1d183003faf0b60267: Publish libakerror article, update plan --- 2022/03/17/The-Righteous-Mind/index.html | 63 +- 2022/05/02/The-Hero-Code/index.html | 63 +- 2022/05/04/American-Prison/index.html | 63 +- 2022/05/19/Jesus-Outside-the-Lines/index.html | 63 +- 2022/10/15/Live-Not-By-Lies/index.html | 63 +- .../Christianity-First-3000-Years/index.html | 63 +- 2023/09/24/How-Should-We-Then-Live/index.html | 63 +- .../index.html | 63 +- .../10/10/The-Cost-of-Discipleship/index.html | 63 +- 2023/11/05/Copper-Sun/index.html | 63 +- .../index.html | 63 +- .../29/Simon-Sinek-Start-with-Why/index.html | 63 +- 2024/02/09/expectations/index.html | 63 +- 2024/03/03/new-manager-pt1/index.html | 63 +- 2024/03/05/new-manager-pt2/index.html | 63 +- 2024/03/08/new-manager-pt3/index.html | 63 +- 2025/01/04/Socrates-got-a-raw-deal/index.html | 67 +- 2026/01/11/News-2026-Week-1/index.html | 842 ++++++++++ 2026/01/11/Single-Pane-of-Glass/index.html | 786 +++++++++ 2026/01/11/Smart-LEGO/index.html | 782 +++++++++ 2026/01/11/libakerror/index.html | 1440 +++++++++++++++++ about/index.html | 77 +- archives/2022/03/index.html | 73 +- archives/2022/05/index.html | 73 +- archives/2022/10/index.html | 73 +- archives/2022/index.html | 73 +- archives/2023/09/index.html | 73 +- archives/2023/10/index.html | 73 +- archives/2023/11/index.html | 73 +- archives/2023/12/index.html | 73 +- archives/2023/index.html | 73 +- archives/2024/01/index.html | 73 +- archives/2024/02/index.html | 73 +- archives/2024/03/index.html | 73 +- archives/2024/index.html | 73 +- archives/2025/01/index.html | 73 +- archives/2025/index.html | 73 +- archives/2026/01/index.html | 129 +- archives/2026/index.html | 129 +- archives/index.html | 148 +- archives/page/2/index.html | 117 +- archives/page/3/index.html | 949 +++++++++++ atom.xml | 130 +- categories/Books/index.html | 73 +- categories/Books/page/2/index.html | 73 +- categories/Current-Events/index.html | 77 +- categories/Faith/index.html | 73 +- categories/History/index.html | 73 +- categories/Leadership/index.html | 73 +- categories/Liberal-Education/index.html | 73 +- categories/Outdoors/index.html | 73 +- categories/Philosophy/index.html | 73 +- categories/Technology/index.html | 113 +- categories/index.html | 86 +- categories/technology/index.html | 932 +++++++++++ consulting/index.html | 73 +- contact/index.html | 73 +- images/butwhy.jpeg | Bin 0 -> 5432 bytes images/disciprine.jpg | Bin 0 -> 103915 bytes images/waiting-bear.webp | Bin 0 -> 32868 bytes index.html | 348 ++-- links/index.html | 73 +- now/index.html | 84 +- page/2/index.html | 189 ++- page/3/index.html | 975 +++++++++++ tags/index.html | 73 +- 66 files changed, 10347 insertions(+), 1032 deletions(-) create mode 100644 2026/01/11/News-2026-Week-1/index.html create mode 100644 2026/01/11/Single-Pane-of-Glass/index.html create mode 100644 2026/01/11/Smart-LEGO/index.html create mode 100644 2026/01/11/libakerror/index.html create mode 100644 archives/page/3/index.html create mode 100644 categories/technology/index.html create mode 100644 images/butwhy.jpeg create mode 100644 images/disciprine.jpg create mode 100644 images/waiting-bear.webp create mode 100644 page/3/index.html diff --git a/2022/03/17/The-Righteous-Mind/index.html b/2022/03/17/The-Righteous-Mind/index.html index dece0e7..d2175bd 100644 --- a/2022/03/17/The-Righteous-Mind/index.html +++ b/2022/03/17/The-Righteous-Mind/index.html @@ -45,7 +45,7 @@ content="2022-03-17"> + content="2026-01-11"> @@ -97,6 +97,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ +
+ +
+
+

+ News - 2026 - Week 1 +

+
+ + + +
+

Here are my thoughts on the news this week. It’s worth what you paid for it.

+

AOSP twice yearly releases? Calm down, F/OSS advocates.

AndroidAuthority reports that Google will only be releasing the source code for AOSP, Android Open Source Platform, twice a year.

+
+

“Effective in 2026, to align with our trunk-stable development model and ensure platform stability for the ecosystem, we will publish source code to AOSP in Q2 and Q4. For building and contributing to AOSP, we recommend utilizing android-latest-release instead of aosp-main. The aosp-latest-release manifest branch will always reference the most recent release pushed to AOSP. For more information, see Changes to AOSP.”

+
+

Reactions on slashdot and other places are typically upset. “Android isn’t open source”, “Time to find an alternative”, etc. While I don’t necessarily disagree with some of these commenters, I think they’re misunderstanding (or choosing to distort) what’s happening here. They feel like Google is doing the Evil thing again (which they are frequently guilty of) and withholding source code from certain releases that fall between the twice yearly releases. They see this as a stepping stone towards Android becoming closed source. I’m inclined to read the announcement at face value, and believe what it says: They’re transitioning to twice yearly releases, and asking everyone to start doing the same, and are aligning their source releases with that process. I don’t really see the problem with that.

+

Now whether or not Android is truly open source is an entirely different (and nuanced) question, and some of us do feel a certain kind of way about that. But this announcement is a bit of a nothing burger.

+

Everything old is new again: the return of the keyboard computer

Remember the home computers of the 1980s? Remember how their form factor tended to be that of a big bulky keyboard you plugged in to a monitor (or, if you were spendy, a special monitor)? Commodore VIC-20, 64 and 128, ZX Spectrum and Spectrum +2, Amiga 500, Amstad CPC 464, BBC Micro, etc. This form factor essentially defined personal computers in the late 1970s and early 1980s.

+

Now, we see this form factor as a quaint reminder of a simpler time, and there is even some nostalgia around it. The Commodore 64 has always had a really strong cult following, and with the recent acquisition of Commodore by (essentially) the fan community itself, there is even a new Commodore computer being released for the first time in decades, the Commodore Ultimate - and you can guess the form factor on this bad boy. Thus far, I have resisted the completely nonsensical urge to buy one of these things and put it on my desk. We’ll see how long the $350 price tag helps me keep such a level headed position.

+

I like to say “everything old is new again”, because we tend to live in a circle of trends, and things keep coming back around. Apparently it is the time of the keyboard computer once again, because - believe it or not! - there is a brand new keyboard form factor PC for the enterprise. Yes, you read that right, HP’s enterprise PC lineup now includes a keyboard PC, the HP EliteBoard G1a.

+
HP EliteBoard G1a
+ +

The Register ran a story about this little curiosity recently, and they note that it is intended to fill a very specific niche: for enterprises which use “hot desks” or “hotel cubes” where an employee might pop in and work for a day, but they may not use the same hot desk or hotel cube every day, so leaving a permanent workstation doesn’t make sense. I guess that HP also assumes A) these workers do not work when away from their desk (a blessed expectation in this modern age), or B) these enterprises are going to choose not to simply issue laptops to these workers.

+

Personally, it’s charming, and I love to see it. But as a former enterprise IT manager, it’s a baffling choice. Issuing laptops seems like the smarter thing to do as an enterprise - your IT team is probably already issuing them, already storing them, already has a support contract for keeping them running, already has spare parts, your workforce is already using them, and you don’t have to equip desks with separate monitors and mice. Mice are going to get lost. Monitors are going to get moved away from the hotel cube/hot desk when someone’s “give me a second monitor” IT ticket takes too long to fulfill. And the workers who are issued these things are absolutely not going to be inclined to open them and work from home, let alone a coffee shop. You’d better have very strict expectations that these workers only do work in the hot desk, at the office.

+

And if that’s your expectation …. why not just put a frickin desktop there? I don’t get it.

+

There is nothing new under the sun, AI just does it faster

This one probably should’ve gotten its own post.

+

The New Stack ran a story recently about how traditional methods of monitoring and reacting to events in your IT infrastructure are out of date in the age of AI. Specifically they mention dashboards, but that’s not what they’re actually talking about. They’re talking about having humans in the incident response loop.

+

In the article, they make the point that the traditional incident response workflow is slow. Consider:

+
    +
  1. An event occurs somewhere in your IT infrastructure or application
  2. +
  3. Some sensor detects this event
  4. +
  5. Some sensor sends the event to a centralized tool for monitoring such events
  6. +
  7. The centralized tool slaps a timestamp on the event and aggregates it with everything else
  8. +
  9. Something notices that event, and decides it needs to alert a human. Incident Response procedures for your organization begins here.
  10. +
  11. A human receives the alert, reads it with their eyeballs, and process it with their smartmeat.
  12. +
  13. That human consults with other humans, and they combine their smartmeat about what to do about the event
  14. +
  15. Some human (maybe the same human) uses their fingermeat to twiddle some bits, reacting to the event, and hopefully resolving it. If not, GOTO 3.
  16. +
+

The argument in the article is that AI tools make your dashboards obsolete because steps 6, 7, and 8 of the process can now be automated by AI, and executed faster than a human team could ever hope to do so. And, yes, that’s true, automation can react faster than humans. But that’s no secret to those of us who have actually been in this industry for more than a few days, and we don’t need AI to do it.

+

Anyone hear of “event driven architecture”? This is a very old idea that predates AI. The idea is that you have an architecture where decoupled systems don’t really know about each other’s guts, and they just respond to events occurring, with those events coming over standardized interfaces. It’s a good idea. It’s also been coupled with incident response by DevOps folks for nearly as long to automate systems in response to bad things happening.

+

The AI driven workflows that the New Stack article refers to are really no different than what your DevOps/DevSecOps/NoOps/SRE team has been doing alrady. The difference is that these flows:

+
    +
  1. Probably use some amount of automatic introspection (AI tools inspecting the output of a set of components to determine how to parse that output, and classify it into normal and error conditions)
  2. +
  3. Probably use some amount of automatic code generation (AI tools generating code that should work to reset a given system to a normal state when an error condition is detected)
  4. +
  5. Probably replace humans in steps 6, 7, 8 of the previous flow with multiple AI agents that can reach consensus (according to business rules codified by humans) about when it is time to execute the code from item #2
  6. +
+

Which I think is just hilarious, because it flies in the face of what we got told by the enterprise for so long. Your human automation teams have been capable of delivering this functionality to you all along - in fact we’ve been BEGGING you to let us write the software and turn it on, and you have continuously refused. And the reasoning is always the same. “You want to automatically change the production system in response to a change? We aren’t comfortable with that. A human must be in charge of deciding to throw that lever.” And even when the business may have been comfortable with allowing automation to do it, frequently change controls were in place that prevented such activities, often in response to regulatory or legal requirements that prevented the automation from doing the needful.

+

So how is this suddenly okay now that it’s AI doing it? AI is less deterministic, less idempotent, less comprehensible, and less predictable. And yet we expect enterprises to allow AI to have the keys to prod when the deterministic, idempotent, comprehensible, utterly predictable code from their automation teams, has been refused for so long?

+

Whatever. Do what you want, man. But when your agents hallucinate something and fire when they shouldn’t, or don’t fire when they should, your SRE team is gonna have a bash script that can fix your problems. This problem, and the solution, is not new, you’re just finding a new, faster, and arguably worse way to handle it.

+

Get off my lawn.

+

Sociopaths and hardcore gamers in civil service

Slashdot reports on a Chinese paper thusly

+
+

A new working paper from researchers at the University of Hong Kong has found that Chinese graduate students who plagiarized more heavily in their master’s theses were significantly more likely to pursue careers in the civil service and to climb the ranks faster once inside. John Liu and co-authors analyzed 6 million dissertations from CNKI, a Chinese academic repository, and cross-referenced them against public records of civil-service exam-takers to identify 120,000 civil servants and their academic work. Those who entered the public sector had plagiarism scores 15.6% above average. Customs and tax officials fared worst – their scores ran 25% and 26% higher than private-sector peers respectively. Within the civil service, those who plagiarized more were promoted 9% faster during the first five years of their careers. The researchers validated their plagiarism metric through an experiment involving 443 job applicants who were asked to roll dice for rewards without monitoring. Those who had plagiarized more also reported improbably high rolls.

+
+

This shouldn’t really surprise anyone who has spent any time working in civil service - whether in government, police, fire, or EMS. For whatever reason, these positions tend to attract a certain kind of people who tend to either be the absolute best kind of person, or the absolute worst kind of person. (And sometimes they are both, unfortunately.) We also know that civil service somehow tends to wind up promoting and rewarding nepotists and sociopaths, because they’ve learned how to play the game, and to rig it. What’s worse is that, often times, they are doing it from a place of positive moral justification - “the ends justify the means”. And history has shown us, over and over, why that’s a very dangerous line to walk, and we shouldn’t tolerate it. But we do, because ultimately, we need results from the people in these positions. And we are often willing to either look the other way, or simply not look too hard into the details, when someone is getting results.

+

Sociopaths are great at getting results. They also scare the hell out of us. But sociopaths are great at understanding the rules, and maximizing their ability to operate in those rules for their own benefit. They learn what people expect to see, and they use their chameleon superpowers to show that on the outside, while still getting what they want through other less visible means. Cheating is fine, as long as the social expectations are met on the outside. Dirty deeds are fine, as long as the socially acceptable outcome is achieved, and nobody can really see how that sausage is getting made. The ends justify the means.

+

Back in the early 2000s, The Escapist was still being published as a zine. (Personally I miss it.) It was released as a PDF with pages you would turn and everything (anyone remember PDF readers that animated pages turning and made the paper shuffling noise? I do, and I don’t miss that at all.) One article from Volume 1 Issue 22 struck me as being so damned good that I’ve saved a copy of that on my NAS ever since, and every once in a while, something happens that makes me bring it up again.

+

Escapist Volume 1 Issue 22 is still available on the Internet Archive as of the time of this writing. Just in case, I also have a local copy of it attached to this article. In this issue, John Tynes wrote an article called “The Contrarian: Why We Fight”. In the article, he explains why hardcore gamers so often tended to gravitate towards games about war and conquest with violent themes. It’s a really great article that says more about human psychology than maybe the author meant to say, and I recommend reading it. But when thinking of sociopaths in civil service, I’m remindeded of this part of the article:

+
+

I believe humans have a deep longing for authority, to possess it or to obey it. It is tempered by our empathy, our ability to view another’s situation and project it onto ourselves. But our games know nothing of empathy. We optimize our play to reach the solution in the most direct way possible. When you watch a video of someone completing the entirety of Half-Life in 45 minutes, you have to think: That guy could make the trains run on time. There is no pause for conversation or exploration. There is merely the fanatical implementation of an optimal result.

+

A final solution.

+

Somebody has to save the world. And that means somebody has to rule it. We gamers have had the training. We’ve learned the mindset. We know the score. We are efficient, deadly, methodical. If only we were in charge – then, oh then, we could show the world how much we care about it. We could wrap our arms around all that suffering and whisper over our speed runs, our fervent smashing of crates, the countless times we’ve saved them all already. And if any of them talked back or questioned our wisdom we could show them exactly what we’ve learned.

+

Press the button.

+
+

Goodbye APIs, hello AI interfaces

The New Stack ran a story recently about how data management is going to change in 2026, for a bunch of reasons. In the article, there was an interesting claim made about how systems will interact in the future.

+
+

Such collaboration will range the gamut of data-centric resources, from federation infrastructure to traditional databases supporting different modalities, vector embeddings, and protocols such as MCP, Agent-User Interaction (AG-UI), and Agent to UI (A2UI). Users will no longer look for a single hyperscaler or vendor offering these things, but rather “the ability to plug and play,” commented Yugabyte CEO Karthik Ranganathan. “Just saying one thing will do it all is like saying we know the answer to a question even before you’ve asked it.”

+
+

Imagine the horror of your only programmatic interface to a given system being, not a well defined API, with defined and structured inputs and outputs on a contract with a schema, but an agentic interface that you talk to, in awful natural language, and hope it does the right thing.

+

Sweet Jesus. And developers think they have it bad now.

+

Quantum computing, complexity, and comprehension

I am not a quantum computing expert, but a recent article about wires and qubits got me thinking.

+
+

“If you think about how the semiconducting industry evolved, you’ve got chips in your phone and your laptop with billions of transistors. There’s absolutely not billions of control lines that go down to the chip,”

+
+

It’s been said that you haven’t achieved mastery of something until you can explain it to a non-technical person outside of your field. It’s also been said that genius is the ability to reduce the complex to the simple.

+

The shrinking number of control lines on a chip represent an increasing understanding of the utility of the chip, thereby allowing us to present a more narrow interface to the device. In the early days there was a 1:1 number of lines on a CPU for memory address, data, etc. An 8 bit machine had 8 address lines and 8 data lines. Over time we’ve been able to improve performance and simplify the API on these components to reduce the number of directly exposed items. Even in the 80s, computers did not have instructions for referencing individual bits in memory, they dealt only in bytes (logical operations not excepted). As our understanding of the machine and its use cases proceeded, the interfaces got smaller and simpler.

+

On a long enough timeline Quantum computing will, assuming the technology has sufficient utility to drive continued development and refinement, reach the point where binary von neumann general purpose computers reached in the 80s. When that happens, I wonder what the languages and tooling to communicate with them is going to look like, as the interfaces (once standardized) will undoubtedly look nothing like what we’re used to.

+

Just as one example, the idea of a “General Purpose Computer” is so intrinsically tied to our idea of a “Computer” that Wikipedia has no separate article for a General Purpose Computer. Quantum Computing is going to be a whole new frontier.

+ +
+
+ +
+
+ + + + +
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2026/01/11/Single-Pane-of-Glass/index.html b/2026/01/11/Single-Pane-of-Glass/index.html new file mode 100644 index 0000000..92cfff9 --- /dev/null +++ b/2026/01/11/Single-Pane-of-Glass/index.html @@ -0,0 +1,786 @@ + + + + + + + + + + Single Pane of Glass + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ +
+ +
+
+

+ Single Pane of Glass +

+
+ + + +
+

On a given day, I have to manage:

+
    +
  • Three telephones that can ring
  • +
  • Two telephones that can manage text messages
  • +
  • Five separate chat programs
  • +
  • (At least) Three separate computers that I actually use (not even counting servers)
  • +
  • (At least) Three separate email accounts
  • +
  • (At least) Five separate task trackers
  • +
  • (At least) Three separate note apps (or physical notebooks with notes in them)
  • +
+

How do I know what to do next? Let me check my task tracker. Oh wait… which one should I check first? Okay, I’ll check that one. But… what if I’m forgetting something important in the other four task trackers? I should go check.

+
Actual footage of me finding my next task
+ +

Hey this is an interesting news story that I’d like to forward to my wife. Let’s send it over to her quickly. Wait.. what’s the best way to send it? I found it on my laptop. She doesn’t use discord, or IRC, or steam chat, or mattermost. Her phone is an iphone, my PC is linux, so I can’t forward it from my laptop using iMessage. I could use google messenger, paired with my second android phone, to send it via google messages, but then she’s getting a text from my work number, which is confusing. I could email it, but that will take forever. I guess I’ll just send it over facebook messenger, and let Zuck see the meme.

+
Which mail carrier did that go through again?
+ +

Oh hey here’s a good idea I should write down for later. later arrives … Now where did I put that idea? I need to work on it some more. Did I put it in my Synology notes? In the notebook I keep in my breast pocket? In onenote? In my emacs org-mode that I am kinda half-assed using? In that random text file on my laptop that I forgot to sync somewhere else so I can reach it when I’m here at the coffee shop on my Android tablet? Well …. I guess that idea wasn’t that important anyway. I’m sure I’ll find it later.

+
O Note, Where Art Thou?
+ +

The fragmentation of the digital landscape, particularly my digital landscape, is absolutely out of control. My kingdom for a single tool for each of my central needs:

+
    +
  • Communication
  • +
  • Planning
  • +
  • Note taking
  • +
+

I am officially on a quest to identify the proper single pane of glass for all of these items and to mercilessly eliminate the others from my life.

+
I'm a grown ass man
+ + +
+
+ +
+
+ + +
+
+ + TOC +
+
+
+ +
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2026/01/11/Smart-LEGO/index.html b/2026/01/11/Smart-LEGO/index.html new file mode 100644 index 0000000..5196feb --- /dev/null +++ b/2026/01/11/Smart-LEGO/index.html @@ -0,0 +1,782 @@ + + + + + + + + + + Smart LEGO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ +
+ +
+
+

+ Smart LEGO +

+
+ + + +
+

A Slashdot user reports that

+
+

The Lego Group today unveiled Smart Bricks, a tiny computer that fits entirely inside a classic 2x4 brick and which the company is calling the most significant evolution in its building system since the introduction of the minifigure in 1978. The Smart Brick contains a custom ASIC smaller than a single Lego stud and includes light and sound output, light sensors, inertial sensors for detecting movement and tilt, and a microphone that functions as a virtual button rather than a recording device. The bricks detect NFC-equipped smart tags embedded in new tiles and minifigures, and they form a Bluetooth mesh network to sense each other’s position and orientation. They charge wirelessly on a pad that can handle multiple bricks simultaneously.

+
+

So we have physical artifacts the size of a lego brick that can:

+
    +
  • detect movement and tilt
  • +
  • sense light
  • +
  • output light and sound
  • +
  • a microphone that acts like a button
  • +
+

I’m really curious to get my hands on some of these and see what you can do with them. I could easily see them being constructed into physical artifacts that serve as some kind of interactive amusement device - at the very least, you would be able to make a Simon Says or BopIt. I’m also curious how (if at all) they could interact with Mindstorms. I’m sure someone out there will figure out a way to make them interact with smoething they weren’t meant to.

+

On a long enough timeline, we’ll have an entire computer constructed entirely of LEGO Smart Bricks that runs DOOM, like a real life minecraft Redstone contraption.

+

What a time to be alive.

+ +
+
+ +
+
+ + +
+
+ + TOC +
+ +
+ +
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2026/01/11/libakerror/index.html b/2026/01/11/libakerror/index.html new file mode 100644 index 0000000..ce55e5b --- /dev/null +++ b/2026/01/11/libakerror/index.html @@ -0,0 +1,1440 @@ + + + + + + + + + + libakerror + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ +
+ +
+
+

+ libakerror +

+
+ + + +
+

I’d like to present a C library I wrote for exception style error handling in C code, why I wrote it, and why I prefer this style of error handling when writing in the C language.

+

libakerror: A safe exception handling library for C

+

To be clear, when I say “exception style error handling”, I am referring to the practice of one section of code creating an exception object, which is an object containing a rich error context, and then the language raising (or throwing) that exception up through the call stack, allowing all code paths in the call stack the opportunity to respond to that exception, and if the exception reaches all the way back up to the program’s top level without being handled, it will terminate the program.

+

Before completing this article, I strongly recommend that you go and read the libakerror README, at least the “Library Architecture” section. This article will not spend a lot of time explaining what libakerror does, or even how it does it, but rather why I prefer this style of error handling. So if you don’t go through the README, you may be a bit lost for the rest of the article.

+
Ill wait
+ +

Note that the question of “why do you like writing software in C so much” won’t be answered here. That’s a separate blog post TBD. This post also assumes you are already familiar with the C programming language, although if you know any programming languages at all, you can probably get through it. If you don’t know C, I strongly recommend these two books:

+ +

Getting roasted in IRC is motivational

I recently decided to do a fun thing: I took this software library on LiberaChat to #c and asked them roast it. Fun times!

+

It did result in some improvements to the library (what doesn’t kill us makes us stronger), but mostly what it resulted in was illustrating a huge philosophical divide between me and some other C programmers. Essentially it can be summed up as

+
<#c user> this is solving a problem that doesn't exist
+
+<#c user> and you're a retard
+ +

Touché, sir.

+

To be fair, the jury is still (and will probably remain) out on the extent and nature of my particular retardation. I’m open to the possibility. But let’s address the more serious question.

+

There is no problem. C is not broken.

Let me be clear: There is nothing wrong with the C language. I hold that C is basically perfect (fight me). This is especially true about its error handling mechanisms: C’s error handling is not broken, and this library does not claim to fix it.

+

Well if it’s perfect then why write the library?

+

Because we can do better

Even though it’s not broken, I think it provides us the tools to do better. Consider this code:

+
#include <stdio.h>
+
+int main(void)
+{
+	FILE *fp = fopen("somefile.txt", "w");
+	if ( fp == NULL ) {
+		return 1;
+	}
+	return fprintf(fp, "Some message\n");
+}
+ +

What is the return value of this function? … In the simplest case, it is equal to strlen("Some message\n"). But what about when it fails? man describes the failure behavior of fprintf thusly:

+
If an output error is encountered, a negative value is returned.
+ +

So …. there are conceivably times when, theoretically, we could get a valid file pointer, pass it off to fprintf(), and get a non-zero return code. We can capture that error code, we can react to it, but we are left to wonder about the meaning of that return code.

+
But why?
+ +

If we look at the libc source code for fprintf, we see that there are several possible failure modes:

+
    +
  • arguments malformed (NULL file pointer or message)
  • +
  • streams are incorrectly oriented (character or wide character)
  • +
  • errors on IO locking and/or unlocking
  • +
  • errors flushing buffers to disk (such as no space remaining on the output device)
  • +
+

So, yeah, we know that there was a failure. Something bad happened. But what? And why?

+

Check errno, stupid

In the case of fprintf and other well behaved libc functions, the C standard requires that they set errno. This is a global variable that contains an integer value corresponding to an error code. You can see which values of errno are valid on your system at the command line:

+
$ errno --list
+EPERM 1 Operation not permitted
+ENOENT 2 No such file or directory
+ESRCH 3 No such process
+EINTR 4 Interrupted system call
+EIO 5 Input/output error
+ENXIO 6 No such device or address
+E2BIG 7 Argument list too long
+ENOEXEC 8 Exec format error
+EBADF 9 Bad file descriptor
+ECHILD 10 No child processes
+ENAMETOOLONG 36 File name too long
+.....
+ +

So in the simple case above, we can get the exact reason from errno. Then we can react with a much more specific course of action, even if the correct action is, “abort the program”. Consider:

+
int rc = fprintf(fp, "Some message\n");
+if ( rc < 0 ) {
+	printf("Failed to write");
+	switch ( errno ) {
+	case ESTALE:
+        // Stale file handle
+		// do something about it here and continue (somehow)
+		break;
+	case ENOSPC:
+	    // No space on the device
+		// do something about it here and continue (somehow)
+		break;
+	default:
+		// Some other undefined error, all we know how to do is abort
+		return 1;
+	}
+}
+ +

This isn’t bad, but it is a little bit inconvenient.

+
    +
  • You have to know that the library in question returns one value, but that value is not the actual error, the actual error is somewhere else
  • +
  • You have to fetch the error from somewhere else and check it explicitly
  • +
  • You have to explicitly handle every possible error case and ensure you have a default error handler.
  • +
+

The libakerror way

How does that same fprintf checking code look in libakerror?

+
#include <akerror.h>
+#include <errno.h>
+#include <string.h>
+
+int main(void) {
+    FILE *fp = fopen("somefile.txt", "w");
+    PREPARE_ERROR(errctx);
+    ATTEMPT {
+	FAIL_NONZERO_BREAK(errctx, fprintf(fp, "Some message\n"), errno, "Failed to write");
+    } CLEANUP {
+    } PROCESS(errctx) {
+    } HANDLE(errctx, ESTALE) {
+	    // Stale file handle
+	    // do something about it here and continue (somehow)
+    } HANDLE(errctx, ENOSPC) {
+	    // No space on the device
+	    // do something about it here and continue (somehow)
+    } HANDLE_DEFAULT(errctx) {
+	    // Some other undefined error, all we know how to do is abort
+	    return 1;
+    } FINISH_NORETURN(errctx);
+}
+ +

I believe this approach has several advantages over the previous method, without even digging into some of the library’s cooler features:

+
    +
  • It’s 2 lines shorter (not much, but it counts)
  • +
  • The separate portions of the error checking logic are placed into explicitly named scopes (attempting something, cleaning up from the attempt, and handling errors), clarifying what each block of code is doing
  • +
  • The initial process of performing an action, capturing the initial return code, and checking further errno details, and storing it into a single error context object, is done on one line that also encodes your intent (“fail if this operation returns non-zero”)
  • +
  • There is an explicit cleanup step in the error handling process - more on that in a minute
  • +
  • The default error handling behavior is wrapped into the FINISH macro; we don’t have to worry about it
  • +
+

But the macros hide a lot of complexity

Fair enough. Let’s compare what the two look like after the preprocessor is done with them.

+

First, our test case without libakerror:

+

+   int rc = fprintf(fp, "Some message\n");
+   if ( rc < 0 ) {
+printf("Failed to write");
+switch ( errno ) {
+case ESTALE:
+
+
+    break;
+case ENOSPC:
+
+
+    break;
+default:
+
+    return 1;
+}
+   }
+ +

As expected, it looks basically exactly as we wrote it, whitespace not withstanding.

+

Now here is the preprocessed libakerror code (after cleaning it up - the preprocessor has no respect for humans and how we see whitespace):

+
// PREPARE_ERROR(errctx);
+akerr_error_init();
+akerr_ErrorContext __attribute__ ((unused)) *errctx = ((void *)0);;
+
+// ATTEMPT
+switch ( 0 ) {
+case 0: {
+    // FAIL_NONZERO_BREAK
+    if ( fprintf(fp, "Some message\n") != 0 ) {
+		if ( errctx == ((void *)0)) {
+			errctx = array_next_error();
+			if ( errctx == ((void *)0)) {
+				error_log_method("%s:%s:%d: Unable to pull an ErrorContext from the array!", "test2.c", (char *)__func__, 13);
+				exit(1);
+			}
+		}
+		errctx->refcount += 1;;
+		errctx->status = errno;
+		snprintf((char *)errctx->fname, 256, "test2.c");
+		snprintf((char *)errctx->function, 128, __func__);
+		errctx->lineno = 13;
+		snprintf((char *)errctx->message, 1024, "Failed to write");
+		errctx->stacktracebufptr += snprintf(errctx->stacktracebufptr, AKERR_MAX_ERROR_STACKTRACE_BUF_LENGTH, "%s:%s:%d: %d (%s) : %s\n", (char *)errctx->fname, (char *)errctx->function, errctx->lineno, errctx->status, akerr_name_for_status(errctx->status, ((void *)0)), errctx->message);;
+		break;
+		};
+    }
+};
+
+// CLEANUP { }
+{
+}
+
+// PROCESS {
+if ( errctx != ((void *)0) ) {
+    switch ( errctx->status ) {
+    case 0:
+	errctx->handled = true;
+	{}
+	break;
+    // HANDLE(errctx, ESTALE)
+    case ESTALE:
+	errctx->stacktracebufptr = (char *)&errctx->stacktracebuf;
+	errctx->handled = true;
+	{}
+	break;
+    // HANDLE(errctx, ENOSPC)
+    case ENOSPC:
+	errctx->stacktracebufptr = (char *)&errctx->stacktracebuf;
+	errctx->handled = true;
+	{}
+	break;
+    // HANDLE_DEFAULT(errctx)
+    default:
+	errctx->stacktracebufptr = (char *)&errctx->stacktracebuf; 
+	errctx->handled = true;
+	{
+	    return 1;
+	}
+    };
+};
+
+// FINISH_NORETURN(errctx)
+if (errctx != ((void *) 0)) {
+    if (errctx->handled == 0) {
+        akerr_log_method ("%s%s:%s:%d: %s %d (%s): %s", (char *) &errctx->stacktracebuf, (char *) "test2.c", (char *) __func__, 21, "Unhandled Error", errctx->status, akerr_name_for_status (errctx->status, ((void *) 0)), errctx->message);;
+        akerr_handler_unhandled_error (errctx);
+    }
+}
+if (errctx != ((void *) 0))
+{
+    errctx = akerr_release_error (errctx);
+};;
+ +

The preprocessed libakerror code, prettied up, expands to 72 lines. That’s quite a balloon from our original 15 lines of error handling code.

+

We don’t like it

There are a few obvious criticisms we can level against the preprocessed libakerror code here, and I think all of them are defensible.

+
    +
  • It’s switch statements all the way down, man. Yes, the macros primarily just wrap a big switch statement that detects when the error has been set and uses case statements to define how to handle it. As I said, C isn’t broken. And if it’s not broke, don’t fix it. We just wrap it.
  • +
  • It’s calling methods I didn’t ask for, like error_init() Yes. There are a few helper methods that are called to manage the internal error state of the library as part of the macros. The things it’s doing are:
      +
    • Initializing the library and its objects, if they are not already initialized (one-time cost)
    • +
    • Finding the next available error context object (or failing gracefully if there is not one available). The performance of this method is predictable related to the MAX_HEAP_ERROR setting defining the maximum number of error frames, and it doesn’t do much. The other less performant code (like managing error context objects, string formatting, etc) are never executed unless there is an error detected.
    • +
    +
  • +
  • That FAIL_NONZERO_BREAK macro is freakin huge. Yes, there’s a lot of code hidden behind the CATCH and FAIL_* macros. But if you look at what they’re doing, it’s setting up a durable error context object with a ton of context about the error and where it occurred. This (to me) is worth the lines of code.
  • +
  • It’s doing expensive string formatting. This is true but only in the cases where an error has been detected. None of that code is executed when there is no error.
  • +
  • You’re doing things with pointers! That’s dangerous! This is a C library, get over it. Pointer management is not inherently unsafe behavior, and what we’re doing here is not particularly creative or risky.
  • +
+

Why I like it

Because it helps me in several ways:

+
    +
  1. It helps me avoid situations where I don’t check errors
  2. +
  3. It helps me debug a program error when all I have is a log and can’t attach a debugger
  4. +
  5. It helps me ensure cleanup behavior occurs on errors
  6. +
  7. It creates an automatic “jump to an error handler on error” pattern without using goto
  8. +
+

Again, C is not broken, and you can do all of this without this library. This library just makes it easier. And as humans, if something is easier, we are more likely to do it.

+

Ensuring errors get checked

It’s easy to end up in a situation where you don’t check a return code.

+
char *memptr;
+memptr = malloc(SOME_SIZE); /* Uh oh, what happens if memory allocation fails? */
+strcpy(memptr, "Some string"); /* Kaboom! */
+ +

This exact behavior is the source of a stunning number of errors in professional code that runs large portions of very important systems. Obviously you can discipline yourself to check and handle errors

+
char *memptr;
+memptr = malloc(SOME_SIZE);
+if ( memptr == NULL ) {
+    return 1; /* Let's assume our caller knows what to do */
+}
+strcpy(memptr, "Some string");
+ +

This is how the grand majority of C code ever written solves these problems. Modern C standards add a helpful keyword to cause our compilers to emit an error when return codes are not checked. For example in gcc you can:

+
extern int dangerous_func(void) __attribute__((warn_unused_result));
+
+int main(void) {
+    dangerous_func(); // Compiler will warn here
+    int rc = dangerous_func(); // OK, return value is used
+    return 0;
+}
+ +

Unfortunately it’s not so much a language standard so compilers may implement it differently. For example MSVC uses a _Check_return annotation instead. But they both offer the functionality.

+

This also only helps if the libraries you’re calling (like malloc) were defined with this semantic. And, again unfortunately, the C standard library does not require (or embrace) this mechanism. C stdlib’s default philosophy (which I generally agree with) is “I assume you know what you’re doing”. Unfortunately, assuming I know what I’m doing (or that any human knows what they’re doing) is a famously dangerous thing.

+

So if we can’t rely on our standard library enforcing that we’re doing something with return codes, we have to make it easier to discipline ourselves into checking the code. We have to build habits. Checking for a null pointer and returning 1 does not seem like a difficult habit to build. And it certainly seems simple to build when compared to stacking a series of macros.

+ + + + + + + + + +
**Naked C****libakerr C**
+
char *memptr;
+memptr = malloc(SOME_SIZE);
+if ( memptr == NULL ) {
+	printf("Failed to allocate memory");
+    return 1;
+}
+strcpy(memptr, "Some string");
+
+
PREPARE_ERROR(errctx);
+ATTEMPT {
+  char *memptr;
+  FAIL_ZERO_BREAK(errctx, (memptr = malloc(SOME_SIZE)), AKERR_NULLPOINTER, "Failed to allocate memory");
+  strcpy(memptr, "Some string");
+} CLEANUP {
+} PROCESS(errctx) {
+} FINISH(errctx, true);
+
+ +

Again, it’s only 1 line longer than the naked C. The working line in libakerr seems longer - however the FAIL_ZERO_BREAK line is only 102 characters, while the naked C code (memptr assignment from malloc, result comparison, and return statement) is 150 characters. Even I’m surprised, because honestly looking at the libakerror code, it does feel longer and more verbose to me. It’s probably some headtrick with the length of that FAIL_ZERO_BREAK line.

+

Now, that doesn’t matter, because we all know that the keyboard is not the choke point on getting code written. But it’s worth pointing out - if something feels like it’s taking longer, then the programmmer will hate it. And that’s where we come to another philosophical difference: The naked C version feels like it’s slower to write. (Because it actually is!) This means I will be less likely to discipline myself when writing the Naked C code, and more likely to take shortcuts, because (as much as I love writing code and I love writing it in C) ultimately I want to get it done and make it work.

+

Ultimately, by allowing me to write my code in such a way that error checking feels less cumbersome, I am confident that libakerror helps me solve a very major problem that most of us tend to overlook:

+
you lack discipline
+ +

Debugging when all you have is logs

+

Without good debug logs, a program is much more difficult to debug.

+
+

I’ve spent the majority of my career working with software that runs in environments where the only hope for debugging it is to interrogate some log files after an error has already occurred. Getting the chance to attach a debugger and examine the state of the running program is a rare treat that we almost never get to experience in the DevSecOps world. So if a program does not emit useful logs, in my opinion, it is basically impossible to debug it in my world.

+

And even when you have a debugger, a log is still a good way to quickly hone in on where something happened, even if you must take a much longer time to figure out why it happened. Let’s consider the code in one of the libakerror test programs.

+

In this code, we have a call graph like main() -> func1() -> func2(). In func2() we experience some error that causes us to exit early. If we were to run this in naked C, it might look like this:

+
#include <stdio.h>
+
+int func2(void)
+{
+	printf("This is a failure in func2");
+	return 1;
+}
+
+int func1(void)
+{
+	return func2();
+}
+
+int main(void)
+{
+	return func1();
+}
+ +

Now running the naked C code, we get

+
$ ./test ; echo $?
+This is a failure in func2
+1
+ +

This might seem like a good result. It tells us that there was an error, and where that error occurred. This is easy to do by manually adding debug printfs in naked C. But there is a question it does not help us answer, a question that is incredibly important in the real world:

+
+

HOW DID WE GET TO WHERE THE ERROR HAPPENED IN THE FIRST PLACE?

+
+

In this contrived example we know clearly that func1() called func2() and so here we are. But let’s say we’re working with some amount of code that is a black box to us - maybe our code is a callback from another library and we can’t be sure just exactly what the code path is that’s being taken. Or maybe it’s our own code, and we’ve simply forgotten all the possible pathways that this code could be reached. (This happens far more often than we want to admit.)

+

Now if we look at the libakerror version:

+
#include "akerror.h"
+
+akerr_ErrorContext *func2(void)
+{
+    PREPARE_ERROR(errctx);
+    ATTEMPT {
+	    FAIL(errctx, AKERR_NULLPOINTER, "This is a failure in func2");
+    } CLEANUP {
+    } PROCESS(errctx) {
+    } FINISH(errctx, true);
+    SUCCEED_RETURN(errctx);
+}
+
+akerr_ErrorContext *func1(void)
+{
+    PREPARE_ERROR(errctx);
+    ATTEMPT {
+	    CATCH(errctx, func2());
+    } CLEANUP {
+    } PROCESS(errctx) {
+    } FINISH(errctx, true);
+    SUCCEED_RETURN(errctx);
+}
+
+int main(void)
+{
+    PREPARE_ERROR(errctx);
+    ATTEMPT {
+    	CATCH(errctx, func1());
+    } CLEANUP {
+    } PROCESS(errctx) {
+    } FINISH_NORETURN(errctx);
+}
+ +

… and the libakerror output:

+
libakerror/tests/err_trace.c:func2:7: 1 (Null Pointer Error) : This is a failure in func2
+libakerror/tests/err_trace.c:func2:10
+libakerror/tests/err_trace.c:func1:18: Detected error 0 from array (refcount 1)
+libakerror/tests/err_trace.c:func1:18
+libakerror/tests/err_trace.c:func1:21
+libakerror/tests/err_trace.c:main:30: Detected error 0 from array (refcount 1)
+libakerror/tests/err_trace.c:main:30
+libakerror/tests/err_trace.c:main:33: Unhandled Error 1 (Null Pointer Error): This is a failure in func2
+ +

Every akerr_ErrorContext object that’s been populated with PREPARE_ERROR() contains the file, line number, and function name of the location where it is when the error is encountered. The stack frame that actually detects the error includes the actual error code, a description of the error code’s meaning, and a short message from the CATCH or FAIL_* macro that set it. This gives us a pretty good stacktrace to start diagnosing the problem.

+

Now there are other ways of getting a stacktrace, and they are arguably better options than what’s being done here, but they have a few caveats:

+
    +
  1. Lots of the options only work well on Linux or BSD variants
  2. +
  3. Some of the options only work well with a certain binary format (such as ELF)
  4. +
  5. Since all of them inspect the call stack at runtime, they can’t tell you things like file names, line numbers, and method names unless the code was compiled with DWARF or other debugging symbols enabled
  6. +
+

The libakerror backtrace code is limited in that it can only tell you about the frames wherein PREPARE_ERROR(), CATCH or FAIL_* macros are used, but:

+
    +
  1. The behavior is entirely cross platform. It works equally well on any platform where your code will compile that supplies __FILE__, __LINE__ and __func__ macros. (Every compiler supplies these as far as I know.)
  2. +
  3. The behavior works independently of machine architecture or binary formats
  4. +
  5. Because these are all preprocessor macros, the names of source files, lines, and function names are inserted at compile time, requiring no debugging symbols to be explicitly included in your binary.
  6. +
+

And I think that’s a perfectly acceptable set of tradeoffs.

+

Ensuring cleanup behavior occurs on error

This is another very common class of problems in basically every language, not just C. Let’s consider this code:

+
int writeToFile(char *fname, char *string)
+{
+	int success = 0;
+	FILE *fp = fopen(fname, "w");
+	if ( fp == NULL ) {
+		printf("Error %d (%s)", errno, strerror(errno));
+	    return 1;
+	}
+   	if ( string == NULL ) {
+   		printf("Can't print null string to file!\n");
+   		return 1;
+   	}
+	success = strlen(string);
+   	if ( fwrite(fp, string) != success ) {
+		printf("Error %d (%s)", errno, strerror(errno));
+		return 1;
+	}
+	if ( fclose(fp) != 0 ) {
+		printf("Error %d (%s)", errno, sterror(errno));
+		return 1;
+	}
+	return 0;
+}
+ + +

The error should stand out. When we check for the null string pointer, we return early, but we forget to close the file handle we just opened. Using libakerror helps prevent this behavior in 2 ways:

+
    +
  1. Since I have a macro that inserts a PREPARE .. ATTEMPT .. CLEANUP .. PROCESS .. HANDLE .. FINISH block at the stroke of a single key, I’m automatically presented with a clearly named block where I’m supposed to put cleanup code. This triggers me to ask myself “what should I clean up here?”, and write that code. (To be fair, you can do the same thing with a macro and a naked C block with a comment inside of it that prompts you appropriately.)
  2. +
  3. If I begin a ATTEMPT block and omit the CLEANUP block, the macros generate invalid code, and compilation will fail. (Granted - the compilation fails in a way that is confusing in the way that a missing brace is confusing, but it does prevent running code with an incomplete error management block. Not quite rust, but I’ll take it.)
  4. +
+

In libakerror, this code becomes:

+
akerr_ErrorContext *writeToFile(char *fname, char *string)
+{
+	PREPARE_ERROR(errctx);
+	FILE *fp = NULL;
+	int success = 0;
+	ATTEMPT {
+	    FAIL_ZERO_BREAK(errctx, (fp = fopen(fname, "w")), errno, "Failed to open %s : %s", fname, strerr(errno));
+		FAIL_ZERO_BREAK(errctx, string, AKERR_NULLPOINTER, "Null pointer");
+		success = strlen(string);
+   		FAIL_NONZERO_BREAK(errctx, (fwrite(fp, string) == success), errno, strerror(errno));
+	} CLEANUP {
+		FAIL_NONZERO_RETURN(errctx, fclose(fp), errno, strerror(errno));
+	} PROCESS(errctx) {
+	} FINISH(errctx, true);
+	SUCCEED(errctx);
+}
+ +

Now this is, of course, a contrived example made purely to prove a point - this could just as easily be solved by moving the string NULL check above the opening of the file pointer (which is, in fact, the more correct thing to do). But this issue (needing to perform some kind of clean up, such as freeing memory or closing files, after an error) is very common in the real world and this was an easy way to demonstrate it.

+

Managing early exit handlers with cleanup without goto

Continuing the writeToFile() example from above, we have an issue of best practice. As the code is currently written (in naked C), when we fix the code to add the cleanup after the string check, we wind up with this:

+
int writeToFile(char *fname, char *string)
+{
+	int success = 0;
+	FILE *fp = fopen(fname, "w");
+	if ( fp == NULL ) {
+		printf("Error %d (%s)", errno, strerror(errno));
+	    return 1;
+	}
+   	if ( string == NULL ) {
+   		printf("Can't print null string to file!\n");
+		fclose(fp);
+   		return 1;
+   	}
+	success = strlen(string);
+   	if ( fwrite(fp, string) != success ) {
+		printf("Error %d (%s)", errno, strerror(errno));
+		return 1;
+	}
+	if ( fclose(fp) != 0 ) {
+		printf("Error %d (%s)", errno, sterror(errno));
+		return 1;
+	}
+	return 0;
+}
+ +

… But wait a second … we know that it’s possible (although odd) that fclose(fp) could ALSO fail. We might need to report that error as well! So now we do what most programmers do, and we duplicate the code.

+
int writeToFile(char *fname, char *string)
+{
+	int success = 0;
+	FILE *fp = fopen(fname, "w");
+	if ( fp == NULL ) {
+		printf("Error %d (%s)", errno, strerror(errno));
+	    return 1;
+	}
+   	if ( string == NULL ) {
+   		printf("Can't print null string to file!\n");
+		if ( fclose(fp) != 0 ) {
+			printf("Error %d (%s)", errno, sterror(errno));
+			return 1;
+		}
+   		return 1;
+   	}
+	success = strlen(string);
+   	if ( fwrite(fp, string) != success ) {
+		printf("Error %d (%s)", errno, strerror(errno));
+		return 1;
+	}
+	if ( fclose(fp) != 0 ) {
+		printf("Error %d (%s)", errno, sterror(errno));
+		return 1;
+	}
+	return 0;
+}
+ +

Gag me with a spoon. I hate it, and so do you. So we have another pattern that we use: the early exit handler with our friend that everyone loves to hate, goto.

+
int writeToFile(char *fname, char *string)
+{
+	int success = 0;
+	int rc = 0;
+	FILE *fp = fopen(fname, "w");
+	if ( fp == NULL ) {
+		goto _writeToFile_earlyexit
+	}
+   	if ( string == NULL ) {
+   		printf("Can't print null string to file!\n");
+   		rc = 1;
+	   	goto _writeToFile_cleanup
+   	}
+	success = strlen(string);
+   	if ( fwrite(fp, string) != success ) {
+		goto _writeToFile_earlyexit
+	}
+	goto _writeToFile_cleanup
+_writeToFile_earlyexit:
+	printf("Error %d (%s)", errno, sterror(errno));
+	return 1;
+_writeToFile_cleanup:
+	if ( fclose(fp) != 0 || rc != 0 ) {
+		goto _writeToFile_earlyexit
+	}
+	return 0;
+}
+ +

Despite the revulsion that I know a lot of you are feeling right now, this code is better than the code we had before, and it is in fact using a common pattern that is acceptable in the industry. However, this has several problems.

+
    +
  1. The use of goto in the C language is so universally feared that most C linters will report this code as a warning at the least, and some static code analyzers may in fact flag it as an “unsafe” risk, blocking your code from leaving the build system
  2. +
  3. You will feel bad about publishing it (unless you’re me, go off)
  4. +
  5. goto labels share a single universal namespace, so they have to be very lengthy, and writing a ton of them can get annoying
  6. +
  7. In order to do this properly, we had to introduce two separate blocks: one for the cleanup, one for the early exit behavior. This is because some code paths don’t need to call the cleanup, but all code paths (including the cleanup) might need to call the early exit behavior. So in order to prevent duplicating code, we made two goto targets.
  8. +
  9. For the same reason as above, we had to introduce a new variable, rc, to hold the intended final return code for our method - either 0 or 1. Then, because the cleanup method is in the unhappy path for the string null check, and we must flow from there into the earlyexit handler, but ONLY in the case when we in the unhappy code path, we had to set the rc variable in the string NULL check and then check it again in the cleanup method, using it as a flag to divert execution into the early exit handler.
  10. +
+
+

“That’s crazy. Real C programmers don’t do this! This is just proof that you’re retarded!”

+
+

Again, I accept the possibility that I’m just an idiot. But I promise you, real C programmers do this with remarkable frequency. To be clear, you do NOT have to do this. There ARE better ways. Creative use of switch, and break will give you a better result.

+

But I don’t worry about it, because using libakerr, I do not have to manage this! My early exit behavior is baked into the macros and the macro structure. We don’t even need to change the libakerror code from our previous example to get the same (but smarter) behavior as what you see in the naked C example; it’s already doing it. Look at the cleaned up preprocessed code after the macros have been expanded, look at where the code may return, why, and how the cleanup logic is handled.

+
akerr_ErrorContext *writeToFile (char *fname, char *string)
+{
+	// PREPARE_ERROR(errctx)
+    akerr_init ();
+    akerr_ErrorContext __attribute__((unused)) * errctx = ((void *) 0);;
+	
+    FILE *fp = ((void *) 0);
+    int success = 0;
+	
+	// ATTEMPT
+    switch (0) {
+    case 0: {
+		
+		// FAIL_ZERO_BREAK(errctx, (fp = fopen(fname, "w")), errno, "Failed to open %s : %s", fname, strerror(errno));
+        if ((fp = fopen (fname, "w")) == 0) {
+            if (errctx == ((void *) 0)) {
+                errctx = akerr_next_error ();
+                if (errctx == ((void *) 0)) {
+                    akerr_log_method("%s:%s:%d: Unable to pull an error context from the array!", "test2.c", (char *) __func__, 118);
+                    exit (1);
+                }
+            }
+            errctx->refcount += 1;;
+            errctx->status = (*__errno_location ());
+            snprintf ((char *) errctx->fname, 256, "test2.c");
+            snprintf ((char *) errctx->function, 128, __func__);
+            errctx->lineno = 118;
+            snprintf ((char *) errctx->message, 1024, "Failed to open %s : %s", fname, strerror ((*__errno_location ())));
+            errctx->stacktracebufptr += snprintf (errctx->stacktracebufptr, 2048, "%s:%s:%d: %d (%s) : %s\n", (char *) errctx->fname, (char *) errctx->function, errctx->lineno, errctx->status, akerr_name_for_status (errctx->status, ((void *) 0)), errctx->message);;
+            break;
+	    };
+		
+		// FAIL_ZERO_BREAK(errctx, string, AKERR_NULLPOINTER, "Null pointer");
+        if (string == 0) {
+            if (errctx == ((void *) 0)) {
+                errctx = akerr_next_error ();
+                if (errctx == ((void *) 0)) {
+                    akerr_log_method("%s:%s:%d: Unable to pull an error context from the array!", "test2.c", (char *) __func__, 119);
+                    exit (1);
+		        }
+	        }
+            errctx->refcount += 1;;
+            errctx->status = 1;
+            snprintf ((char *) errctx->fname, 256, "test2.c");
+            snprintf ((char *) errctx->function, 128, __func__);
+            errctx->lineno = 119;
+            snprintf ((char *) errctx->message, 1024, "Null pointer");
+            errctx->stacktracebufptr += snprintf (errctx->stacktracebufptr, 2048, "%s:%s:%d: %d (%s) : %s\n", (char *) errctx->fname, (char *) errctx->function, errctx->lineno, errctx->status, akerr_name_for_status (errctx->status, ((void *) 0)), errctx->message);;
+            break;
+	    };
+        success = strlen (string);
+
+        // FAIL_NONZERO_BREAK(errctx, (fprintf(fp, "%s", string) == success), errno, "%s", strerror(errno));
+        if ((fprintf (fp, "%s", string) == success) != 0) {
+            if (errctx == ((void *) 0)) {
+                errctx = akerr_next_error ();
+                if (errctx == ((void *) 0)) {
+                    akerr_log_method("%s:%s:%d: Unable to pull an error context from the array!", "test2.c", (char *) __func__, 121);
+                    exit (1);
+		        }
+	        }
+            errctx->refcount += 1;;
+            errctx->status = (*__errno_location ());
+            snprintf ((char *) errctx->fname, 256, "test2.c");
+            snprintf ((char *) errctx->function, 128, __func__);
+            errctx->lineno = 121;
+            snprintf ((char *) errctx->message, 1024, "%s", strerror ((*__errno_location ())));
+            errctx->stacktracebufptr += snprintf(errctx->stacktracebufptr, 2048, "%s:%s:%d: %d (%s) : %s\n", (char *) errctx->fname, (char *) errctx->function, errctx->lineno, errctx->status, akerr_name_for_status (errctx->status, ((void *) 0)), errctx->message);;
+            break;
+	    };
+        }
+    };
+	
+	// CLEANUP
+    {
+	if (fclose (fp) != 0) {
+	    if (errctx == ((void *) 0)) {
+		    errctx = akerr_next_error ();
+		    if (errctx == ((void *) 0)) {
+		        akerr_log_method("%s:%s:%d: Unable to pull an error context from the array!", "test2.c", (char *) __func__, 123);
+		        exit (1);
+		    }
+	    }
+	    errctx->refcount += 1;;
+	    errctx->status = (*__errno_location ());
+	    snprintf ((char *) errctx->fname, 256, "test2.c");
+	    snprintf ((char *) errctx->function, 128, __func__);
+	    errctx->lineno = 123;
+	    snprintf ((char *) errctx->message, 1024, "%s", strerror ((*__errno_location ())));
+	    errctx->stacktracebufptr += snprintf(errctx->stacktracebufptr, 2048,"%s:%s:%d: %d (%s) : %s\n", (char *) errctx->fname, (char *) errctx->function, errctx->lineno, errctx->status, akerr_name_for_status (errctx->status, ((void *) 0)), errctx->message);;
+	    return errctx;
+	};
+    }
+	
+	// PROCESS(errctx)
+	
+    if (errctx != ((void *) 0)) {
+	    switch (errctx->status) {
+            case 0:
+            errctx->handled = 1;
+	        {
+	        }
+        };
+    };
+	
+	// FINISH(errctx, true)
+    if (errctx != ((void *) 0)) {
+		if (errctx->handled == 0 && 1 == 1) {
+			errctx->stacktracebufptr += snprintf (errctx->stacktracebufptr, 2048, "%s:%s:%d\n", (char *) "test2.c", (char *) __func__, 125);
+			return errctx;
+		}
+    }	
+    if (errctx != ((void *) 0)) {
+        errctx = akerr_release_error (errctx);
+    };;
+	
+	// SUCCEED_RETURN(errctx);
+    if (errctx != ((void *) 0)) {
+      errctx = akerr_release_error (errctx);
+    };
+    return ((void *) 0);;
+}
+ +

Here we can see that using the libakerror framework, our code:

+
    +
  1. Exits early whenever an akerr_ErrorContext object is unable to be initialized (via exit(1))
  2. +
  3. Returns from the CLEANUP block if the fclose(fp) call fails, returning the akerr_ErrorContext up to the calling context
  4. +
  5. Returns 0 at the end of the function assuming everything happens correctly
  6. +
+

And it does it all without goto.

+

Solving a problem you don’t actually have

So, let’s revisit the claim

+
<#c user> this is solving a problem that doesn't exist
+ +
    +
  1. We have been able to demonstrate several varieties of problems which are very real, very common, and very much present even in our beloved C language.
  2. +
  3. We have been able to demonstrate that libakerror solves these problems quite handily
  4. +
+

I still don’t like it

And that’s fine. It’s okay for people to like different things. But if you have the same kinds of problems I’ve described here, and you love (or want to love) writing in C, but error handling has made you gunshy … Feel free to take this code and use it. It’s free (as in speech).

+ +
+
+ +
+
+ + + + +
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/about/index.html b/about/index.html index 183c1a1..7878ba4 100644 --- a/about/index.html +++ b/about/index.html @@ -79,6 +79,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ +
+ +
+

Archive

+
+
+ + + +
2022
+ + +
+
+
+ +
+ + The Righteous Mind + +
+
+
+
+ +
+ + + +
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/atom.xml b/atom.xml index f17c69d..2520fa7 100644 --- a/atom.xml +++ b/atom.xml @@ -6,7 +6,7 @@ - 2026-01-08T14:22:41.402Z + 2026-01-11T03:03:05.994Z https://aklabs.net/ @@ -16,31 +16,12 @@ Hexo - - News - 2026 - Week 1 - - https://aklabs.net/2026/01/08/News-2026-Week-1/ - 2026-01-08T14:22:41.402Z - 2026-01-08T14:22:41.402Z - - - Musings on android source code releases, the return of the keyboard PC, some AI workflows are just event driven architecture, sociopaths in civil service, what happens when AI interfaces replace proper APIs, and comprehensible interfaces to quantum computing chips - - - - - - - - - - Single Pane of Glass - - https://aklabs.net/2026/01/08/Single-Pane-of-Glass/ - 2026-01-08T14:22:41.402Z - 2026-01-08T14:22:41.402Z + + https://aklabs.net/2026/01/11/Single-Pane-of-Glass/ + 2026-01-11T03:03:05.994Z + 2026-01-11T03:03:05.994Z There are too many ways of doing the same shit across too many different mediums and platforms @@ -54,10 +35,10 @@ Smart LEGO - - https://aklabs.net/2026/01/08/Smart-LEGO/ - 2026-01-08T14:22:41.402Z - 2026-01-08T14:22:41.402Z + + https://aklabs.net/2026/01/11/Smart-LEGO/ + 2026-01-11T03:03:05.994Z + 2026-01-11T03:03:05.994Z @@ -73,6 +54,42 @@ + + + + libakerror + + https://aklabs.net/2026/01/11/libakerror/ + 2026-01-11T03:03:05.994Z + 2026-01-11T03:03:05.994Z + + + A C library I wrote for exception style error handling in C code + + + + + + + + + + News - 2026 - Week 1 + + https://aklabs.net/2026/01/11/News-2026-Week-1/ + 2026-01-11T03:03:05.993Z + 2026-01-11T03:03:05.993Z + + + Musings on android source code releases, the return of the keyboard PC, some AI workflows are just event driven architecture, sociopaths in civil service, what happens when AI interfaces replace proper APIs, and comprehensible interfaces to quantum computing chips + + + + + + + + @@ -80,7 +97,7 @@ https://aklabs.net/2025/01/04/Socrates-got-a-raw-deal/ 2025-01-04T15:06:30.000Z - 2026-01-08T14:22:41.402Z + 2026-01-11T03:03:05.994Z @@ -109,7 +126,7 @@ https://aklabs.net/2024/03/08/new-manager-pt3/ 2024-03-08T00:10:28.000Z - 2026-01-08T14:22:41.403Z + 2026-01-11T03:03:05.995Z @@ -132,7 +149,7 @@ https://aklabs.net/2024/03/05/new-manager-pt2/ 2024-03-05T12:10:28.000Z - 2026-01-08T14:22:41.402Z + 2026-01-11T03:03:05.995Z @@ -155,7 +172,7 @@ https://aklabs.net/2024/03/03/new-manager-pt1/ 2024-03-03T12:10:28.000Z - 2026-01-08T14:22:41.402Z + 2026-01-11T03:03:05.994Z @@ -178,7 +195,7 @@ https://aklabs.net/2024/02/09/expectations/ 2024-02-09T08:30:30.000Z - 2026-01-08T14:22:41.402Z + 2026-01-11T03:03:05.994Z @@ -201,7 +218,7 @@ https://aklabs.net/2024/01/29/Simon-Sinek-Start-with-Why/ 2024-01-29T16:35:48.000Z - 2026-01-08T14:22:41.402Z + 2026-01-11T03:03:05.994Z @@ -226,7 +243,7 @@ https://aklabs.net/2023/12/23/United-States-Catholic-Catechism-for-Adults/ 2023-12-23T17:03:44.000Z - 2026-01-08T14:22:41.402Z + 2026-01-11T03:03:05.994Z @@ -251,7 +268,7 @@ https://aklabs.net/2023/11/05/Copper-Sun/ 2023-11-05T20:01:51.000Z - 2026-01-08T14:22:41.401Z + 2026-01-11T03:03:05.993Z @@ -276,7 +293,7 @@ https://aklabs.net/2023/10/10/The-Cost-of-Discipleship/ 2023-10-10T20:23:33.000Z - 2026-01-08T14:22:41.402Z + 2026-01-11T03:03:05.994Z @@ -301,7 +318,7 @@ https://aklabs.net/2023/09/28/Bill-Heavey-Jerkey-What-Did-I-Just-Eat/ 2023-09-28T20:34:33.000Z - 2026-01-08T14:22:41.401Z + 2026-01-11T03:03:05.993Z @@ -326,7 +343,7 @@ https://aklabs.net/2023/09/24/How-Should-We-Then-Live/ 2023-09-24T20:58:40.000Z - 2026-01-08T14:22:41.401Z + 2026-01-11T03:03:05.993Z @@ -351,7 +368,7 @@ https://aklabs.net/2023/09/12/Christianity-First-3000-Years/ 2023-09-12T21:20:20.000Z - 2026-01-08T14:22:41.401Z + 2026-01-11T03:03:05.993Z @@ -376,7 +393,7 @@ https://aklabs.net/2022/10/15/Live-Not-By-Lies/ 2022-10-15T22:11:25.000Z - 2026-01-08T14:22:41.402Z + 2026-01-11T03:03:05.993Z @@ -401,7 +418,7 @@ https://aklabs.net/2022/05/19/Jesus-Outside-the-Lines/ 2022-05-19T21:51:09.000Z - 2026-01-08T14:22:41.401Z + 2026-01-11T03:03:05.993Z @@ -426,7 +443,7 @@ https://aklabs.net/2022/05/04/American-Prison/ 2022-05-04T21:42:34.000Z - 2026-01-08T14:22:41.401Z + 2026-01-11T03:03:05.993Z @@ -449,7 +466,7 @@ https://aklabs.net/2022/05/02/The-Hero-Code/ 2022-05-02T21:58:12.000Z - 2026-01-08T14:22:41.402Z + 2026-01-11T03:03:05.994Z @@ -462,31 +479,6 @@ - - - - - - - - - The Righteous Mind - - https://aklabs.net/2022/03/17/The-Righteous-Mind/ - 2022-03-17T21:31:56.000Z - 2026-01-08T14:22:41.402Z - - - - - - - <img alt="Image of The Righteous Mind" style="float: left; padding: 20px 20px 20px 20px;" - - - - - diff --git a/categories/Books/index.html b/categories/Books/index.html index a50efc9..327766a 100644 --- a/categories/Books/index.html +++ b/categories/Books/index.html @@ -79,6 +79,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ +
+ +
+
Categories - technology
+
+
+ + + +
2026
+ + +
+
+
+ +
+ + libakerror + +
+
+
+
+ +
+ +
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/consulting/index.html b/consulting/index.html index dbbeb0e..695c401 100644 --- a/consulting/index.html +++ b/consulting/index.html @@ -79,6 +79,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ +
+ +
+ + +
+ + + + +
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/index.html b/tags/index.html index bb77ef0..ae54694 100644 --- a/tags/index.html +++ b/tags/index.html @@ -79,6 +79,37 @@ + + + + + + + + + + + + + +