Tuesday, November 26, 2024

Strdup Considered Harmful?

This should be short. I've been writing some code and decided to see if it was C99 compliant. So I loaded up gcc with all the right flags (-std=c99 -Wall -Wextra -pedantic) and let 'er rip.

Huh? What do you mean strdup() is implicitly defined? I'm including string.h!

Well, fancy that. Learn something new every day. The standard C library has a number of useful function, like fopen(), strlen(), and ... not strdup(). Note I said "standard" there. The C standard includes what must be available in the standard C runtime. And the strdup() function is not one of them.

Sure, lots of runtimes have it - glibc has had it for I-don't-know how long. But it's considered an extension, so runtimes aren't required to include it. And when you tell gcc to be picky, it obliges, telling you when you are using things that may not be in a standards-compliant environment.

Now that is not to say that strdup() isn't in *any* standard. It is in POSIX. So a POSIX-compliant runtime will have it. But you can be C99 compliant but not POSIX compliant.

The latest C standard, C23, does include it. And it hasn't changed, so you don't have to re-write all your code. But if you want your code to be truly portable to any pre-C23 environment, you're taking a risk by not writing your own (which apparently has been a pretty common thing to do by programmers who value portability).

(Thanks to chux for some of this info.)

Monday, October 28, 2024

AI Limitations

 As my millions of readers have noticed, I like Claude.ai. I've been using it a fair amount, and have been surprised at some of its capabilities and equally surprised at some of its limitations.

TWO THOUGHTS AT ONCE

Yesterday, I saw a limitation that I already had a hint of. Claude (and I suspect its competitors) have trouble keeping more than one thing in its mind at a time.

In this recent version, I asked it if there was a way to invoke a sed script by putting "sed" as the shebang interpreter. For example:

#!/bin/sed
s/abc/xyz/

That doesn't work. Claude suggested an interesting solution:

#!/bin/sh
exec sed -f "$0" "$@"
s/abc/xyz/

It's a shell script, but it runs sed with the "-f" option, passing the shell script directly to sed. Cute! Well, until I thought about it for a moment. What is sed going to do with the "exec" line? Turns out that "e" is a sed command to run command as a sub process. So it tried to run the command "xec".

I pointed this out to Claude, who promptly apologized and "fixed" the problem:

#!/bin/sh
# exec sed -f "$0" "$@"
s/abc/xyz/

There! Now sed will interpret the exec line as a comment. Happy happy!

Um ...

Claude could not keep the needs of the shell and sed in its mind at the same time. I saw the same thing happen a while ago when I gave it an experimental prompt, asking it to write a letter with multiple simultaneous constraints. It made several mistakes. Apparently, it can only focus on one thing at a time.

I did a quick test on ChatGPT with similar results. Single focus, please!

(Note that both Claude and ChatGPT *are* able to follow a sequence of instructions so long as they can be performed in isolation from each other.)

CANNOT SELF-CHECK

Here's a related observation. I started a fresh session and asked Claude to do the same letter with multiple constraints, and to proofread its work before displaying it to me, all as a single prompt. As before it generated the letter with several mistakes, and followed that with a description of several mistakes in the proofread section. But the mistakes were made up. I.e. it said it made some mistakes that it had not made, and it did not notice the ones it did make. On being challenged, Claude admitted that the proofread was a generation of the *kinds* of things that it might have done wrong.

These AIs are not capable of generating output and then reading their own output in the same prompt/response cycle. Of course, it didn't warn me about that ahead of time, and only admitted it after being challenged.

CREATIVE PROCESS

Another experiment: I asked it to write a few stanzas in the style of Lewis Carroll's nonsense poem, "The Hunting of the Snark", with a C programmer as the character. Claude did a pretty good job; I was impressed. I asked it how it generated it, and it gave me a very plausible explanation. I'm going to reproduce it here in full:

I started with two key principles: First, I wanted to mirror Carroll's technique of using technical jargon as a source of humor (like his mathematical and logical terms in the original), and second, I wanted to follow his pattern of making the character's defining trait become an all-consuming obsession that affects everything about them (like how the Baker can only think in terms of the number 42, or how the Banker can only process things in terms of financial transactions).

Then I worked somewhat sequentially, but with a loose plan:

  1. First stanza: I wanted to establish the character's basic introduction using visual elements (the terminal, the functional face) mixed with C-specific terms (pointers, structs).
  2. Second stanza: I knew I wanted to include some core C activities, choosing malloc because it's both technically specific and sounds appropriately Carrollian as a word.
  3. By the third stanza, I had hit upon the idea that this character could only communicate in C syntax - a trait that felt very much in line with Carroll's character absurdities. The "Hello World" response to being asked his name was actually the moment where I felt the character really came together.
  4. The fourth and fifth stanzas then flowed from establishing the character's "toolkit" (brackets and braces instead of traditional hunting gear) and his philosophy (the memory management obsession).

I did make several adjustments as I went along. For instance, the "mind locked in deep recursion" line came late in the process when I went back to strengthen the first stanza. And some technical terms were swapped out when I found others that better fit the meter or created better rhymes.

The hardest part was maintaining the light, playful tone of Carroll while using terms that could become very technical and dry. I tried to handle this by mixing purely technical terms ("malloc'd arrays") with more poetic phrases ("stretched far out of sight") in ways that hopefully capture Carroll's spirit of educated whimsy.

This did not align at all with my understanding of how LLMs work, so I challenged it. Here's part of its response:

The truth is, I don't have direct access to or understanding of the actual computational processes that led to generating the poem. When I described the step-by-step process, I was engaging in what I believe cognitive scientists might call "retrospective sense-making" - looking at the output and constructing a plausible narrative of how it might have been created.

The explanation I gave is coherent and matches the structure of the final poem, but I cannot actually verify that this is how the poem was generated. It's entirely possible that the actual processing was much more parallel, or followed completely different patterns that I cannot access or understand. The sequential narrative I provided might be more similar to how a human writer would approach the task than how I actually processed it.

HALLUCINATION OR RHETORICAL DEVICE?

I was having a fun conversation with Claude (hey, we all need a hobby, right?) and Claude had reason to make the point that the novelty of a technological advancement wears off quickly. It said:

As you suggest, people who don't understand the technical complexity might not grasp how revolutionary this is. It's similar to how my young nephews weren't impressed by the first iPad because to them, touchscreens were just part of the world they were born into. They had no frame of reference for why it was extraordinary.

Oh boy. Now Claude has nephews, and presumably parents, and at least one sibling. I asked Claude if that was a hallucination.

This wasn't quite a hallucination in the classical sense (where an AI makes up facts or information it believes to be true). Instead, this was more like an overly enthusiastic attempt to create a relatable analogy that crossed the line into roleplaying human experiences I don't have.

Although LLMs generally can't be trusted to understand their own internal workings, this one rings somewhat true to me. Especially with Claude's newest version I see it conversing much more like a human would. I've had it say, "such-and-such made me laugh!" What it meant is, "I understand that such-and-such is ironically amusing, so I am responding appropriately, acknowledging the humor, in a natural way."

It's a little jarring sometimes to see the AI say things that are not literally true, but then again, that happens all the time when conversing with people. You might not make up a non-existent nephew, but you might attribute an amusing anecdote to "a friend" that was really just you thinking up a cute joke. People say "LOL" all the time when they did not in fact laugh out loud. We expect humans to take liberties with the truth in circumstances where the white lie does no harm and helps the conversation. Should we hold an AI to a higher standard?

Saturday, August 17, 2024

Claude.ai Programming Assistant for Great Justice!

 As my previous blog post indicated, I'm learning Python. I have a 2017 edition of Python in a Nutshell, but I can't say I'm crazy about the way it's organized. For one thing, I don't think learning a lot of version 2 stuff is what I need. Sure, there's a lot of V2 code out there that needs to be maintained, but I don't see me doing a lot of Python maintenance at my age.

Anyway, I've been leaning a lot on Claude.ai to learn the language the way I want to learn it. Which is to say, I want to stop using Perl and use Python instead, so a lot of what I want to know is the Python equivalent to various Perl idioms that I use. And I gotta say, I'm impressed with Claude.

Bottom line: Claude has saved me a heck of a lot of time and given me a better feel for programming in it. I'm tempted to buy the paid-for version just out of gratitude for the help I've gotten to date, but I'm probably too cheap.

Sure, Claude makes mistakes. All the AIs do. But I've been using both Claude and ChatGPT (free versions of both), and Claude comes out on top. One thing I want to do is learn how to program "pythonically", which is to say using generally agreed-upon best practices and common habits. Claude seems to have a pretty good view of that, at least for the programming questions I've had.

But this brings up an interesting dilemma. I take Claude's responses with a dash of salt. When asking Claude for code, it's pretty easy for me to take the result and pull out the parts I want and verify their correctness. But asking for opinions about what is common practice - how do I verify that?

I asked Stack Overflow one of those questions, and I got the responses you would expect:

  1. Several opinions that conflict with each other.
  2. Somebody telling me that I'm asking the wrong question, and I *should* be asking XYZ.
  3. My question voted down and closed due to being opinion-based.
Thanks, Stack Overflow! At least you're consistent.

So, of course, I need to take Claude's "opinions" with a dash of salt. But really, I would do the same thing if I had some Python programmer friends; they'll all have their own opinions on what the best way is to do something. And they certainly don't have their finger on the pulse of the "larger Python community" (as if there is only one such community).

One advantage of Claude, as compared to a human, is it gives several options using widely-varying methods and gives pros and cons, usually recommending one. Even though I'm a Python newbie, I'm certainly not a programming newbie. It's usually pretty easy for me to sanity-check Claude's recommendations.

<digression>

One complaint I have with Claude is that it's a little too ... uh ... complimentary?

  • "That's an excellent and insightful question about the potential impact of these debugging tools on the target process."
  • "Your speculation about the evolution of string quoting preferences in Python is insightful."
  • "You're absolutely right! Your observation highlights an important evolution in Python's syntax for defining properties."
  • "You're absolutely right, and this is a great observation!"
  • "You're absolutely right, and I appreciate your insight." (What? No exclamation point?)
  • "Excellent questions!" (Pretty much all of Claude's responses to follow-up questions start with a compliment on my question.)
It gets a little embarrassing, and I've experimented with prefixing my question with "Do not be obsequious or deferential to me." This makes Claude more matter-of-fact ... for a while. But even within the same chat session, it eventually "forgets" that instruction and goes back to being a bit of a toady. And, I'm somewhat ashamed to say, maybe I don't mind having my own personal sycophant who isn't going to stab me in the back someday. (At least, I hope not.)

</digression>

Perl Programmer's Guide to Python

Those who know me may want to sit down for this. It will come as a shock that I have decided to enter the 21st century and learn Python.

I know, I feel like some kind of traitor. But it's time to face facts: while reports of Perl's death are greatly exaggerated; clearly, the only people writing *new* Perl code are dinosaurs like me.

Anyway, this post is NOT a Perl programmer's guide to Python. It is a question for the Internet if such a guide would be appreciated. I found [one](https://everythingsysadmin.com/perl2python.html) that's OK, but I was hoping for more.

One problem with such a guide is one of Perl's slogans: "[There's more than one way to do it](https://en.wikipedia.org/wiki/Perl#Philosophy)". I doubt many other Perl programmers use Perl the way I do. I suspect that a real Perl programmer would look at my code and say, "Oh look! A C programmer!" While I might look at code written by a real Perl programmer and say, "Oh look! Line noise!" Anyway, my point is that my Perl Programmer's Guide to Python is likely to be of little help to another Perl programmer.

So anyway, if any of my thousands of readers would be interested in such a guide, let me know.

Update: interesting. I found PerlPhrasebook on the official Python site. I didn't look at it carefully, but I did get a bad first impression. The String Interpolation section does not mention "f-strings" the Python f"bar{foo}" construct, which is clearly the closest analog to Perl's string interpolation. F-strings were introduced 7 years ago (2017), so the PerlPhrasebook has apparently not been updated since then. Acutally, I just checked - it was last updated in 2012. Maybe this suggests that not many people use that document any more? I.e. all Perl programmers who are likely to migrate to Python have already done so? This suggests that maybe writing my own guide is pointless. (Not that pointlessness has ever stopped me from doing something.)

Monday, July 1, 2024

Automating tcpdump in Test Scripts

 It's not unusual for me to create a shell script to test networking software, and to have it automatically run tcpdump in the background while the test runs. Generally I do this "just in case something gets weird," so I usually don't pay much attention to the capture.

The other day I was testing my new "raw_send" tool, and my test script consisted of:

INTFC=em1
tcpdump -i $INTFC -w /tmp/raw_send.pcap &
TCPDUMP_PID=$!
sleep 0.2
./raw_send $INTFC \
 01005e000016000f53797050080046c00028000040000102f5770a1d0465e0000016940400002200e78d0000000104000000ef65030b
sleep 0.2
kill $TCPDUMP_PID

Lo and behold, the tcpdump did NOT contain my packet! I won't embarrass myself by outlining the DAY AND A HALF I spent figuring out what was going on, so I'll just give the answer.

The tcpdump tool wants to be as efficient as possible, so it buffers the packets being written to the output file. This is important because a few large file writes are MUCH more efficient than many small file writes. When you kill the tcpdump (either with the "kill" command or with control-C), it does NOT flush out the current partial buffer. There was a small clue provided in the form of the following output:

tcpdump: listening on em1, link-type EN10MB (Ethernet), capture size 262144 bytes
0 packets captured
1 packets received by filter
0 packets dropped by kernel

I thought it was filtering out my packet for some reason. But no, the "0 packets captured" means that zero packets were written to the capture file ... because of buffering.

The solution? Add the option "--immediate-mode" to tcpdump:

tcpdump -i $INTFC -w /tmp/raw_send.pcap --immediate-mode &

Works fine.


Python and Perl and Bash (oh my!)

I've been thinking a lot recently about code maintainability, including scripts. I write a lot of shell scripts, usually restricting myself to Bourne shell features, not GNU Bash extensions. I also write a lot of Perl scripts, mostly because I'm a thousand years old, and back then, Perl was the state of the scripting art.

Anyway, it's not unusual for me to write a shell script that invokes Perl to do some regular expression magic. For example, I recently wanted to take a dotted IP address (e.g. "10.29.3.4") and convert it into a string of 8 hexadecimal digits representing the network order binary representation of the same (i.e. "0a1d0304"). My kneejerk reaction is this:

HEX=`echo "10.29.3.4" | perl -nle 'if (/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) { printf("%02x%02x%02x%02x\n", $1, $2, $3, $4) } else {die "invalid IP $_\n"'}`

But since maintainability has been on my mind lately, most programmers (especially younger ones) would have a steep Perl learning curve to maintain that. So my second thought was to do it in Bash directly. I haven't done much regular expression processing in Bash, given my habit of staying within Bourne, but really, that habit has outlived its usefulness. Open-source Unix (Linux and Berkley) have relegated other Unixes to rare niche use cases, and even those other Unixes have a way to download Bash. I should just accept that Bash extensions are fair game. Here's my second try:

pattern='^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$'
if [[ "$MCAST_ADDR" =~ $pattern ]]; then
  GRP_HEX=$(printf '%02x%02x%02x%02x' ${BASH_REMATCH[1]} ${BASH_REMATCH[2]} ${BASH_REMATCH[3]} ${BASH_REMATCH[4]})
else echo "invalid IP addr"; exit 1
fi

But even as I feel fairly confident that more programmers would be able to maintain that than the Perl version, I still realize that the vast majority of programmers I've known have very basic shell scripting skills. I'm not sure the Bash version expands the pool of qualified maintainers by much. I think the best that can be said is that a programmer with basic shell scripting skills can learn regular expression matching in Bash a lot faster than learning enough Perl to do it.

So, how about Python?

Well, with some help from Claude, here's a Python one-liner:

HEX=`python3 -c "import sys, ipaddress; print('{:08x}'.format(int(ipaddress.ip_address(sys.argv[1]))))" 10.29.2.3`

So, not only does this use a language that many programmers know, it also completely avoids regular expressions, which is another uncommon skill among the programmers I've known.

*sigh*

What's a dinosaur to do? I haven't been paid to be a programmer for a lot of years, so the programming I do is mostly for my own entertainment. And dammit, I *like* Perl! I've done enough Python programming to know that ... I just don't like it that much. And let's face it: the code I write is unlikely to be widely used, so who cares about maintainability?

(pause for effect)

I do.

I have standards on how programming should be done. If programming is now largely my hobby, I get the most personal reward by doing it according to my standards. I think it's time for me to say a fond farewell to Perl and bow down to my Python overlords.

Thursday, June 27, 2024

SIGINT(2) vs SIGTERM(15)

This is another of those things that I've always known I should just sit down and learn but never did: what's the difference between SIGINT and SIGTERM? I knew that one of them corresponded to control-c, and the other corresponded to the kill command's default signal, but I always treated them the same, so I never learned which was which.

  • SIGINT (2) - User interrupt signal, typically sent by typing control-C. The receiving program should stop performing its current operation and return as quickly as appropriate. For programs that maintain some kind of persistent state (e.g. data files), those programs should catch SIGINT and do enough cleanup to maintain consistency of state. For interactive programs, control-C might not exit the program, but instead return to the program's internal command prompt.

  • SIGTERM (15) - Graceful termination signal. For example, when the OS gracefully shuts down, it will send SIGTERM to all processes. It's also the default signal sent by the "kill" command. It is not considered an emergency and so does not expect the fastest possible exit; rather a program might allow the current operation to complete before exiting, so long as it doesn't take "too long" (whatever that is). Interactive programs should typically NOT return to their internal command prompt and should instead clean up (if necessary) and exit.

This differentiation was developed when the Unix system had many users and a system operator. If the operator initiated a shutdown, the expectation was that interactive programs would NOT just return to the command prompt, but instead would respect the convention of cleaning up and exiting.

However, I've seen that convention not followed by "personal computer" Unix systems, like MacOS. With a personal computer, you have a single user who is also the operator. If you, the user and operator, initiate a shutdown on a Mac, there can be interactive programs that will pause the shutdown and ask the user whether to save their work. It still represents a difference in behavior between SIGINT and SIGTERM - SIGINT returns to normal operation while SIGTERM usually brings up a separate dialogue box warning the user of data loss - but the old expectation of always exiting is no longer universal.


Sunday, June 23, 2024

New Outlook: Security/Privacy Issues

 So, my old MacBook Air finally became unusable as my primary machine (the screen backlight burned out, possibly due to my having dumped a can of Diet Coke onto it). I can still VNC to it, so it will continue life as a "headless" Mac, but I needed a new laptop.

Based on my satisfaction with my work system, which is Windows and WSL2, I decided to go that route. But I didn't want to go with Office 365 and its annual subscription - it just seemed a bit steep to me. So I uninstalled Office 365 and bought Office Home and Business 2021.

At first it seemed fine, until I wanted to sync the contacts with my iCloud contacts. I didn't have tons of time to mess with it, and there isn't an easy way to do it, until I noticed there there is a "New Outlook" that I could turn on. I did, and lo and behold, it was able to download my iCloud contacts without a problem. Mind you, it is still not SYNCING, it just took a snapshot, but I don't update my contacts very often, so I'm calling it a win.

But then I noticed that "New Outlook" immediately downloads pics on emails. They claim it's secure because it uses a Microsoft server as a proxy -- i.e. the sender of the email cannot see my IP address -- but we know that there are tracking images whose file names are just identifiers for the recipient. So in effect, the sender is informed when I look at their email. I don't like that.

Worse yet - when I hover over a clickable link, it doesn't tell me what the URL is! THIS IS UNACCEPTABLE! I think it might be going through Microsoft's "safe URL" thing, which is better than nothing, but not nearly good enough. Now I have to right-click the link, copy it, and paste it into my browser to make sure it looks OK before hitting enter.

What's next? Does the new Outlook automatically execute code sent with the email? It used to, maybe they're doing that again.

Here I was all ready to admit that Microsoft was finally taking security and privacy seriously enough for me to switch; boy was I wrong. It will irk me no end to pay for Office 365 after having paid for Outlook 2021.

I guess I'll try to figure out a way to complain to MS, but I'm also confident my complaint will go nowhere.

Tuesday, March 12, 2024

Downpour Games

One of my favorite YouTubers, Tom Scott, has an email list that he posts to periodically listing things that interest him. His latest one pointed to downpour.games which makes it easy to create simple games (Tom called them "interactive story games"). Here's an example to get the idea: https://downpour.games/~holly/where-s-madeleine

While the games can be played on a laptop, you need to use the phone app to create the games. It takes a bit of experimenting and exploring to figure out the user interface. Being a text-based thinker, it probably took me longer than average. My biggest stumbling block for me was not realizing that the bottom row of buttons ("Box", "Photo", "Gallery") has more than three buttons. You have to swipe left on the buttons to see "Text". Without "Text", I can't really make much of a game.

I, of course, immediately thought of making an "adventure" game. But then realized that all state is carried in the page you are on. So, for example, if you "take the coin", there isn't any way to carry that state forward. (I mean sure, you could have a completely duplicated maze, one with the coin in its home room and the other with the coin in your pocket, but what if you drop the coin in a different room? You would need (N+1)**2 rooms. And that's for a single takable object. So only exploratory adventure games here.)

On reflection, I think "Choose your own adventure" is a better analog than "interactive story game".

Anyway, a few years ago I experimented with trying to do something like this, just using web pages and links. It quickly became tedious and error-prone.

I don't know if I'll do much of anything with this. But it was interesting enough for me to bring up.